From 6078e8a903e6ee4ae5c3b6f0fd0ef112ef5e1ec1 Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 25 Aug 2016 22:29:47 +0800 Subject: [PATCH 0001/2161] compatible for laravel 5.3 --- src/Form.php | 2 +- src/Grid.php | 4 +--- src/Routing/Router.php | 6 +++--- views/pagination.blade.php | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 views/pagination.blade.php diff --git a/src/Form.php b/src/Form.php index e884b26043..76e5a63851 100644 --- a/src/Form.php +++ b/src/Form.php @@ -539,7 +539,7 @@ protected static function getDataByColumn($data, $columns) protected function getFieldByColumn($column) { return $this->builder->fields()->first( - function ($index, Field $field) use ($column) { + function (Field $field) use ($column) { if (is_array($field->column())) { return in_array($column, $field->column()); } diff --git a/src/Grid.php b/src/Grid.php index 893a7e87c5..df9e88fdd5 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -244,9 +244,7 @@ public function paginator() { $query = Input::all(); - return $this->model()->eloquent()->appends($query)->render( - new AdminThreePresenter($this->model()->eloquent()) - ); + return $this->model()->eloquent()->appends($query)->render('admin::pagination'); } /** diff --git a/src/Routing/Router.php b/src/Routing/Router.php index ca8e4d777f..c23287f472 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -72,9 +72,9 @@ protected function setAdminRoutes() 'auth/permissions' => 'PermissionController', ]); - $router->controllers([ - 'auth' => 'AuthController', - ]); + $router->get('auth/login', 'AuthController@getLogin'); + $router->post('auth/login', 'AuthController@postLogin'); + $router->get('auth/logout', 'AuthController@getLogout'); }); } diff --git a/views/pagination.blade.php b/views/pagination.blade.php new file mode 100644 index 0000000000..7ee6c2671e --- /dev/null +++ b/views/pagination.blade.php @@ -0,0 +1,34 @@ + From 1922cd7b72fa8b5d9999e0da7a7cdfeb519f3d42 Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 25 Aug 2016 22:43:45 +0800 Subject: [PATCH 0002/2161] fix bug --- composer.json | 6 ++---- src/Grid.php | 1 - src/Pagination/AdminThreePresenter.php | 22 ---------------------- 3 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 src/Pagination/AdminThreePresenter.php diff --git a/composer.json b/composer.json index d2c4a2caa0..1c70bf2eb8 100644 --- a/composer.json +++ b/composer.json @@ -13,14 +13,12 @@ ], "require": { "php": ">=5.4.0", - "illuminate/support": "~5.0", + "laravel/framework": "~5.3", "intervention/image" : "~2.3" }, "require-dev": { "phpunit/phpunit": "~4.0", - "phpspec/phpspec": "~2.1", - "laracasts/testdummy": "~2.0", - "laravel/laravel": "~5.2", + "laravel/laravel": "~5.3", "symfony/css-selector": "2.8.*|3.0.*", "symfony/dom-crawler": "2.8.*|3.0.*" }, diff --git a/src/Grid.php b/src/Grid.php index df9e88fdd5..f3fb8a823f 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -9,7 +9,6 @@ use Encore\Admin\Grid\Filter; use Encore\Admin\Grid\Model; use Encore\Admin\Grid\Row; -use Encore\Admin\Pagination\AdminThreePresenter; use Illuminate\Database\Eloquent\Model as Eloquent; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Collection; diff --git a/src/Pagination/AdminThreePresenter.php b/src/Pagination/AdminThreePresenter.php deleted file mode 100644 index 28f454b31f..0000000000 --- a/src/Pagination/AdminThreePresenter.php +++ /dev/null @@ -1,22 +0,0 @@ -hasPages()) { - return sprintf( - '', - $this->getPreviousButton(), - $this->getLinks(), - $this->getNextButton() - ); - } - - return ''; - } -} From e005fb677772265885427d0f9a42f2299dd595ff Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 25 Aug 2016 22:52:21 +0800 Subject: [PATCH 0003/2161] fix bug --- .travis.yml | 1 - composer.json | 2 +- tests/LaravelTest.php | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a199dfcf1a..7f41b49604 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - 5.5 - 5.6 - 7 diff --git a/composer.json b/composer.json index 1c70bf2eb8..170fddb122 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require": { - "php": ">=5.4.0", + "php": ">=5.6.4", "laravel/framework": "~5.3", "intervention/image" : "~2.3" }, diff --git a/tests/LaravelTest.php b/tests/LaravelTest.php index 34248c2971..541521c941 100644 --- a/tests/LaravelTest.php +++ b/tests/LaravelTest.php @@ -5,6 +5,7 @@ class LaravelTest extends TestCase public function testLaravel() { $this->visit('/') - ->see('Laravel 5'); + ->assertResponseStatus(200) + ->see('Laravel'); } } From 58b56fb86764264c58e0d3e9394fae2080a0ae7c Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 14 Sep 2016 18:13:40 +0800 Subject: [PATCH 0004/2161] fix pjax html entity encode issue --- src/Middleware/PjaxMiddleware.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/PjaxMiddleware.php b/src/Middleware/PjaxMiddleware.php index 9f7f06a0f6..6a7b77e07c 100644 --- a/src/Middleware/PjaxMiddleware.php +++ b/src/Middleware/PjaxMiddleware.php @@ -81,7 +81,7 @@ protected function fetchContents($crawler, $container) abort(422); } - return $content->html(); + return html_entity_decode($content->html()); } /** From aa5ecfaf5340b192739175f72beca445555ed6bd Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 22 Sep 2016 13:39:35 +0800 Subject: [PATCH 0005/2161] issue fix --- src/Form/Builder.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Form/Builder.php b/src/Form/Builder.php index cc81325f9a..de72049685 100644 --- a/src/Form/Builder.php +++ b/src/Form/Builder.php @@ -193,12 +193,18 @@ public function render() $confirm = trans('admin::lang.delete_confirm'); $token = csrf_token(); + $location = '/' . trim($this->form->resource(), '/'); + $script = << + @if(config('app.locale') == 'zh_CN') diff --git a/views/partials/menu.blade.php b/views/partials/menu.blade.php index 09a5801488..cf602cf2df 100644 --- a/views/partials/menu.blade.php +++ b/views/partials/menu.blade.php @@ -1,7 +1,7 @@ -@if(!isset($item['roles']) || (isset($item['roles']) && Admin::user()->isRole($item['roles']))) +@if(Admin::user()->visible($item['roles'])) @if(!isset($item['children']))
  • - + {{$item['title']}}
  • diff --git a/views/tree.blade.php b/views/tree.blade.php index 893a72ec5f..2115d4a436 100644 --- a/views/tree.blade.php +++ b/views/tree.blade.php @@ -1,6 +1,37 @@ -@extends('admin::index') +
    +
    +

    -@section('content') - {!! $tree !!} -@endsection + +
    + +
    +
    +
      + @each('admin::tree.branch', $items, 'branch') +
    +
    +
    + + +
    \ No newline at end of file diff --git a/views/tree/branch.blade.php b/views/tree/branch.blade.php new file mode 100644 index 0000000000..b535744b27 --- /dev/null +++ b/views/tree/branch.blade.php @@ -0,0 +1,26 @@ +@if(!isset($branch['children'])) +
  • +
    + {{ $branch['title'] }} + + + + +
    +
  • +@else +
  • +
    + {{ $branch['title'] }} + + + + +
    +
      + @foreach($branch['children'] as $branch) + @include('admin::tree.branch', $branch) + @endforeach +
    +
  • +@endif \ No newline at end of file diff --git a/views/widgets/callout.blade.php b/views/widgets/callout.blade.php index 630dc2a42f..5d51273d8d 100644 --- a/views/widgets/callout.blade.php +++ b/views/widgets/callout.blade.php @@ -2,5 +2,5 @@ @if(isset($title))

    {{ $title }}

    @endif - {{ $content }} + {!! $content !!} \ No newline at end of file From ac2e1fedb8096f22cfd73a676c85d12a70027748 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 10 Oct 2016 18:26:17 +0800 Subject: [PATCH 0019/2161] issue fix --- src/Widgets/Form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Widgets/Form.php b/src/Widgets/Form.php index d35aebbc60..e301a4542a 100644 --- a/src/Widgets/Form.php +++ b/src/Widgets/Form.php @@ -255,7 +255,7 @@ public function __call($method, $arguments) */ public function render() { - return view('admin::widgets.form', $this->getVariables()); + return view('admin::widgets.form', $this->getVariables())->render(); } /** From d2fa9119e2208da77eddafeb2743b979ad0ee63b Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 12 Oct 2016 10:21:46 +0800 Subject: [PATCH 0020/2161] add tests --- config/admin.php | 14 +++++++------- tests/AuthTest.php | 25 ++++++++++++++++++++++++- tests/MenuTest.php | 4 ++-- tests/PermissionsTest.php | 22 +++++++++++++++++----- tests/RolesTest.php | 15 ++++++++++++--- tests/UsersTest.php | 4 ++-- 6 files changed, 64 insertions(+), 20 deletions(-) diff --git a/config/admin.php b/config/admin.php index ef1c2d36ed..bf085a184e 100644 --- a/config/admin.php +++ b/config/admin.php @@ -20,21 +20,21 @@ ], 'database' => [ - 'users_table' => 'administrators', + 'users_table' => 'admin_users', 'users_model' => Encore\Admin\Auth\Database\Administrator::class, - 'roles_table' => 'roles', + 'roles_table' => 'admin_roles', 'roles_model' => Encore\Admin\Auth\Database\Role::class, - 'permissions_table' => 'permissions', + 'permissions_table' => 'admin_permissions', 'permissions_model' => Encore\Admin\Auth\Database\Permission::class, - 'menu_table' => 'menu', + 'menu_table' => 'admin_menu', 'menu_model' => Encore\Admin\Auth\Database\Menu::class, - 'role_users_table' => 'role_administrators', - 'role_permissions_table' => 'role_permissions', - 'role_menu_table' => 'role_menu', + 'role_users_table' => 'admin_role_users', + 'role_permissions_table' => 'admin_role_permissions', + 'role_menu_table' => 'admin_role_menu', ], /* diff --git a/tests/AuthTest.php b/tests/AuthTest.php index 2774bbb23b..ea19f1d23f 100644 --- a/tests/AuthTest.php +++ b/tests/AuthTest.php @@ -25,7 +25,30 @@ public function testLogin() ->see('dashboard') ->seeCredentials($credentials, 'admin') ->seeIsAuthenticated('admin') - ->seePageIs('admin'); + ->seePageIs('admin') + ->see('Menu') + ->see('Index') + ->see('Auth') + ->see('Users') + ->see('Roles') + ->see('Permission') + ->see('Menu') + ->see('1024') + ->see('150%') + ->see('2786') + ->see('698726') + ->see('Tabs') + ->see('Radar') + ->see('Bar') + ->see('Orders') + ->see('Polar Area') + ->see('Doughnut') + ->see('Line') + ->see('Table') + ->see('Email') + ->see('Last Login') + ->see('Copyright') + ->see('Version'); } public function testLogout() diff --git a/tests/MenuTest.php b/tests/MenuTest.php index 81b9e593ad..52912d2e17 100644 --- a/tests/MenuTest.php +++ b/tests/MenuTest.php @@ -35,7 +35,7 @@ public function testAddMenu() ->see('Tips') ->submitForm('Submit', $item) ->seePageIs('admin/auth/menu') - ->seeInDatabase('menu', $item) + ->seeInDatabase(config('admin.database.menu_table'), $item) ->assertEquals(7, Menu::count()); $this->setExpectedException(Illuminate\Foundation\Testing\HttpException::class); @@ -57,7 +57,7 @@ public function testEditMenu() ->see('Menu') ->submitForm('Submit', ['title' => 'blablabla']) ->seePageIs('admin/auth/menu') - ->seeInDatabase('menu', ['title' => 'blablabla']) + ->seeInDatabase(config('admin.database.menu_table'), ['title' => 'blablabla']) ->assertEquals(6, Menu::count()); } } diff --git a/tests/PermissionsTest.php b/tests/PermissionsTest.php index 6712e647a6..3aedf3c1c2 100644 --- a/tests/PermissionsTest.php +++ b/tests/PermissionsTest.php @@ -24,10 +24,22 @@ public function testAddAndDeletePermission() ->see('Permissions') ->submitForm('Submit', ['slug' => 'can-edit', 'name' => 'Can edit']) ->seePageIs('admin/auth/permissions') - ->seeInDatabase('permissions', ['slug' => 'can-edit']) - ->seeInDatabase('permissions', ['name' => 'Can edit']) + ->seeInDatabase(config('admin.database.permissions_table'), ['slug' => 'can-edit']) + ->seeInDatabase(config('admin.database.permissions_table'), ['name' => 'Can edit']) ->assertEquals(1, Permission::count()); + } + public function testAddPermissionToRole() + { + $this->visit('admin/auth/roles/1/edit') + ->see('Edit') + ->submitForm('Submit', ['permissions' => [1]]) + ->seePageIs('admin/auth/roles') + ->seeInDatabase(config('admin.database.role_permissions_table'), ['role_id' => 1, 'permission_id' => 1]); + } + + public function testDeletePermission() + { $this->delete('admin/auth/permissions/1') ->assertEquals(0, Permission::count()); } @@ -38,15 +50,15 @@ public function testEditPermission() ->see('Permissions') ->submitForm('Submit', ['slug' => 'can-edit', 'name' => 'Can edit']) ->seePageIs('admin/auth/permissions') - ->seeInDatabase('permissions', ['slug' => 'can-edit']) - ->seeInDatabase('permissions', ['name' => 'Can edit']) + ->seeInDatabase(config('admin.database.permissions_table'), ['slug' => 'can-edit']) + ->seeInDatabase(config('admin.database.permissions_table'), ['name' => 'Can edit']) ->assertEquals(1, Permission::count()); $this->visit('admin/auth/permissions/1/edit') ->see('Permissions') ->submitForm('Submit', ['slug' => 'can-delete']) ->seePageIs('admin/auth/permissions') - ->seeInDatabase('permissions', ['slug' => 'can-delete']) + ->seeInDatabase(config('admin.database.permissions_table'), ['slug' => 'can-delete']) ->assertEquals(1, Permission::count()); } } diff --git a/tests/RolesTest.php b/tests/RolesTest.php index 7d0ce86ef7..56657cc649 100644 --- a/tests/RolesTest.php +++ b/tests/RolesTest.php @@ -25,11 +25,20 @@ public function testAddRole() ->see('Roles') ->submitForm('Submit', ['slug' => 'developer', 'name' => 'Developer...']) ->seePageIs('admin/auth/roles') - ->seeInDatabase('roles', ['slug' => 'developer']) - ->seeInDatabase('roles', ['name' => 'Developer...']) + ->seeInDatabase(config('admin.database.roles_table'), ['slug' => 'developer']) + ->seeInDatabase(config('admin.database.roles_table'), ['name' => 'Developer...']) ->assertEquals(2, Role::count()); } + public function testAddRoleToUser() + { + $this->visit('admin/auth/users/1/edit') + ->see('Edit') + ->submitForm('Submit', ['roles' => [2]]) + ->seePageIs('admin/auth/users') + ->seeInDatabase(config('admin.database.role_users_table'), ['user_id' => 1, 'role_id' => 2]); + } + public function testDeleteRole() { $this->assertEquals(1, Role::count()); @@ -44,7 +53,7 @@ public function testEditRole() ->see('Roles') ->submitForm('Submit', ['name' => 'blablabla']) ->seePageIs('admin/auth/roles') - ->seeInDatabase('roles', ['name' => 'blablabla']) + ->seeInDatabase(config('admin.database.roles_table'), ['name' => 'blablabla']) ->assertEquals(1, Role::count()); } } diff --git a/tests/UsersTest.php b/tests/UsersTest.php index 32677fd667..a09462b11d 100644 --- a/tests/UsersTest.php +++ b/tests/UsersTest.php @@ -33,7 +33,7 @@ public function testCreateUser() ->see('Create') ->submitForm('Submit', $user) ->seePageIs('admin/auth/users') - ->seeInDatabase('administrators', ['username' => 'Test']); + ->seeInDatabase(config('admin.database.users_table'), ['username' => 'Test']); $this->visit('admin/auth/logout') ->dontSeeIsAuthenticated('admin') @@ -50,7 +50,7 @@ public function testUpdateUser() ->see('Create') ->submitForm('Submit', ['name' => 'test']) ->seePageIs('admin/auth/users') - ->seeInDatabase('administrators', ['name' => 'test']); + ->seeInDatabase(config('admin.database.users_table'), ['name' => 'test']); } public function testResetPassword() From f682912eb5e19386e3df0a7c1b9376e85b054180 Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 13 Oct 2016 18:27:00 +0800 Subject: [PATCH 0021/2161] fix case ignore issue --- views/form/multipleSelect.blade.php | 16 ---------------- views/form/switchField.blade.php | 13 ------------- 2 files changed, 29 deletions(-) delete mode 100644 views/form/multipleSelect.blade.php delete mode 100644 views/form/switchField.blade.php diff --git a/views/form/multipleSelect.blade.php b/views/form/multipleSelect.blade.php deleted file mode 100644 index 19eca95912..0000000000 --- a/views/form/multipleSelect.blade.php +++ /dev/null @@ -1,16 +0,0 @@ -
    - - - -
    - - @include('admin::form.error') - - - -
    -
    \ No newline at end of file diff --git a/views/form/switchField.blade.php b/views/form/switchField.blade.php deleted file mode 100644 index eef2ef7b79..0000000000 --- a/views/form/switchField.blade.php +++ /dev/null @@ -1,13 +0,0 @@ -
    - - - -
    - - @include('admin::form.error') - - - - -
    -
    \ No newline at end of file From cbd5e5f26620ba32b48c71404916f518200ee6e0 Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 14 Oct 2016 14:52:15 +0800 Subject: [PATCH 0022/2161] image upload issue --- src/Form.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Form.php b/src/Form.php index 1b7e72a702..91f67df024 100644 --- a/src/Form.php +++ b/src/Form.php @@ -603,12 +603,20 @@ protected function validate($input) $columns = $field->column(); if (is_string($columns)) { + + if (!array_key_exists($columns, $input)) { + continue; + } + $data[$field->label()] = array_get($input, $columns); $rules[$field->label()] = $rule; } if (is_array($columns)) { foreach ($columns as $key => $column) { + if (!array_key_exists($column, $input)) { + continue; + } $data[$field->label().$key] = array_get($input, $column); $rules[$field->label().$key] = $rule; } From 8abe1e9878605ae5e61fa7c34ad7d5d56c25bf42 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 17 Oct 2016 18:24:55 +0800 Subject: [PATCH 0023/2161] fix issue #55 --- src/Form.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Form.php b/src/Form.php index 91f67df024..c6ee56b880 100644 --- a/src/Form.php +++ b/src/Form.php @@ -321,6 +321,7 @@ protected function saveRelation($relations) switch (get_class($relation)) { case \Illuminate\Database\Eloquent\Relations\BelongsToMany::class: + case \Illuminate\Database\Eloquent\Relations\MorphToMany::class: $relation->attach($values[$name]); break; case \Illuminate\Database\Eloquent\Relations\HasOne::class: @@ -395,6 +396,7 @@ protected function updateRelation($relations) switch (get_class($relation)) { case \Illuminate\Database\Eloquent\Relations\BelongsToMany::class: + case \Illuminate\Database\Eloquent\Relations\MorphToMany::class: $relation->sync($prepared[$name]); break; case \Illuminate\Database\Eloquent\Relations\HasOne::class: From c24862285191b43aed99da47e644fb9351d3f8fe Mon Sep 17 00:00:00 2001 From: Song Date: Mon, 17 Oct 2016 10:27:51 +0000 Subject: [PATCH 0024/2161] Applied fixes from StyleCI --- src/Form.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Form.php b/src/Form.php index c6ee56b880..e4584469bd 100644 --- a/src/Form.php +++ b/src/Form.php @@ -605,7 +605,6 @@ protected function validate($input) $columns = $field->column(); if (is_string($columns)) { - if (!array_key_exists($columns, $input)) { continue; } From ce687da86d1d91d5003b8327b4b33f6b6c89ec66 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 17 Oct 2016 18:47:26 +0800 Subject: [PATCH 0025/2161] update comment --- src/Form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Form.php b/src/Form.php index e4584469bd..4f6d386006 100644 --- a/src/Form.php +++ b/src/Form.php @@ -50,7 +50,7 @@ * @method Field\Display display($column, $label = '') * @method Field\Rate rate($column, $label = '') * @method Field\Divide divide() - * @method Field\Password password() + * @method Field\Password password($column, $label = '') */ class Form { From 26b035c3ac40faa21e753c1ae91a1b654c52d5a0 Mon Sep 17 00:00:00 2001 From: z-song Date: Tue, 18 Oct 2016 11:06:06 +0800 Subject: [PATCH 0026/2161] fix issue & update README --- README.md | 5 +++++ docs/zh/model-form.md | 40 ++++++++++++++++++++++++++++++++++++++- src/Form/Field/Select.php | 36 +++++++++++++++++++++++++++++++++-- src/Grid/Row.php | 2 +- 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8b0f8eb515..0c104cd176 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,11 @@ $router->resource('users', UserController::class); Inspired by [SleepingOwlAdmin](https://github.com/sleeping-owl/admin) and [rapyd-laravel](https://github.com/zofe/rapyd-laravel). +交流 +------------ +QQ群:278455482 + + License ------------ `laravel-admin` is licensed under [The MIT License (MIT)](LICENSE). diff --git a/docs/zh/model-form.md b/docs/zh/model-form.md index bf1176e716..f605c3e796 100644 --- a/docs/zh/model-form.md +++ b/docs/zh/model-form.md @@ -18,7 +18,7 @@ CREATE TABLE `movies` ( ``` -对应的数据模型为`App\Models\Movie`,下面的代码可以生成`users`的数据表单: +对应的数据模型为`App\Models\Movie`,下面的代码可以生成`movies`的数据表单: ```php @@ -80,11 +80,49 @@ $form->text($column, [$label])->rules('required|min:10'); $form->select($column[, $label])->options([1 => 'foo', 2 => 'bar', 'val' => 'Option name']); ``` +或者通过ajax加载option选项 +```php +$form->select($column[, $label])->options('/admin/demo/options'); +``` + +url `/admin/demo/options`返回json格式: +``` +[ + { + "id": 1, + "text": "hello" + }, + { + "id": 2, + "text": "world" + }, +] +``` + #### 多选框 ```php $form->multipleSelect($column[, $label])->options([1 => 'foo', 2 => 'bar', 'val' => 'Option name']); ``` +或者通过ajax加载option选项 +```php +$form->multipleSelect($column[, $label])->options('/admin/demo/options'); +``` + +url `/admin/demo/options`返回json格式: +``` +[ + { + "id": 1, + "text": "hello" + }, + { + "id": 2, + "text": "world" + }, +] +``` + #### textarea输入框: ```php $form->textarea($column[, $label]); diff --git a/src/Form/Field/Select.php b/src/Form/Field/Select.php index 718f37784a..5bc90a3fe1 100644 --- a/src/Form/Field/Select.php +++ b/src/Form/Field/Select.php @@ -9,18 +9,50 @@ class Select extends Field { public function render() { - $this->script = "$(\"#{$this->id}\").select2({allowClear: true});"; + $this->script .= "$(\"#{$this->id}\").select2({allowClear: true});"; return parent::render()->with(['options' => $this->options]); } public function options($options = []) { + // remote options + if (is_string($options)) { + return call_user_func_array([$this, 'loadOptionsFromRemote'], func_get_args()); + } + if ($options instanceof Arrayable) { $options = $options->toArray(); } - $this->options = $options; + $this->options = (array) $options; + + return $this; + } + + /** + * Load options from remote + * + * @param string $url + * @param array $parameters + * @param array $options + * @return $this + */ + protected function loadOptionsFromRemote($url, $parameters = [], $options = []) + { + $ajaxOptions = [ + 'url' => $url.'?'.http_build_query($parameters) + ]; + + $ajaxOptions = json_encode(array_merge($ajaxOptions, $options)); + + $this->script .= <<id}").select2({data: data}); +}); + +EOT; return $this; } diff --git a/src/Grid/Row.php b/src/Grid/Row.php index aa32a184a3..a8e4bc7e54 100644 --- a/src/Grid/Row.php +++ b/src/Grid/Row.php @@ -183,7 +183,7 @@ public function cells() */ public function __get($attr) { - return $this->data[$attr] ?: null; + return array_get($this->data, $attr); } /** From 46f7c6cb0cdd5e9e2dbbbe7973213e9b0fc89afd Mon Sep 17 00:00:00 2001 From: Song Date: Tue, 18 Oct 2016 03:08:34 +0000 Subject: [PATCH 0027/2161] Applied fixes from StyleCI --- src/Form/Field/Select.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Form/Field/Select.php b/src/Form/Field/Select.php index 5bc90a3fe1..3f077364c8 100644 --- a/src/Form/Field/Select.php +++ b/src/Form/Field/Select.php @@ -31,17 +31,18 @@ public function options($options = []) } /** - * Load options from remote + * Load options from remote. * * @param string $url - * @param array $parameters - * @param array $options + * @param array $parameters + * @param array $options + * * @return $this */ protected function loadOptionsFromRemote($url, $parameters = [], $options = []) { $ajaxOptions = [ - 'url' => $url.'?'.http_build_query($parameters) + 'url' => $url.'?'.http_build_query($parameters), ]; $ajaxOptions = json_encode(array_merge($ajaxOptions, $options)); From 8eced21c04af7cec745c17bf8a7ea9874694b7cb Mon Sep 17 00:00:00 2001 From: z-song Date: Tue, 18 Oct 2016 18:14:52 +0800 Subject: [PATCH 0028/2161] support clould upload & update README --- README.md | 1 + config/admin.php | 11 ++++- docs/zh/README.md | 10 +++- docs/zh/form-upload.md | 101 +++++++++++++++++++++++++++++++++++++++ src/Form/Field/File.php | 40 +++++++++++----- src/Form/Field/Image.php | 13 +++-- src/Grid/Column.php | 7 ++- 7 files changed, 163 insertions(+), 20 deletions(-) create mode 100644 docs/zh/form-upload.md diff --git a/README.md b/README.md index 0c104cd176..d67fcddfa2 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ php artisan admin:install - [显示布局](/docs/zh/layout.md) - [数据模型表格](/docs/zh/model-grid.md) - [数据模型表单](/docs/zh/model-form.md) + - [图片/文件上传](/docs/zh/form-upload.md) - [组件](/docs/zh/table.md) - [表格](/docs/zh/table.md) - [表单](/docs/zh/form.md) diff --git a/config/admin.php b/config/admin.php index bf085a184e..56034b04e7 100644 --- a/config/admin.php +++ b/config/admin.php @@ -15,8 +15,15 @@ ], 'upload' => [ - 'image' => base_path('public/upload/image'), - 'file' => base_path('public/upload/file'), + + 'disk' => 'admin', + + 'directory' => [ + 'image' => 'image', + 'file' => 'file', + ], + + 'host' => 'http://localhost:8000/upload/', ], 'database' => [ diff --git a/docs/zh/README.md b/docs/zh/README.md index 6d55bc1afc..d67fcddfa2 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -3,6 +3,8 @@ laravel-admin [![Build Status](https://travis-ci.org/z-song/laravel-admin.svg?branch=master)](https://travis-ci.org/z-song/laravel-admin) [![StyleCI](https://styleci.io/repos/48796179/shield)](https://styleci.io/repos/48796179) +[![Packagist](https://img.shields.io/packagist/l/encore/laravel-admin.svg?maxAge=2592000)](https://packagist.org/packages/encore/laravel-admin) +[![Total Downloads](https://img.shields.io/packagist/dt/encore/laravel-admin.svg?style=flat-square)](https://packagist.org/packages/encore/laravel-admin) `laravel-admin` 是一个可以快速帮你构建后台管理的工具,它提供的页面组件和表单元素等功能,能帮助你使用很少的代码就实现功能完善的后台管理功能。 @@ -53,12 +55,13 @@ php artisan admin:install - [显示布局](/docs/zh/layout.md) - [数据模型表格](/docs/zh/model-grid.md) - [数据模型表单](/docs/zh/model-form.md) + - [图片/文件上传](/docs/zh/form-upload.md) - [组件](/docs/zh/table.md) - [表格](/docs/zh/table.md) - [表单](/docs/zh/form.md) - [盒子](/docs/zh/box.md) - [信息盒子](/docs/zh/info-box.md) - - [选项卡](/docs/zh/box.md) + - [选项卡](/docs/zh/tab.md) - [滑动相册](/docs/zh/carousel.md) - [折叠容器](/docs/zh/collapse.md) - 数据图表 TODO @@ -249,6 +252,11 @@ $router->resource('users', UserController::class); Inspired by [SleepingOwlAdmin](https://github.com/sleeping-owl/admin) and [rapyd-laravel](https://github.com/zofe/rapyd-laravel). +交流 +------------ +QQ群:278455482 + + License ------------ `laravel-admin` is licensed under [The MIT License (MIT)](LICENSE). diff --git a/docs/zh/form-upload.md b/docs/zh/form-upload.md new file mode 100644 index 0000000000..86181a6c35 --- /dev/null +++ b/docs/zh/form-upload.md @@ -0,0 +1,101 @@ +# 文件/图片上传 + +[model-form](/docs/zh/model-form.md)通过以下的调用来生成form元素。 + +```php +$form->file('file_column'); +$form->image('image_column'); +``` + +[model-form](/docs/zh/model-form.md)支持本地和云存储的文件上传 + +### 本地上传 + +先添加存储配置,`config/filesystems.php` 添加一项`disk`: + +```php + +'disks' => [ + ... , + + 'admin' => [ + 'driver' => 'local', + 'root' => public_path('upload'), + 'visibility' => 'public', + ], +], + +``` + +设置上传的路径为`public/upload`(public_path('upload'))。 + +然后选择上传的`disk`,打开`config/admin.php`找到: + +```php + +'upload' => [ + + 'disk' => 'admin', + + 'directory' => [ + 'image' => 'image', + 'file' => 'file', + ], + + 'host' => 'http://localhost:8000/upload/', +], + + +``` + +将`disk`设置为上面添加的`admin`,`directory.image`和`directory.file`分别为用`$form->image($column)`和`$form->file($column)`上传的图片和文件的上传目录 + +`host`为图片和文件的网络访问url前缀。 + + +### 云盘上传 + +如果需要上传到云存储,需要安装兼容`laravel storage`操作方式的driver,拿七牛云存储举例 + +首先安装 [zgldh/qiniu-laravel-storage](https://github.com/zgldh/qiniu-laravel-storage) + +同样配置好disk,在`config/filesystems.php` 添加一项: + +```php +'disks' => [ + ... , + 'qiniu' => [ + 'driver' => 'qiniu', + 'domains' => [ + 'default' => 'xxxxx.com1.z0.glb.clouddn.com', //你的七牛域名 + 'https' => 'dn-yourdomain.qbox.me', //你的HTTPS域名 + 'custom' => 'static.abc.com', //你的自定义域名 + ], + 'access_key'=> '', //AccessKey + 'secret_key'=> '', //SecretKey + 'bucket' => '', //Bucket名字 + 'notify_url'=> '', //持久化处理回调地址 + ], +], + +``` + +然后修改`laravel-admin`的上传配置,打开`config/admin.php`找到: + +```php + +'upload' => [ + + 'disk' => 'qiniu', + + 'directory' => [ + 'image' => 'image', + 'file' => 'file', + ], + + 'host' => 'http://of8kfibjo.bkt.clouddn.com/', +], + +``` + +`disk`选择上面配置的`qiniu`,`host`配置为七牛云存储的测试域名。 diff --git a/src/Form/Field/File.php b/src/Form/Field/File.php index 787f6e4f08..c2805250ee 100644 --- a/src/Form/Field/File.php +++ b/src/Form/Field/File.php @@ -4,6 +4,7 @@ use Encore\Admin\Form\Field; use Illuminate\Support\Facades\Input; +use Illuminate\Support\Facades\Storage; use Symfony\Component\HttpFoundation\File\UploadedFile; class File extends Field @@ -17,9 +18,12 @@ class File extends Field protected $options = []; + protected $storage = ''; + public function __construct($column, $arguments = []) { $this->initOptions(); + $this->initStorage(); parent::__construct($column, $arguments); } @@ -33,6 +37,16 @@ protected function initOptions() ]; } + protected function initStorage() + { + $this->storage = Storage::disk(config('admin.upload.disk')); + } + + public function defaultStorePath() + { + return config('admin.upload.directory.file'); + } + public function move($directory, $name = null) { $this->directory = $directory; @@ -52,14 +66,11 @@ public function prepare(UploadedFile $file = null) return $this->original; } - $this->directory = $this->directory ? - $this->directory : config('admin.upload.file'); - - $this->name = $this->name ? $this->name : $file->getClientOriginalName(); + $this->directory = $this->directory ?: $this->defaultStorePath(); - $target = $this->uploadAndDeleteOriginal($file); + $this->name = $this->name ?: $file->getClientOriginalName(); - return trim(str_replace(public_path(), '', $target->__toString()), '/'); + return $this->uploadAndDeleteOriginal($file); } /** @@ -71,7 +82,9 @@ protected function uploadAndDeleteOriginal(UploadedFile $file) { $this->renameIfExists($file); - $target = $file->move($this->directory, $this->name); + $target = $this->directory.'/'.$this->name; + + $this->storage->put($target, file_get_contents($file->getRealPath())); $this->destroy(); @@ -85,11 +98,11 @@ protected function preview() return <<
    - +
    EOT; } @@ -101,6 +114,11 @@ public function options($options = []) return $this; } + public function objectUrl($path) + { + return trim(config('admin.upload.host'), '/') .'/'. trim($path, '/'); + } + public function render() { $this->js[] = 'bootstrap-fileinput/js/fileinput_locale_'.config('app.locale').'.js'; @@ -151,13 +169,13 @@ public function isDeleteRequest() */ public function renameIfExists(UploadedFile $file) { - if (file_exists("$this->directory/$this->name")) { + if ($this->storage->exists("$this->directory/$this->name")) { $this->name = md5(uniqid()).'.'.$file->guessExtension(); } } public function destroy() { - @unlink($this->original); + $this->storage->delete($this->original); } } diff --git a/src/Form/Field/Image.php b/src/Form/Field/Image.php index c75769dd77..edff1bb27a 100644 --- a/src/Form/Field/Image.php +++ b/src/Form/Field/Image.php @@ -11,6 +11,11 @@ class Image extends File protected $calls = []; + public function defaultStorePath() + { + return config('admin.upload.directory.image'); + } + public function prepare(UploadedFile $image = null) { if (is_null($image)) { @@ -21,15 +26,15 @@ public function prepare(UploadedFile $image = null) return $this->original; } - $this->directory = $this->directory ? $this->directory : config('admin.upload.image'); + $this->directory = $this->directory ?: $this->defaultStorePath(); - $this->name = $this->name ? $this->name : $image->getClientOriginalName(); + $this->name = $this->name ?: $image->getClientOriginalName(); $target = $this->uploadAndDeleteOriginal($image); $target = $this->executeCalls($target); - return trim(str_replace(public_path(), '', $target->__toString()), '/'); + return $target; } /** @@ -52,7 +57,7 @@ public function executeCalls($target) protected function preview() { - return ''; + return ''; } public function render() diff --git a/src/Grid/Column.php b/src/Grid/Column.php index 6604675d3f..f57afdefc9 100644 --- a/src/Grid/Column.php +++ b/src/Grid/Column.php @@ -265,14 +265,17 @@ public function progressBar($style = 'primary', $size = 'sm', $max = 100) /** * Wrap value as a image. * + * @param string $server * @param int $width * @param int $height * * @return $this */ - public function image($width = 200, $height = 200) + public function image($server = '', $width = 200, $height = 200) { - $wrapper = ""; + $server = $server ?: config('admin.upload.host'); + + $wrapper = ""; $this->htmlWrapper($wrapper); From cbd0c8382101747ad59ad0aef4e7744a705ad35d Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 19 Oct 2016 10:42:04 +0800 Subject: [PATCH 0029/2161] modify pjax default timeout --- views/index.blade.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/views/index.blade.php b/views/index.blade.php index 0a6df89e49..ac97ed6964 100644 --- a/views/index.blade.php +++ b/views/index.blade.php @@ -88,7 +88,10 @@ + @if(config('app.locale') == 'zh_CN') @@ -95,7 +96,19 @@ $(document).on('submit', 'form[pjax-container]', function(event) { $.pjax.submit(event, '#pjax-container') - }) + }); + + $.noty.defaults.layout = 'topRight'; + $.noty.defaults.theme = 'relax'; + + $(document).on('pjax:error', function(event, xhr) { + noty({ + text: "Warning!
    "+xhr.responseText, + type:'warning', + timeout: 3000 + }); + return false; + }); $(document).on("pjax:popstate", function() { From c786e45b97b914d5404c148432112b7ed064e68a Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 20 Oct 2016 16:57:57 +0800 Subject: [PATCH 0036/2161] add Grid::allowCreation() method --- src/Grid.php | 27 ++++++++++++++++++++++++++- views/grid.blade.php | 2 ++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Grid.php b/src/Grid.php index 3b851ba372..a289e8b498 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -101,12 +101,19 @@ class Grid protected $keyName = 'id'; /** - * Allow batch allow. + * Allow batch deletion. * * @var bool */ protected $allowBatchDeletion = true; + /** + * Allow creation. + * + * @var bool + */ + protected $allowCreation = true; + /** * Allow actions. * @@ -354,6 +361,24 @@ public function disableBatchDeletion() $this->allowBatchDeletion = false; } + /** + * Disable creation. + */ + public function disableCreation() + { + $this->allowCreation = false; + } + + /** + * If allow creation. + * + * @return bool + */ + public function allowCreation() + { + return $this->allowCreation; + } + /** * If allow actions. * diff --git a/views/grid.blade.php b/views/grid.blade.php index 50dd40391e..6e3db63636 100644 --- a/views/grid.blade.php +++ b/views/grid.blade.php @@ -4,10 +4,12 @@
    {!! $grid->renderFilter() !!} + @if($grid->allowCreation()) + @endif
    From c31f9e995901a3ab82382c308c5dd1ab39c274ad Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 20 Oct 2016 17:00:24 +0800 Subject: [PATCH 0037/2161] disable batch deletion for auth gird --- src/Controllers/PermissionController.php | 2 ++ src/Controllers/RoleController.php | 2 ++ src/Controllers/UserController.php | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Controllers/PermissionController.php b/src/Controllers/PermissionController.php index 439641b9c1..3c0ce74b72 100644 --- a/src/Controllers/PermissionController.php +++ b/src/Controllers/PermissionController.php @@ -71,6 +71,8 @@ protected function grid() $grid->created_at(trans('admin::lang.created_at')); $grid->updated_at(trans('admin::lang.updated_at')); + + $grid->disableBatchDeletion(); }); } diff --git a/src/Controllers/RoleController.php b/src/Controllers/RoleController.php index c5cb08906e..71acadd7f8 100644 --- a/src/Controllers/RoleController.php +++ b/src/Controllers/RoleController.php @@ -78,6 +78,8 @@ protected function grid() $row->actions('edit'); } }); + + $grid->disableBatchDeletion(); }); } diff --git a/src/Controllers/UserController.php b/src/Controllers/UserController.php index 3886141316..90a1f8bd7d 100644 --- a/src/Controllers/UserController.php +++ b/src/Controllers/UserController.php @@ -86,6 +86,8 @@ protected function grid() $row->actions('edit'); } }); + + $grid->disableBatchDeletion(); }); } From 7f0d96c9a5c6ffad836463805b653d86d5e29c8b Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 21 Oct 2016 13:26:07 +0800 Subject: [PATCH 0038/2161] pjax for search --- views/grid/filter.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/grid/filter.blade.php b/views/grid/filter.blade.php index 66e070531d..d465d2147e 100644 --- a/views/grid/filter.blade.php +++ b/views/grid/filter.blade.php @@ -1,5 +1,5 @@
    -
    +
    @foreach($filters as $filter) From 0fdab49c0436f8f9a4824b9602fa589a292616ad Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 21 Oct 2016 14:29:18 +0800 Subject: [PATCH 0039/2161] fix filter bugs --- src/Form.php | 13 +++++++++++-- src/Grid.php | 2 +- src/Grid/Filter.php | 26 ++++++++++++++++++++++++-- src/Grid/Filter/AbstractFilter.php | 2 +- src/Grid/Filter/Gt.php | 8 +++++--- src/Grid/Filter/Like.php | 8 +++++--- src/Grid/Filter/Lt.php | 8 +++++--- views/grid/filter.blade.php | 2 +- 8 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/Form.php b/src/Form.php index a7deaf2fb1..683447aa3c 100644 --- a/src/Form.php +++ b/src/Form.php @@ -400,11 +400,20 @@ protected function updateRelation($relations) $relation->sync($prepared[$name]); break; case \Illuminate\Database\Eloquent\Relations\HasOne::class: + + $related = $this->model->$name; + + // if related is empty + if (is_null($related)) { + $related = $relation->getRelated(); + $related->{$relation->getForeignKey()} = $this->model->{$this->model->getKeyName()}; + } + foreach ($prepared[$name] as $column => $value) { - $this->model->$name->setAttribute($column, $value); + $related->setAttribute($column, $value); } - $this->model->$name->save(); + $related->save(); break; } } diff --git a/src/Grid.php b/src/Grid.php index a289e8b498..0c64fbb82e 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -330,7 +330,7 @@ public function rows(Closure $callable = null) */ protected function setupFilter() { - $this->filter = new Filter($this->model()); + $this->filter = new Filter($this, $this->model()); } /** diff --git a/src/Grid/Filter.php b/src/Grid/Filter.php index ba878a4dde..75ef55bf9a 100644 --- a/src/Grid/Filter.php +++ b/src/Grid/Filter.php @@ -2,6 +2,7 @@ namespace Encore\Admin\Grid; +use Encore\Admin\Grid; use Encore\Admin\Grid\Filter\AbstractFilter; use Illuminate\Support\Facades\Input; use ReflectionClass; @@ -17,6 +18,11 @@ */ class Filter { + /** + * @var Grid + */ + protected $grid; + /** * @var */ @@ -32,8 +38,16 @@ class Filter */ protected $allows = ['is', 'like', 'gt', 'lt', 'between']; - public function __construct(Model $model) + /** + * Create a new filter instance. + * + * @param Grid $grid + * @param Model $model + */ + public function __construct(Grid $grid, Model $model) { + $this->grid = $grid; + $this->model = $model; $this->is($this->model->eloquent()->getKeyName()); @@ -93,6 +107,14 @@ public function execute() return $this->model->buildData(); } + /** + * @return Grid + */ + public function getGrid() + { + return $this->grid; + } + /** * Get the string contents of the filter view. * @@ -100,7 +122,7 @@ public function execute() */ public function render() { - return view('admin::grid.filter')->with(['filters' => $this->filters()]); + return view('admin::grid.filter')->with(['filters' => $this->filters(), 'grid' => $this->grid]); } /** diff --git a/src/Grid/Filter/AbstractFilter.php b/src/Grid/Filter/AbstractFilter.php index bbff326f5e..847f3d24e6 100644 --- a/src/Grid/Filter/AbstractFilter.php +++ b/src/Grid/Filter/AbstractFilter.php @@ -100,7 +100,7 @@ public function condition($inputs) $value = array_get($inputs, $this->column); if (!isset($value)) { - return; + return null; } $this->value = $value; diff --git a/src/Grid/Filter/Gt.php b/src/Grid/Filter/Gt.php index 4459b3d586..85b0756cc4 100644 --- a/src/Grid/Filter/Gt.php +++ b/src/Grid/Filter/Gt.php @@ -6,11 +6,13 @@ class Gt extends AbstractFilter { public function condition($inputs) { - if (!isset($inputs[$this->column])) { - return; + $value = array_get($inputs, $this->column); + + if (is_null($value)) { + return null; } - $this->value = $inputs[$this->column]; + $this->value = $value; return $this->buildCondition($this->column, '>=', $this->value); } diff --git a/src/Grid/Filter/Like.php b/src/Grid/Filter/Like.php index ba0c9c34a4..6df6f40ffe 100644 --- a/src/Grid/Filter/Like.php +++ b/src/Grid/Filter/Like.php @@ -6,11 +6,13 @@ class Like extends AbstractFilter { public function condition($inputs) { - if (!isset($inputs[$this->column])) { - return; + $value = array_get($inputs, $this->column); + + if (is_null($value)) { + return null; } - $this->value = $inputs[$this->column]; + $this->value = $value; return $this->buildCondition($this->column, 'like', "%{$this->value}%"); } diff --git a/src/Grid/Filter/Lt.php b/src/Grid/Filter/Lt.php index 1c8b4f28cb..1bc7977a3b 100644 --- a/src/Grid/Filter/Lt.php +++ b/src/Grid/Filter/Lt.php @@ -6,11 +6,13 @@ class Lt extends AbstractFilter { public function condition($inputs) { - if (!isset($inputs[$this->column])) { - return; + $value = array_get($inputs, $this->column); + + if (is_null($value)) { + return null; } - $this->value = $inputs[$this->column]; + $this->value = $value; return $this->buildCondition($this->column, '<=', $this->value); } diff --git a/views/grid/filter.blade.php b/views/grid/filter.blade.php index d465d2147e..e319dc1216 100644 --- a/views/grid/filter.blade.php +++ b/views/grid/filter.blade.php @@ -1,5 +1,5 @@
    - +
    @foreach($filters as $filter) From 4fb572ade8f0e824093c6875d3768134b0fd54a4 Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 21 Oct 2016 18:22:15 +0800 Subject: [PATCH 0040/2161] fix delete record issue --- config/admin.php | 4 ++-- src/Form.php | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config/admin.php b/config/admin.php index fbed481a61..2c43c73130 100644 --- a/config/admin.php +++ b/config/admin.php @@ -56,7 +56,7 @@ | | skin-green | |---------------------------------------------------------| */ - 'skin' => 'skin-green', + 'skin' => 'skin-blue', /* |---------------------------------------------------------| @@ -67,7 +67,7 @@ | | sidebar-mini | |---------------------------------------------------------| */ - 'layout' => [], + 'layout' => ['sidebar-mini'], 'version' => '1.0', ]; diff --git a/src/Form.php b/src/Form.php index 683447aa3c..e13148881d 100644 --- a/src/Form.php +++ b/src/Form.php @@ -208,6 +208,9 @@ public function destroy($id) $ids = explode(',', $id); foreach ($ids as $id) { + if (empty($id)) { + continue; + } $this->deleteFilesAndImages($id); $this->model->find($id)->delete(); } From ee312729a7478752caa3b0cde392e1f6ab9ed701 Mon Sep 17 00:00:00 2001 From: Song Date: Fri, 21 Oct 2016 10:26:32 +0000 Subject: [PATCH 0041/2161] Applied fixes from StyleCI --- src/Grid/Filter.php | 2 +- src/Grid/Filter/AbstractFilter.php | 2 +- src/Grid/Filter/Gt.php | 2 +- src/Grid/Filter/Like.php | 2 +- src/Grid/Filter/Lt.php | 2 +- src/Middleware/PjaxMiddleware.php | 1 - 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Grid/Filter.php b/src/Grid/Filter.php index 75ef55bf9a..cb8296956d 100644 --- a/src/Grid/Filter.php +++ b/src/Grid/Filter.php @@ -41,7 +41,7 @@ class Filter /** * Create a new filter instance. * - * @param Grid $grid + * @param Grid $grid * @param Model $model */ public function __construct(Grid $grid, Model $model) diff --git a/src/Grid/Filter/AbstractFilter.php b/src/Grid/Filter/AbstractFilter.php index 847f3d24e6..bbff326f5e 100644 --- a/src/Grid/Filter/AbstractFilter.php +++ b/src/Grid/Filter/AbstractFilter.php @@ -100,7 +100,7 @@ public function condition($inputs) $value = array_get($inputs, $this->column); if (!isset($value)) { - return null; + return; } $this->value = $value; diff --git a/src/Grid/Filter/Gt.php b/src/Grid/Filter/Gt.php index 85b0756cc4..f298d732c4 100644 --- a/src/Grid/Filter/Gt.php +++ b/src/Grid/Filter/Gt.php @@ -9,7 +9,7 @@ public function condition($inputs) $value = array_get($inputs, $this->column); if (is_null($value)) { - return null; + return; } $this->value = $value; diff --git a/src/Grid/Filter/Like.php b/src/Grid/Filter/Like.php index 6df6f40ffe..1331e0f380 100644 --- a/src/Grid/Filter/Like.php +++ b/src/Grid/Filter/Like.php @@ -9,7 +9,7 @@ public function condition($inputs) $value = array_get($inputs, $this->column); if (is_null($value)) { - return null; + return; } $this->value = $value; diff --git a/src/Grid/Filter/Lt.php b/src/Grid/Filter/Lt.php index 1bc7977a3b..e46c4c1b0a 100644 --- a/src/Grid/Filter/Lt.php +++ b/src/Grid/Filter/Lt.php @@ -9,7 +9,7 @@ public function condition($inputs) $value = array_get($inputs, $this->column); if (is_null($value)) { - return null; + return; } $this->value = $value; diff --git a/src/Middleware/PjaxMiddleware.php b/src/Middleware/PjaxMiddleware.php index 1d5ddeee17..363698878c 100644 --- a/src/Middleware/PjaxMiddleware.php +++ b/src/Middleware/PjaxMiddleware.php @@ -43,7 +43,6 @@ public function handle($request, Closure $next) protected function filterResponse(Response $response, $container) { if (!$response->isSuccessful()) { - $crawler = new Crawler($response->getContent()); $response->setContent( From 035d1dd309eb33d52bd662102c50f3c0c8f7aadd Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 21 Oct 2016 18:48:05 +0800 Subject: [PATCH 0042/2161] update README.md [skip ci] --- README.md | 19 +--- docs/zh/menu.md | 53 +-------- docs/zh/model-grid.md | 241 ++++++++++++++++++++++++++++++++++++++++- docs/zh/quick-start.md | 15 +-- 4 files changed, 247 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index d67fcddfa2..c788ceb8d5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ laravel-admin [![Build Status](https://travis-ci.org/z-song/laravel-admin.svg?branch=master)](https://travis-ci.org/z-song/laravel-admin) [![StyleCI](https://styleci.io/repos/48796179/shield)](https://styleci.io/repos/48796179) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/z-song/laravel-admin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/z-song/laravel-admin/?branch=master) [![Packagist](https://img.shields.io/packagist/l/encore/laravel-admin.svg?maxAge=2592000)](https://packagist.org/packages/encore/laravel-admin) [![Total Downloads](https://img.shields.io/packagist/dt/encore/laravel-admin.svg?style=flat-square)](https://packagist.org/packages/encore/laravel-admin) @@ -73,9 +74,6 @@ php artisan admin:install `app/Admin/routes.php`文件用来配置后台路由,详细使用请阅读[路由配置](/docs/zh/router.md)。 -`app/Admin/menu.php`文件用来配置后台左侧菜单栏,详细使用请阅读[菜单栏配置](/docs/zh/menu.md)。 - - `app/Admin/Controllers`目录用来存放后台路由器文件,该目录下的`HomeController.php`文件是后台首页的显示控制器,`ExampleController.php`为实例文件。 快速开始 @@ -217,22 +215,11 @@ $router->resource('users', UserController::class); ### 3.添加左侧菜单栏连接 -打开文件`app/Admin/menu.php`,添加以下数据: - -``` -... -[ - 'title' => '用户列表', - 'url' => 'users', - 'icon' => 'fa-users', -], -... - -``` +打开`http://localhost:8000/admin/auth/menu`,添加对应的menu 然后就能在后台管理页面的左侧边栏看到用户管理页面的链接入口了。 -对于数据表格(model-grid)和数据表单(model-form)的详细使用请查看[model-grid]()和[model-form]()。 +对于数据表格(model-grid)和数据表单(model-form)的详细使用请查看[model-grid](/docs/zh/model-grid.md)和[model-form](/docs/zh/model-form.md)。 其它 ------------ diff --git a/docs/zh/menu.md b/docs/zh/menu.md index 37fceda7a4..03beac6e38 100644 --- a/docs/zh/menu.md +++ b/docs/zh/menu.md @@ -1,54 +1,5 @@ # 左侧边栏配置 -左侧边栏的显示在文件`app/Admin/menu.php`中配置: +打开`http://localhost:8000/admin/auth/menu`设置左侧边栏菜单。 -```php - 'Index', - 'url' => '/', - 'icon' => 'fa-bar-chart', - ], - [ - 'title' => 'Auth', - 'icon' => 'fa-tasks', - 'children' => [ - [ - 'title' => 'Users', - 'url' => 'auth/users', - 'icon' => 'fa-user', - 'roles' => ['developer', 'administrator'], - ], - [ - 'title' => 'Roles', - 'url' => 'auth/roles', - 'icon' => 'fa-user', - 'roles' => ['administrator'], - ], - [ - 'title' => 'Permissions', - 'url' => 'auth/permissions', - 'icon' => 'fa-user', - 'roles' => ['administrator'], - ], - ] - ], -]; - -``` -`title`为显示标题。`url`为点击链接。`icon`为标题前图标,基于[font-awesome](https://fortawesome.github.io/Font-Awesome/icons/)的图标,`roles`可指定显示该菜单项的后台用户角色。如果有子级菜单项,可以在`children`中配置,配置内容和上面一样,侧边栏支持嵌套的多级菜单。 +树状菜单基于[Nestable](https://github.com/dbushell/Nestable)开发,通过拖动来调整菜单的结构和顺序,点击下面的保存按钮来保存设置,然后刷新就能看到左侧边栏的变化了,`路径`天填写访问路径,角色选择可以看见这个菜单项的角色身份。 diff --git a/docs/zh/model-grid.md b/docs/zh/model-grid.md index b03ce0a62c..a0aa12f889 100644 --- a/docs/zh/model-grid.md +++ b/docs/zh/model-grid.md @@ -122,6 +122,11 @@ $grid->email()->value(function ($email) { ``` +#### 禁用创建按钮 +```php +$grid->disableCreation(); +``` + #### 禁用批量删除按钮 ```php $grid->disableBatchDeletion(); @@ -148,6 +153,11 @@ $grid->rows(function($row){ if($row->id % 3) { $row->action('edit'); } + + // 添加自定义操作按钮 + $row->actions()->add(function ($row) { + return ""; + }); //指定列添加自定义操作按钮 if($row->id % 2) { @@ -171,4 +181,233 @@ $grid->filter(function($filter){ // sql: ... WHERE `user.created_at` BETWEEN $start AND $end; $filter->between('created_at', 'Created Time')->datetime(); }); -``` \ No newline at end of file +``` + +## 关联模型 + + +### 一对一 +`users`表和`profiles`表通过`profiles.user_id`字段生成一对一关联 + +```sql + +CREATE TABLE `users` ( +`id` int(10) unsigned NOT NULL AUTO_INCREMENT, +`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, +`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, +`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +CREATE TABLE `profiles` ( +`id` int(10) unsigned NOT NULL AUTO_INCREMENT, +`user_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL, +`age` varchar(255) COLLATE utf8_unicode_ci NOT NULL, +`gender` varchar(255) COLLATE utf8_unicode_ci NOT NULL, +`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +``` + +对应的数据模分别为: + +```php + +class User extends Model +{ + public function profile() + { + $this->hasOne(Profile::class); + } +} + +class Profile extends Model +{ + $this->hasOne(User::class); +} + +``` + +通过下面的代码可以关联在一个grid里面: + +```php +Admin::grid(User::class, function (Grid $grid) { + + $grid->id('ID')->sortable(); + + $grid->name(); + $grid->email(); + + $grid->column('profile.age'); + $grid->column('profile.gender'); + + //or + $grid->profile()->age(); + $grid->profile()->gender(); + + $grid->created_at(); + $grid->updated_at(); +}); + +``` + +### 一对多 + +`posts`表和`comments`表通过`comments.post_id`字段生成一对多关联 + +```sql + +CREATE TABLE `posts` ( +`id` int(10) unsigned NOT NULL AUTO_INCREMENT, +`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, +`content` varchar(255) COLLATE utf8_unicode_ci NOT NULL, +`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +CREATE TABLE `comments` ( +`id` int(10) unsigned NOT NULL AUTO_INCREMENT, +`post_id` int(10) unsigned NOT NULL, +`content` varchar(255) COLLATE utf8_unicode_ci NOT NULL, +`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +``` + +对应的数据模分别为: + +```php + +class Post extends Model +{ + public function comments() + { + return $this->hasMany(Comment::class); + } +} + +class Comment extends Model +{ + public function post() + { + return $this->belongsTo(Post::class); + } +} + +``` + +通过下面的代码可以让两个模型在grid里面互相关联: + +```php + +return Admin::grid(Post::class, function (Grid $grid) { + $grid->id('id')->sortable(); + $grid->title(); + $grid->content(); + + $grid->comments('评论数')->value(function ($comments) { + $count = count($comments); + return "{$count}"; + }); + + $grid->created_at(); + $grid->updated_at(); +}); + + +return Admin::grid(Comment::class, function (Grid $grid) { + $grid->id('id'); + $grid->post()->title(); + $grid->content(); + + $grid->created_at()->sortable(); + $grid->updated_at(); +}); + +``` + +### 多对多 + +`users`和`roles`表通过中间表`role_users`产生多对多关系 + +```sql + +CREATE TABLE `users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(190) COLLATE utf8_unicode_ci NOT NULL, + `password` varchar(60) COLLATE utf8_unicode_ci NOT NULL, + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `users_username_unique` (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci + +CREATE TABLE `roles` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL, + `slug` varchar(50) COLLATE utf8_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `roles_name_unique` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci + +CREATE TABLE `role_users` ( + `role_id` int(11) NOT NULL, + `user_id` int(11) NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + KEY `role_users_role_id_user_id_index` (`role_id`,`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci +``` + +对应的数据模分别为: + +```php + +class User extends Model +{ + public function roles() + { + return $this->belongsToMany(Role::class); + } +} + +class Role extends Model +{ + public function users() + { + return $this->belongsToMany(User::class); + } +} + +``` + +通过下面的代码可以让两个模型在grid里面互相关联: + + +```php +return Admin::grid(User::class, function (Grid $grid) { + $grid->id('ID')->sortable(); + $grid->username(); + $grid->name(); + + $grid->roles()->value(function ($roles) { + + $roles = array_map(function ($role) { + return "{$role['name']}"; + }, $roles); + + return join(' ', $roles); + }); + + $grid->created_at(); + $grid->updated_at(); +}); + +``` diff --git a/docs/zh/quick-start.md b/docs/zh/quick-start.md index b943d2b47c..bcd114ffa4 100644 --- a/docs/zh/quick-start.md +++ b/docs/zh/quick-start.md @@ -136,19 +136,8 @@ $router->resource('users', UserController::class); ### 3.添加左侧菜单栏连接 -打开文件`app/Admin/menu.php`,添加以下数据: - -``` -... -[ - 'title' => '用户列表', - 'url' => 'users', - 'icon' => 'fa-users', -], -... - -``` +打开http://localhost:8000/admin/auth/menu ,添加对应的menu 然后就能在后台管理页面的左侧边栏看到用户管理页面的链接入口了。 -对于数据表格(model-grid)和数据表单(model-form)的详细使用请查看[model-grid]()和[model-form]()。 \ No newline at end of file +对于数据表格(model-grid)和数据表单(model-form)的详细使用请查看[model-grid]()和[model-form]()。 From 704a155e12a34b20161833f50eff568975fe8a47 Mon Sep 17 00:00:00 2001 From: z-song Date: Sat, 22 Oct 2016 10:23:04 +0800 Subject: [PATCH 0043/2161] fix form issue [skip ci] --- src/Form/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Form/Builder.php b/src/Form/Builder.php index 7886de1647..163984389e 100644 --- a/src/Form/Builder.php +++ b/src/Form/Builder.php @@ -158,7 +158,7 @@ public function open($options = []) $attributes['enctype'] = 'multipart/form-data'; } - $html = ''; + $html = []; foreach ($attributes as $name => $value) { $html[] = "$name=\"$value\""; } From 89cb18d27f5ac2fa8a09333453227699ea813e70 Mon Sep 17 00:00:00 2001 From: z-song Date: Sun, 23 Oct 2016 00:36:54 +0800 Subject: [PATCH 0044/2161] fix issues --- README.md | 4 +-- docs/zh/README.md | 4 +-- docs/zh/quick-start.md | 4 +-- src/Commands/stubs/ExampleController.stub | 4 +-- src/Commands/stubs/blank.stub | 4 +-- src/Commands/stubs/controller.stub | 4 +-- src/Controllers/ModelForm.php | 28 +++++++++++++++ src/Controllers/PermissionController.php | 2 +- src/Controllers/RoleController.php | 2 +- src/Controllers/UserController.php | 2 +- src/Form.php | 42 +++++++++++++++++++++-- src/Form/Field/Decimal.php | 17 +++++++++ src/Form/Field/Month.php | 13 +++++++ src/Form/Field/MultipleSelect.php | 20 +++++++---- src/Form/Field/Year.php | 13 +++++++ src/Grid.php | 10 +++--- views/form/decimal.blade.php | 16 +++++++++ 17 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 src/Controllers/ModelForm.php create mode 100644 src/Form/Field/Decimal.php create mode 100644 src/Form/Field/Month.php create mode 100644 src/Form/Field/Year.php create mode 100644 views/form/decimal.blade.php diff --git a/README.md b/README.md index c788ceb8d5..ce372e6df5 100644 --- a/README.md +++ b/README.md @@ -109,11 +109,11 @@ use Encore\Admin\Grid; use Encore\Admin\Facades\Admin; use Encore\Admin\Layout\Content; use App\Http\Controllers\Controller; -use Encore\Admin\Controllers\AdminController; +use Encore\Admin\Controllers\ModelForm; class UserController extends Controller { - use AdminController; + use ModelForm; /** * Index interface. diff --git a/docs/zh/README.md b/docs/zh/README.md index d67fcddfa2..1ec47fc7c6 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -111,11 +111,11 @@ use Encore\Admin\Grid; use Encore\Admin\Facades\Admin; use Encore\Admin\Layout\Content; use App\Http\Controllers\Controller; -use Encore\Admin\Controllers\AdminController; +use Encore\Admin\Controllers\ModelForm; class UserController extends Controller { - use AdminController; + use ModelForm; /** * Index interface. diff --git a/docs/zh/quick-start.md b/docs/zh/quick-start.md index bcd114ffa4..caf519d0a4 100644 --- a/docs/zh/quick-start.md +++ b/docs/zh/quick-start.md @@ -30,11 +30,11 @@ use Encore\Admin\Grid; use Encore\Admin\Facades\Admin; use Encore\Admin\Layout\Content; use App\Http\Controllers\Controller; -use Encore\Admin\Controllers\AdminController; +use Encore\Admin\Controllers\ModelForm; class UserController extends Controller { - use AdminController; + use ModelForm; /** * Index interface. diff --git a/src/Commands/stubs/ExampleController.stub b/src/Commands/stubs/ExampleController.stub index 23450e60cc..b84ba291ad 100644 --- a/src/Commands/stubs/ExampleController.stub +++ b/src/Commands/stubs/ExampleController.stub @@ -7,11 +7,11 @@ use Encore\Admin\Grid; use Encore\Admin\Facades\Admin; use Encore\Admin\Layout\Content; use App\Http\Controllers\Controller; -use Encore\Admin\Controllers\AdminController; +use Encore\Admin\Controllers\ModelForm; class ExampleController extends Controller { - use AdminController; + use ModelForm; /** * Index interface. diff --git a/src/Commands/stubs/blank.stub b/src/Commands/stubs/blank.stub index df4ac60d3c..53c923974e 100644 --- a/src/Commands/stubs/blank.stub +++ b/src/Commands/stubs/blank.stub @@ -2,10 +2,10 @@ namespace DummyNamespace; -use Encore\Admin\AdminController; use App\Http\Controllers\Controller; +use Encore\Admin\Controllers\ModelForm; class DummyClass extends Controller { - use AdminController; + use ModelForm; } diff --git a/src/Commands/stubs/controller.stub b/src/Commands/stubs/controller.stub index 01a98def6b..08c331d386 100644 --- a/src/Commands/stubs/controller.stub +++ b/src/Commands/stubs/controller.stub @@ -9,11 +9,11 @@ use Encore\Admin\Grid; use Encore\Admin\Facades\Admin; use Encore\Admin\Layout\Content; use App\Http\Controllers\Controller; -use Encore\Admin\Controllers\AdminController; +use Encore\Admin\Controllers\ModelForm; class DummyClass extends Controller { - use AdminController; + use ModelForm; /** * Index interface. diff --git a/src/Controllers/ModelForm.php b/src/Controllers/ModelForm.php new file mode 100644 index 0000000000..b3d0209fc7 --- /dev/null +++ b/src/Controllers/ModelForm.php @@ -0,0 +1,28 @@ +edit($id); + } + + public function update($id) + { + return $this->form()->update($id); + } + + public function destroy($id) + { + if ($this->form()->destroy($id)) { + return response()->json(['msg' => 'delete success!']); + } + } + + public function store() + { + return $this->form()->store(); + } +} diff --git a/src/Controllers/PermissionController.php b/src/Controllers/PermissionController.php index 3c0ce74b72..5520da04c9 100644 --- a/src/Controllers/PermissionController.php +++ b/src/Controllers/PermissionController.php @@ -11,7 +11,7 @@ class PermissionController extends Controller { - use AdminController; + use ModelForm; /** * Index interface. diff --git a/src/Controllers/RoleController.php b/src/Controllers/RoleController.php index 71acadd7f8..711e77729a 100644 --- a/src/Controllers/RoleController.php +++ b/src/Controllers/RoleController.php @@ -12,7 +12,7 @@ class RoleController extends Controller { - use AdminController; + use ModelForm; /** * Index interface. diff --git a/src/Controllers/UserController.php b/src/Controllers/UserController.php index 90a1f8bd7d..402c9eebcb 100644 --- a/src/Controllers/UserController.php +++ b/src/Controllers/UserController.php @@ -12,7 +12,7 @@ class UserController extends Controller { - use AdminController; + use ModelForm; /** * Index interface. diff --git a/src/Form.php b/src/Form.php index e13148881d..f4c6f11698 100644 --- a/src/Form.php +++ b/src/Form.php @@ -39,6 +39,8 @@ * @method Field\Date date($column, $label = '') * @method Field\Datetime datetime($column, $label = '') * @method Field\Time time($column, $label = '') + * @method Field\Year year($column, $label = '') + * @method Field\Month month($column, $label = '') * @method Field\DateRange dateRange($start, $end, $label = '') * @method Field\DateTimeRange dateTimeRange($start, $end, $label = '') * @method Field\TimeRange timeRange($start, $end, $label = '') @@ -51,6 +53,7 @@ * @method Field\Rate rate($column, $label = '') * @method Field\Divide divide() * @method Field\Password password($column, $label = '') + * @method Field\Decimal decimal($column, $label = '') */ class Form { @@ -256,6 +259,9 @@ public function store() $inserts = $this->prepareInsert($this->updates); foreach ($inserts as $column => $value) { + if (is_array($value)) { + $value = join(',', $value); + } $this->model->setAttribute($column, $value); } @@ -283,11 +289,36 @@ protected function prepare($data = [], Closure $callback = null) $callback($this); } - $this->updates = array_filter($this->inputs, function ($val) { - return is_string($val) or ($val instanceof UploadedFile); + $this->relations = $this->getRelationInputs($data); + + $updates = array_except($this->inputs, array_keys($this->relations)); + + $this->updates = array_filter($updates, function ($val) { + return !is_null($val); }); + } + + /** + * Get inputs for relations. + * + * @param array $inputs + * @return array + */ + protected function getRelationInputs($inputs = []) + { + $relations = []; + + foreach ($inputs as $column => $value) { + if (method_exists($this->model, $column)) { + $relation = call_user_func([$this->model, $column]); - $this->relations = array_filter($this->inputs, 'is_array'); + if ($relation instanceof Relation) { + $relations[$column] = $value; + } + } + } + + return $relations; } /** @@ -362,6 +393,11 @@ public function update($id) $updates = $this->prepareUpdate($this->updates); foreach ($updates as $column => $value) { + + if (is_array($value)) { + $value = join(',', $value); + } + $this->model->setAttribute($column, $value); } diff --git a/src/Form/Field/Decimal.php b/src/Form/Field/Decimal.php new file mode 100644 index 0000000000..add603e196 --- /dev/null +++ b/src/Form/Field/Decimal.php @@ -0,0 +1,17 @@ +script = "$('#{$this->id}').inputmask('decimal', { + rightAlign: true + });"; + + return parent::render(); + } +} \ No newline at end of file diff --git a/src/Form/Field/Month.php b/src/Form/Field/Month.php new file mode 100644 index 0000000000..3bfddff8d6 --- /dev/null +++ b/src/Form/Field/Month.php @@ -0,0 +1,13 @@ +column); - if (!is_array($relations)) { - return; + if (is_string($relations)) { + $this->value = explode(',', $relations); } - foreach ($relations as $relation) { - $this->value[] = array_pop($relation['pivot']); + if (is_array($relations)) { + foreach ($relations as $relation) { + $this->value[] = array_pop($relation['pivot']); + } } } @@ -21,8 +23,14 @@ public function setOriginal($data) { $relations = array_get($data, $this->column); - foreach ($relations as $relation) { - $this->original[] = array_pop($relation['pivot']); + if (is_string($relations)) { + $this->original = explode(',', $relations); + } + + if (is_array($relations)) { + foreach ($relations as $relation) { + $this->original[] = array_pop($relation['pivot']); + } } } diff --git a/src/Form/Field/Year.php b/src/Form/Field/Year.php new file mode 100644 index 0000000000..64877157e6 --- /dev/null +++ b/src/Form/Field/Year.php @@ -0,0 +1,13 @@ +model()->eloquent() instanceof MongodbModel) { - $label = isset($arguments[0]) ? $arguments[0] : ucfirst($method); + $label = isset($arguments[0]) ? $arguments[0] : ucfirst($method); + if ($this->model()->eloquent() instanceof MongodbModel) { return $this->addColumn($method, $label); } $connection = $this->model()->eloquent()->getConnectionName(); if (Schema::connection($connection)->hasColumn($this->model()->getTable(), $method)) { - $label = isset($arguments[0]) ? $arguments[0] : ucfirst($method); - return $this->addColumn($method, $label); } @@ -518,13 +516,13 @@ public function __call($method, $arguments) if ($relation instanceof HasOne || $relation instanceof BelongsTo) { $this->model()->with($method); - return $this->addColumn()->setRelation($method); + return $this->addColumn($method, $label)->setRelation($method); } if ($relation instanceof HasMany || $relation instanceof BelongsToMany || $relation instanceof MorphToMany) { $this->model()->with($method); - return $this->addColumn($method); + return $this->addColumn($method, $label); } } diff --git a/views/form/decimal.blade.php b/views/form/decimal.blade.php new file mode 100644 index 0000000000..729cb98844 --- /dev/null +++ b/views/form/decimal.blade.php @@ -0,0 +1,16 @@ +
    + + + +
    + + @include('admin::form.error') + +
    +
    + +
    + +
    +
    +
    \ No newline at end of file From 9fe57313ee14355d128b9efa7cd519e5eace9e40 Mon Sep 17 00:00:00 2001 From: Song Date: Sat, 22 Oct 2016 16:38:03 +0000 Subject: [PATCH 0045/2161] Applied fixes from StyleCI --- src/Form.php | 7 +++---- src/Form/Field/Decimal.php | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Form.php b/src/Form.php index f4c6f11698..d1cedf6838 100644 --- a/src/Form.php +++ b/src/Form.php @@ -13,7 +13,6 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Validator; -use Symfony\Component\HttpFoundation\File\UploadedFile; /** * Class Form. @@ -260,7 +259,7 @@ public function store() foreach ($inserts as $column => $value) { if (is_array($value)) { - $value = join(',', $value); + $value = implode(',', $value); } $this->model->setAttribute($column, $value); } @@ -302,6 +301,7 @@ protected function prepare($data = [], Closure $callback = null) * Get inputs for relations. * * @param array $inputs + * * @return array */ protected function getRelationInputs($inputs = []) @@ -393,9 +393,8 @@ public function update($id) $updates = $this->prepareUpdate($this->updates); foreach ($updates as $column => $value) { - if (is_array($value)) { - $value = join(',', $value); + $value = implode(',', $value); } $this->model->setAttribute($column, $value); diff --git a/src/Form/Field/Decimal.php b/src/Form/Field/Decimal.php index add603e196..43f65834c8 100644 --- a/src/Form/Field/Decimal.php +++ b/src/Form/Field/Decimal.php @@ -14,4 +14,4 @@ public function render() return parent::render(); } -} \ No newline at end of file +} From 13cd4a2915f16f3dbe8135f17933634840ebaff1 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 24 Oct 2016 10:30:37 +0800 Subject: [PATCH 0046/2161] update docs [skip ci] --- README.md | 221 +++++------------ docs/en/README.md | 381 +++++++++--------------------- docs/en/form-upload.md | 100 ++++++++ docs/en/layout.md | 28 +++ docs/en/menu.md | 5 + docs/en/model-form.md | 319 +++++++++++++++++++++++++ docs/en/model-grid.md | 174 ++++++++++++++ docs/en/quick-start.md | 44 ++++ docs/en/widgets/box.md | 38 +++ docs/en/widgets/carousel.md | 30 +++ docs/en/widgets/collapse.md | 16 ++ docs/en/widgets/form.md | 240 +++++++++++++++++++ docs/en/widgets/info-box.md | 14 ++ docs/en/widgets/tab.md | 18 ++ docs/en/widgets/table.md | 36 +++ docs/zh/README.md | 155 ++---------- docs/zh/{ => widgets}/box.md | 0 docs/zh/{ => widgets}/carousel.md | 0 docs/zh/{ => widgets}/collapse.md | 0 docs/zh/{ => widgets}/form.md | 0 docs/zh/{ => widgets}/info-box.md | 0 docs/zh/{ => widgets}/tab.md | 0 docs/zh/{ => widgets}/table.md | 0 23 files changed, 1252 insertions(+), 567 deletions(-) create mode 100644 docs/en/form-upload.md create mode 100644 docs/en/layout.md create mode 100644 docs/en/menu.md create mode 100644 docs/en/model-form.md create mode 100644 docs/en/model-grid.md create mode 100644 docs/en/quick-start.md create mode 100644 docs/en/widgets/box.md create mode 100644 docs/en/widgets/carousel.md create mode 100644 docs/en/widgets/collapse.md create mode 100644 docs/en/widgets/form.md create mode 100644 docs/en/widgets/info-box.md create mode 100644 docs/en/widgets/tab.md create mode 100644 docs/en/widgets/table.md rename docs/zh/{ => widgets}/box.md (100%) rename docs/zh/{ => widgets}/carousel.md (100%) rename docs/zh/{ => widgets}/collapse.md (100%) rename docs/zh/{ => widgets}/form.md (100%) rename docs/zh/{ => widgets}/info-box.md (100%) rename docs/zh/{ => widgets}/tab.md (100%) rename docs/zh/{ => widgets}/table.md (100%) diff --git a/README.md b/README.md index ce372e6df5..5199c663aa 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,20 @@ laravel-admin [![Packagist](https://img.shields.io/packagist/l/encore/laravel-admin.svg?maxAge=2592000)](https://packagist.org/packages/encore/laravel-admin) [![Total Downloads](https://img.shields.io/packagist/dt/encore/laravel-admin.svg?style=flat-square)](https://packagist.org/packages/encore/laravel-admin) -`laravel-admin` 是一个可以快速帮你构建后台管理的工具,它提供的页面组件和表单元素等功能,能帮助你使用很少的代码就实现功能完善的后台管理功能。 +`laravel-admin` is administrative interface builder for laravel which can help you build CRUD backends just with few lines of code. -[Demo](http://120.26.143.106/admin) 账号/密码:admin/admin +[Demo](http://120.26.143.106/admin) use `username/password:admin/admin` -截图 ------------- +Inspired by [SleepingOwlAdmin](https://github.com/sleeping-owl/admin) and [rapyd-laravel](https://github.com/zofe/rapyd-laravel). -![grid](https://cloud.githubusercontent.com/assets/1479100/16609399/894e0832-4386-11e6-8709-1cc7ce429e7c.png) +[中文文档](/docs/zh/README.md) + +Screenshots +------------ -![form](https://cloud.githubusercontent.com/assets/1479100/12708198/fc6725a8-c8d7-11e5-876f-5c4f00ded0ff.png) +![laravel-admin](https://cloud.githubusercontent.com/assets/1479100/19625297/3b3deb64-9947-11e6-807c-cffa999004be.jpg) -安装 +Installation ------------ ``` @@ -32,54 +34,62 @@ Laravel 5.1 composer require encore/laravel-admin "1.1.x-dev" ``` -在`config/app.php`加入`ServiceProvider`: +In`config/app.php`add`ServiceProvider`: ``` Encore\Admin\Providers\AdminServiceProvider::class ``` -然后运行下面的命令完成安装: +Then run these commands to finnish install: ``` php artisan vendor:publish --tag=laravel-admin php artisan admin:install ``` -启动服务后,在浏览器打开 `http://localhost/admin/` ,使用用户名 `admin` 和密码 `admin`登陆. +open `http://localhost/admin/` in browser,use username `admin` and password `admin` to login. -使用文档 +Documentation ------------ -- [快速开始](/docs/zh/quick-start.md) -- [路由配置](/docs/zh/router.md) -- [菜单配置](/docs/zh/menu.md) -- [显示布局](/docs/zh/layout.md) -- [数据模型表格](/docs/zh/model-grid.md) -- [数据模型表单](/docs/zh/model-form.md) - - [图片/文件上传](/docs/zh/form-upload.md) -- [组件](/docs/zh/table.md) - - [表格](/docs/zh/table.md) - - [表单](/docs/zh/form.md) - - [盒子](/docs/zh/box.md) - - [信息盒子](/docs/zh/info-box.md) - - [选项卡](/docs/zh/tab.md) - - [滑动相册](/docs/zh/carousel.md) - - [折叠容器](/docs/zh/collapse.md) - - 数据图表 TODO -- [权限控制](/docs/zh/permission.md) - -目录结构 +- [quick start](/docs/en/quick-start.md) +- [router](/docs/en/router.md) +- [menu](/docs/en/menu.md) +- [layout](/docs/en/layout.md) +- [model-grid](/docs/en/model-grid.md) +- [model-form](/docs/en/model-form.md) +- [widgets](/docs/en/widgets/table.md) + - [table](/docs/en/widgets/table.md) + - [form](/docs/en/widgets/form.md) + - [box](/docs/en/widgets/box.md) + - [info-box](/docs/en/widgets/info-box.md) + - [tab](/docs/en/widgets/box.md) + - [carousel](/docs/en/widgets/carousel.md) + - [collapse](/docs/en/widgets/collapse.md) + - charts TODO +- [RBAC](/docs/en/permission.md) + +Directory structure ------------ -安装完成之后,后台的安装目录为`app/Admin`,之后大部分的后台开发编码工作都是在这个目录下进行。 +After install,you can find directory`app/Admin`,and then most of our develop work is under this directory. + +``` + +app/Admin +├── Controllers +│   ├── ExampleController.php +│   └── HomeController.php +└── routes.php +``` -`app/Admin/routes.php`文件用来配置后台路由,详细使用请阅读[路由配置](/docs/zh/router.md)。 +`app/Admin/routes.php` is used to define routes,for more detail please read [routes](/docs/zh/router.md). -`app/Admin/Controllers`目录用来存放后台路由器文件,该目录下的`HomeController.php`文件是后台首页的显示控制器,`ExampleController.php`为实例文件。 +The `app/Admin/Controllers` directory is used to store all the controllers, The `HomeController.php` file under this directory is used to handle home request of admin,The `ExampleController.php` file is a controller example. -快速开始 +Quick start ------------ -用`Laravel`自带的`users`表举例,表结构为: +We use `users` table come with `Laravel` for example,the structure of table is: ```sql CREATE TABLE `users` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, @@ -93,137 +103,37 @@ CREATE TABLE `users` ( UNIQUE KEY `users_email_unique` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ``` -对应的数据模型为文件 `App\User.php` +And the model for this table is `App\User.php` -`laravel-admin`可以通过使用以下几步来快速生成`users`表的`CURD`操作页面: +You can follow these steps to setup `CURD` interfaces of table `users`: -### 1.添加路由器 -```php -header('header'); - $content->description('description'); - - $content->body($this->grid()); - }); - } - - /** - * Edit interface. - * - * @param $id - * @return Content - */ - public function edit($id) - { - return Admin::content(function (Content $content) use ($id) { - - $content->header('header'); - $content->description('description'); - - $content->body($this->form()->edit($id)); - }); - } - - /** - * Create interface. - * - * @return Content - */ - public function create() - { - return Admin::content(function (Content $content) { - - $content->header('header'); - $content->description('description'); - - $content->body($this->form()); - }); - } - - /** - * Make a grid builder. - * - * @return Grid - */ - protected function grid() - { - return Admin::grid(User::class, function (Grid $grid) { - - $grid->id('ID')->sortable(); - - $grid->name('用户名'); - $grid->email('邮箱'); - - $grid->created_at(); - $grid->updated_at(); - }); - } - - /** - * Make a form builder. - * - * @return Form - */ - protected function form() - { - return Admin::form(User::class, function (Form $form) { - - $form->display('id', 'ID'); - - $form->text('name', '用户名'); - $form->email('email', '用户邮箱'); - $form->password('password', '密码'); - - $form->dateTime('created_at', 'Created At'); - $form->dateTime('updated_at', 'Updated At'); - }); - } -} +#### 1.add controller + +Use the following command to create a controller for `App\User` model +```php +php artisan admin:make UserController --model=App\\User ``` +The above command will create the controller in `app/Admin/Controllers/UserController.php`. -### 2.添加路由配置 +#### 2.add route -在`laravel-admin`的路由配置文件`app/Admin/routes.php`里添加一行: +Add a route in `app/Admin/routes.php`: ``` $router->resource('users', UserController::class); ``` -### 3.添加左侧菜单栏连接 +#### 3.add left menu item -打开`http://localhost:8000/admin/auth/menu`,添加对应的menu +Open `http://localhost:8000/admin/auth/menu`, add menu link and refresh the page, then you can find a link item in left menu bar. -然后就能在后台管理页面的左侧边栏看到用户管理页面的链接入口了。 +#### 3.build grid and form -对于数据表格(model-grid)和数据表单(model-form)的详细使用请查看[model-grid](/docs/zh/model-grid.md)和[model-form](/docs/zh/model-form.md)。 +The rest needs to be done is open `app/Admin/Contollers/UserController.php`, find `form()` and `grid()` method and write few lines of code with `model-grid` and `model-form`,for more detail, please read [model-grid](/docs/en/model-grid.md) and [model-form](/docs/en/model-form.md). -其它 +Other ------------ -`laravel-admin` 基于以下组件或者服务: +`laravel-admin` based on thses plugins or services: + [Laravel](https://laravel.com/) + [AdminLTE](https://almsaeedstudio.com/) @@ -236,13 +146,8 @@ $router->resource('users', UserController::class); + [Tencent map](http://lbs.qq.com/) + [bootstrap-fileinput](https://github.com/kartik-v/bootstrap-fileinput) + [jquery-pjax](https://github.com/defunkt/jquery-pjax) - -Inspired by [SleepingOwlAdmin](https://github.com/sleeping-owl/admin) and [rapyd-laravel](https://github.com/zofe/rapyd-laravel). - -交流 ------------- -QQ群:278455482 - ++ [Nestable](http://dbushell.github.io/Nestable/) ++ [noty](http://ned.im/noty/) License ------------ diff --git a/docs/en/README.md b/docs/en/README.md index 50331b92ef..5199c663aa 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -1,323 +1,154 @@ -# laravel-admin +laravel-admin +===== [![Build Status](https://travis-ci.org/z-song/laravel-admin.svg?branch=master)](https://travis-ci.org/z-song/laravel-admin) [![StyleCI](https://styleci.io/repos/48796179/shield)](https://styleci.io/repos/48796179) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/z-song/laravel-admin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/z-song/laravel-admin/?branch=master) +[![Packagist](https://img.shields.io/packagist/l/encore/laravel-admin.svg?maxAge=2592000)](https://packagist.org/packages/encore/laravel-admin) +[![Total Downloads](https://img.shields.io/packagist/dt/encore/laravel-admin.svg?style=flat-square)](https://packagist.org/packages/encore/laravel-admin) `laravel-admin` is administrative interface builder for laravel which can help you build CRUD backends just with few lines of code. -[中文文档](/docs/zh.md) - -`laravel-admin` based on these packages and services: - -+ [Laravel](https://laravel.com/) -+ [AdminLTE](https://almsaeedstudio.com/) -+ [Bootstrap Markdown](http://toopay.github.io/bootstrap-markdown/) -+ [Datetimepicker](http://eonasdan.github.io/bootstrap-datetimepicker/) -+ [CodeMirror](https://codemirror.net/) -+ [font-awesome](http://fontawesome.io) -+ [moment](http://momentjs.com/) -+ [Google map](https://www.google.com/maps) -+ [Tencent map](http://lbs.qq.com/) -+ [bootstrap-fileinput](https://github.com/kartik-v/bootstrap-fileinput) -+ [jquery-pjax](https://github.com/defunkt/jquery-pjax) +[Demo](http://120.26.143.106/admin) use `username/password:admin/admin` Inspired by [SleepingOwlAdmin](https://github.com/sleeping-owl/admin) and [rapyd-laravel](https://github.com/zofe/rapyd-laravel). -#Screenshot +[中文文档](/docs/zh/README.md) -![grid](https://cloud.githubusercontent.com/assets/1479100/12708148/6c4aa9fe-c8d7-11e5-94e4-c8105375a564.png) +Screenshots +------------ -![form](https://cloud.githubusercontent.com/assets/1479100/12708198/fc6725a8-c8d7-11e5-876f-5c4f00ded0ff.png) +![laravel-admin](https://cloud.githubusercontent.com/assets/1479100/19625297/3b3deb64-9947-11e6-807c-cffa999004be.jpg) -# Installation +Installation +------------ ``` +Laravel 5.2 composer require encore/laravel-admin "dev-master" + +Laravel 5.3 +composer require encore/laravel-admin "1.3.x-dev" + +Laravel 5.1 +composer require encore/laravel-admin "1.1.x-dev" ``` -Add `ServiceProvider` to `config/app.php`: +In`config/app.php`add`ServiceProvider`: ``` Encore\Admin\Providers\AdminServiceProvider::class ``` -Then run these commands to finish installation: +Then run these commands to finnish install: ``` -php artisan vendor:publish +php artisan vendor:publish --tag=laravel-admin php artisan admin:install ``` -Open `http://localhost/admin/` in your browser,and use username `admin` and password `admin` to login. - -#Usage - -The install path defaults to `app/Admin`. - -Use `routes.php` under `app/Admin` to manage admin routes. +open `http://localhost/admin/` in browser,use username `admin` and password `admin` to login. + +Documentation +------------ + +- [quick start](/docs/en/quick-start.md) +- [router](/docs/en/router.md) +- [menu](/docs/en/menu.md) +- [layout](/docs/en/layout.md) +- [model-grid](/docs/en/model-grid.md) +- [model-form](/docs/en/model-form.md) +- [widgets](/docs/en/widgets/table.md) + - [table](/docs/en/widgets/table.md) + - [form](/docs/en/widgets/form.md) + - [box](/docs/en/widgets/box.md) + - [info-box](/docs/en/widgets/info-box.md) + - [tab](/docs/en/widgets/box.md) + - [carousel](/docs/en/widgets/carousel.md) + - [collapse](/docs/en/widgets/collapse.md) + - charts TODO +- [RBAC](/docs/en/permission.md) + +Directory structure +------------ +After install,you can find directory`app/Admin`,and then most of our develop work is under this directory. -```php -get('/', function() { - return view('admin::index'); -}); - -$router->resources([ - 'administrators' => AdministratorController::class -]); ``` -Use `menu.php` to configure the menus in left sidebar: -```php - 'Index', - 'url' => '/', - 'icon' => 'fa-bar-chart' - ], - [ - 'title' => 'Administrators', - 'url' => '/administrators', - 'icon' => 'fa-tasks', - 'roles' => ['administrator'], // Roles can see this item. - ], - [ - 'title' => 'Multilevel', - 'icon' => 'fa-circle-o', - 'children' => [ - [ - 'title' => 'Level One', - 'url' => '/', - 'icon' => 'fa-circle-o' - ], - [ - 'title' => 'Level One', - 'icon' => 'fa-circle-o', - 'children' => [ - [ - 'title' => 'Level Two', - 'url' => '/', - 'icon' => 'fa-circle-o' - ] - ] - ], - ] - ], -]; +app/Admin +├── Controllers +│   ├── ExampleController.php +│   └── HomeController.php +└── routes.php ``` -`controllers/` is controller directory where to store admin controllers. +`app/Admin/routes.php` is used to define routes,for more detail please read [routes](/docs/zh/router.md). + +The `app/Admin/Controllers` directory is used to store all the controllers, The `HomeController.php` file under this directory is used to handle home request of admin,The `ExampleController.php` file is a controller example. + +Quick start +------------ + +We use `users` table come with `Laravel` for example,the structure of table is: +```sql +CREATE TABLE `users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `password` varchar(60) COLLATE utf8_unicode_ci NOT NULL, + `remember_token` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY (`id`), + UNIQUE KEY `users_email_unique` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci +``` +And the model for this table is `App\User.php` -Language strings are stored in files within the `lang/` directory, and it will use `app.locale` configuration. +You can follow these steps to setup `CURD` interfaces of table `users`: -###Create controllers +#### 1.add controller -If you want to create a resource controller with `User` model,you can use this command: -``` -php artisan admin:make UserController --model=\\App\\User -``` +Use the following command to create a controller for `App\User` model -It will create `UserController.php` under `app/Admin/controllers`,add a resource in `routes.php`: ```php -$router->resources([ - 'users' => UserController::class, //add this line - 'administrators' => AdministratorController::class -]); +php artisan admin:make UserController --model=App\\User ``` +The above command will create the controller in `app/Admin/Controllers/UserController.php`. -At last add access in `menu.php`: +#### 2.add route -```php - [ - 'title' => 'Users', - 'url' => '/users', - 'icon' => 'fa-user' - ], +Add a route in `app/Admin/routes.php`: +``` +$router->resource('users', UserController::class); ``` -So you can see the `users` resource link in the left sidebar menu. - -###Admin\Grid - -`Admin\Grid` is a data grid builder based on `bootstrap table`,in the controller: - -```php -return Admin::grid(User::class, function(Grid $grid){ - - $grid->id('ID')->sortable(); - - //Use dynamic method. - $grid->name(); - //or use column() method: $grid->column('name'); - - //Add mulitiple columns. - $grid->columns('email', 'username' ...); - - //Use related column (hasOne relation). - $grid->column('profile.mobile', 'Mobile'); - //or use $grid->profile()->mobile('Mobile'); - - //Use a callback function to display column value. - $grid->column('profile.mobile', 'Mobile')->value(function($mobile) { - return "+86 $mobile"; - }); - - //Use sortable() method to make the column sortable. - $grid->column('profile.age', 'Age')->sortable(); - - // - $grid->column('progress')->progressBar(['danger', 'striped'], 'xs'); - - //Wrapper value with a badge. - $grid->created_at()->badge('danger'); - - //Wrapper value with a label. - $grid->updated_at()->label('success'); - - //Set query conditions: SELECT * FROM `user` WHERE id > 20 ORDER BY updated_at DESC; - $grid->model()->where('id', '>', '20')->orderBy('updated_at', 'desc'); - - //Set 15 items per-page. - $grid->paginate(15); - - //Set actions (edit,delete). - $grid->actions('edit|delete'); - - //Add row callback function. - $grid->rows(function($row){ - if($row->id <= 10) { - $row->style('color:red'); - } - - //Disable delete action for specify row. - if($row->id % 3) { - $row->action('edit'); - } - - //Add custom action for specify row. - if($row->id % 2) { - $row->actions()->add(function ($row) { - return "btn"; - }); - } - }); - - //Disable batch deletion. - $grid->disableBatchDeletion(); - - //Disable all actions. - $grid->disableActions(); - - //Add data grid filters. - $grid->filter(function($filter){ - - // sql: ... WHERE `user.name` LIKE "%$name%"; - $filter->like('name', 'name'); - - // sql: ... WHERE `user.email` = $email; - $filter->is('emial', 'Email'); - - // sql: ... WHERE `user.created_at` BETWEEN $start AND $end; - $filter->between('created_at', 'Created Time')->datetime(); - }); -}); +#### 3.add left menu item -``` +Open `http://localhost:8000/admin/auth/menu`, add menu link and refresh the page, then you can find a link item in left menu bar. -###Admin\Form +#### 3.build grid and form -`Admin\Form` is a data form builder, in your controller: +The rest needs to be done is open `app/Admin/Contollers/UserController.php`, find `form()` and `grid()` method and write few lines of code with `model-grid` and `model-form`,for more detail, please read [model-grid](/docs/en/model-grid.md) and [model-form](/docs/en/model-form.md). -```php -return Admin::form(User::class, function(Form $form){ - - // $form->field(columnName [, columnName ], labelName = ''); - - $form->display('id', 'ID'); - $form->text('name')->rules('required'); - $form->email('email')->rules('required|email'); - - $form->password('password')->rules('required'); - - //Related column (hasOne relation). - $form->url('https://github.com/profile.homepage',%20'Home%20page'); - - $form->ip('last_login_ip', 'Last login ip'); - $form->datetime('last_login_at', 'Last login time'); - - //All fields can set a default value. - $form->color('color', 'Color')->default('#a34af4'); - - //Code editor based on code mirror see https://codemirror.net/ - $form->code('code')->lang('ruby'); - $form->json('json'); - - $form->currency('price')->symbol('¥'); - $form->number('count'); - $form->rate('rate'); - - //You can use all Intervention Image api in image field (see http://image.intervention.io/getting_started/introduction) - $form->image('avatar')->crop(int $width, int $height, [int $x, int $y]); - - $form->file('document')->rules('mimes:doc,docx,xlsx'); - $form->mobile('mobile')->format('999 9999 9999'); - $form->text('address'); - $form->date('birthday'); - $form->radio('gender')->values(['m' => 'Female', 'f'=> 'Male'])->default('m'); - - //Use Google map or Tencent map. - $form->map('latitude', 'longitude', 'Position'); - - //Options see http://ionden.com/a/plugins/ion.rangeSlider/en.html. - $form->slider('age', 'Age')->options(['max' => 50, 'min' => 20, 'step' => 1, 'postfix' => 'years old']); - - $form->display('created_at', 'Create time'); - $form->display('updated_at', 'Update time'); - - $form->datetimeRange('created_at', 'profile.updated_at', 'Time line'); - - //Belongs to many relation. - $form->multipleSelect('friends')->options(User::all()->lists('name', 'id')); - - //Belongs to many relation. - $form->checkbox('roles')->values(Role::all()->lists('display_name', 'id')); - - $form->switch('open')->states(['on' => 1, 'off' => 0]); - - //Add a divide line. - $form->divide(); - - //Has many relation, show as a list. - $form->hasMany('comments', function(Grid $grid) { - - // Set resource path for items. - $grid->resource('admin/article-comments'); - - $grid->id('ID'); - $grid->author()->value(function($authorId){ - return User::find($authorId)->name; - }); - $grid->email(); - $grid->content()->value(function($content) { - return mb_strimwidth($content, 0, 40, '...'); - }); - }); - - // Add saving callback function. - $form->saving(function(Form $form) { - if($form->password && $form->model()->password != $form->password) - { - $form->password = bcrypt($form->password); - } - }); -}); -``` +Other +------------ +`laravel-admin` based on thses plugins or services: -#License ++ [Laravel](https://laravel.com/) ++ [AdminLTE](https://almsaeedstudio.com/) ++ [Bootstrap Markdown](http://toopay.github.io/bootstrap-markdown/) ++ [Datetimepicker](http://eonasdan.github.io/bootstrap-datetimepicker/) ++ [CodeMirror](https://codemirror.net/) ++ [font-awesome](http://fontawesome.io) ++ [moment](http://momentjs.com/) ++ [Google map](https://www.google.com/maps) ++ [Tencent map](http://lbs.qq.com/) ++ [bootstrap-fileinput](https://github.com/kartik-v/bootstrap-fileinput) ++ [jquery-pjax](https://github.com/defunkt/jquery-pjax) ++ [Nestable](http://dbushell.github.io/Nestable/) ++ [noty](http://ned.im/noty/) -[WTFPL](http://www.wtfpl.net/) +License +------------ +`laravel-admin` is licensed under [The MIT License (MIT)](LICENSE). diff --git a/docs/en/form-upload.md b/docs/en/form-upload.md new file mode 100644 index 0000000000..ed903d0df3 --- /dev/null +++ b/docs/en/form-upload.md @@ -0,0 +1,100 @@ +# file/image upload + +[model-form](/docs/zh/model-form.md) can build file and image upload field with following codes + +```php +$form->file('file_column'); +$form->image('image_column'); +``` + +[model-form](/docs/zh/model-form.md) both support for local and cloud storage upload + +### upload to local + +first add storage configuration, add a disk in `config/filesystems.php`: + +```php + +'disks' => [ + ... , + + 'admin' => [ + 'driver' => 'local', + 'root' => public_path('upload'), + 'visibility' => 'public', + ], +], + +``` + +set upload path to `public/upload`(public_path('upload'))。 + +And then in `config/admin.php` select the `disk` set up above: + +```php + +'upload' => [ + + 'disk' => 'admin', + + 'directory' => [ + 'image' => 'image', + 'file' => 'file', + ], + + 'host' => 'http://localhost:8000/upload/', +], + +``` + +Set `disk` to the` admin` that you added above,`directory.image` and `directory.file` is the upload path for `$form->image($column)` and `$form->file($column)`. + +`host` is url prefix for your uploaded files. + + +### upload to clould + +If you need to upload to the cloud storage, need to install a driver which supports `flysystem` adapter, take `qiniu` cloud storage as example. + +first install [zgldh/qiniu-laravel-storage](https://github.com/zgldh/qiniu-laravel-storage). + +Also configure the disk, in the `config/filesystems.php` add an item: + +```php +'disks' => [ + ... , + 'qiniu' => [ + 'driver' => 'qiniu', + 'domains' => [ + 'default' => 'xxxxx.com1.z0.glb.clouddn.com', + 'https' => 'dn-yourdomain.qbox.me', + 'custom' => 'static.abc.com', + ], + 'access_key'=> '', //AccessKey + 'secret_key'=> '', //SecretKey + 'bucket' => '', //Bucket名字 + 'notify_url'=> '', // + ], +], + +``` + +Then modify the upload configuration of `laravel-admin` and open `config/admin.php` to find: + +```php + +'upload' => [ + + 'disk' => 'qiniu', + + 'directory' => [ + 'image' => 'image', + 'file' => 'file', + ], + + 'host' => 'http://of8kfibjo.bkt.clouddn.com/', +], + +``` + +Select the above configuration` qiniu` for `disk`, `host` configured to `qiniu` cloud storage test domain name. \ No newline at end of file diff --git a/docs/en/layout.md b/docs/en/layout.md new file mode 100644 index 0000000000..4046a6bfa6 --- /dev/null +++ b/docs/en/layout.md @@ -0,0 +1,28 @@ +# Layout + +The layout usage of `laravel-admin` can be found in the `index()` method of the home page's layout file [HomeController.php](/src/Commands/stubs/ExampleController.stub). + +The `Encore\Admin\Layout\Content` class is used to implement the layout of the content area. The `Content::row ($element)` method is used to add a row element: + +```php +// Add text +$content->row('One line of text'); + +// Add a component +$content->row(new Box('title', 'xxxx')); + + +``` + +The `Encore\Admin\Layout\Row` class is used for layout of inline elements. The `Row::column($width, $element)` method is used to add columns in a row: + +```php +// Add columns in the row +$content->row(function(Row $row) { + $row->column(4, 'xxx'); + $row->column(4, 'xxx'); + $row->column(4, 'xxx'); +}); + +``` +The `$width` parameter is used to set the width of the column elements, based on the `bootstrap` layout definition of the grid system. The `$element` parameter can be any object that implements the `Illuminate\Contracts\Support\Renderable` interface or any other printable variable. diff --git a/docs/en/menu.md b/docs/en/menu.md new file mode 100644 index 0000000000..6219d634d1 --- /dev/null +++ b/docs/en/menu.md @@ -0,0 +1,5 @@ +# Menu + +Open the `http://localhost:8000/admin/auth/menu` to set left sidebar menu. + +The tree menu is developed based on [Nestable] (https://github.com/dbushell/Nestable). \ No newline at end of file diff --git a/docs/en/model-form.md b/docs/en/model-form.md new file mode 100644 index 0000000000..78e2e8d3cf --- /dev/null +++ b/docs/en/model-form.md @@ -0,0 +1,319 @@ +# Model-Form + +The `Encore\Admin\Form` class is used to generate a data model-based form. For example, there is a` movies` table in the database + +```sql +CREATE TABLE `movies` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `director` int(10) unsigned NOT NULL, + `describe` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `rate` tinyint unsigned NOT NULL, + `released` enum(0, 1), + `release_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +``` + +The corresponding data model is `App\Models\Movie`, and the following code can generate the` movies` data form: + +```php + +use App\Models\Movie; +use Encore\Admin\Form; +use Encore\Admin\Facades\Admin; + +$grid = Admin::form(Movie::class, function(Form $grid){ + + // Displays the record id + $form->display('id', 'ID'); + + // Add an input box of type text + $form->text('title', 'Movie title'); + + $directors = [ + 'John' => 1, + 'Smith' => 2, + 'Kate' => 3, + ]; + + $form->select('director', 'Director')->options($directors); + + // Add textarea for the describe field + $form->textarea('describe', 'Describe'); + + // Number input + $form->number('rate', 'Rate'); + + // Add a switch field + $form->switch('released', 'Released?'); + + // Add a date and time selection box + $form->dateTime('release_at', 'release time'); + + // Display two time column + $form->display('created_at', 'Created time'); + $form->display('updated_at', 'Updated time'); +}); + +// Displays the form content +echo $form; + +``` + +# Basic Usage + +#### Text input + +```php +$form->text($column, [$label]); + +// Add a submission validation rule +$form->text($column, [$label])->rules('required|min:10'); +``` + +#### select +```php +$form->select($column[, $label])->options([1 => 'foo', 2 => 'bar', 'val' => 'Option name']); +``` + +Or load option by ajax +```php +$form->select($column[, $label])->options('/admin/demo/options'); +``` + +The json format returned from url `/admin/demo/options`: +``` +[ + { + "id": 1, + "text": "hello" + }, + { + "id": 2, + "text": "world" + }, +] +``` + +#### Multiple select +```php +$form->multipleSelect($column[, $label])->options([1 => 'foo', 2 => 'bar', 'val' => 'Option name']); +``` + +Or load option by ajax +```php +$form->multipleSelect($column[, $label])->options('/admin/demo/options'); +``` + +The json format returned from url `/admin/demo/options`: +``` +[ + { + "id": 1, + "text": "hello" + }, + { + "id": 2, + "text": "world" + }, +] +``` + +#### textarea: +```php +$form->textarea($column[, $label]); +``` + +#### radio +```php +$form->radio($column[, $label])->values(['m' => 'Female', 'f'=> 'Male'])->default('m'); +``` + +#### checkbox +The `values ()` method is used to set options: +```php +$form->checkbox($column[, $label])->values([1 => 'foo', 2 => 'bar', 'val' => 'Option name']); +``` + +#### email input +```php +$form->email($column[, $label]); +``` + +#### password input +```php +$form->password($column[, $label]); +``` + +#### url input +```php +$form->url(https://github.com/$column[,%20$label]); +``` + +#### ip input +```php +$form->ip($column[, $label]); +``` + +#### phone number input +```php +$form->mobile($column[, $label])->format('999 9999 9999'); +``` + +#### color select +```php +$form->color($column[, $label])->default('#ccc'); +``` + +#### time select +```php +$form->time($column[, $label]); + +// Set the time format, more formats reference http://momentjs.com/docs/#/displaying/format/ +$form->time($column[, $label])->format('HH:mm:ss'); +``` + +#### date input +```php +$form->date($column[, $label]); + +// 设置日期格式,更多格式参考http://momentjs.com/docs/#/displaying/format/ +$form->date($column[, $label])->format('YYYY-MM-DD'); +``` + +#### datetime input +```php +$form->datetime($column[, $label]); + +// Set the date format, more format reference http://momentjs.com/docs/#/displaying/format/ +$form->datetime($column[, $label])->format('YYYY-MM-DD HH:mm:ss'); +``` + +#### time range select +`$startTime`、`$endTime`is the start and end time fields: +```php +$form->timeRange($startTime, $endTime, 'Time Range'); +``` + +#### date range select +`$startDate`、`$endDate`is the start and end date fields: +```php +$form->dateRange($startDate, $endDate, 'Date Range'); +``` + +#### datetime range select +`$startDateTime`、`$endDateTime` is the start and end datetime fields: +```php +$form->datetimeRange($startDateTime, $endDateTime, 'DateTime Range'); +``` + +#### currency input +```php +$form->currency($column[, $label]); + +// set the unit symbol +$form->currency($column[, $label])->symbol('¥'); + +``` + +#### number input +```php +$form->number($column[, $label]); +``` + +#### rate input +```php +$form->rate($column[, $label]); +``` + +#### image upload +You can use compression, crop, add watermarks and other methods, please refer to [[Intervention] (http://image.intervention.io/getting_started/introduction)], picture upload directory in the file `config / admin.php` `Upload.image` configuration, if the directory does not exist, you need to create the directory and open write permissions: +```php +$form->image($column[, $label]); + +// Modify the image upload path and file name +$form->image($column[, $label])->move($dir, $name); + +// Crop picture +$form->image($column[, $label])->crop(int $width, int $height, [int $x, int $y]); + +// Add a watermark +$form->image($column[, $label])->insert($watermark, 'center'); +``` + +#### file upload +The file upload directory is configured in `upload.file` in the file `config/admin.php`. If the directory does not exist, it needs to be created and write-enabled. +```php +$form->file($column[, $label]); + +// Modify the file upload path and file name +$form->file($column[, $label])->move($dir, $name); + +// And set the upload file type +$form->file($column[, $label])->rules('mimes:doc,docx,xlsx'); +``` + +#### map +Used to select the latitude and longitude, `$ latitude`,` $ longitude` for the latitude and longitude field, using Tencent map when `locale` set of laravel is` zh_CN`, otherwise use Google Maps: +```php +$form->map($latitude, $longitude, $label); + +// Use Tencent map +$form->map($latitude, $longitude, $label)->useTencentMap(); + +// Use google map +$form->map($latitude, $longitude, $label)->useGoogleMap(); +``` + +#### slide +Can be used to select the type of digital fields, such as age: +```php +$form->slider($column[, $label])->options(['max' => 100, 'min' => 1, 'step' => 1, 'postfix' => 'years old']); +``` + +#### rich text editor +```php +$form->editor($column[, $label]); +``` + +#### json editor +```php +$form->json($column[, $label]); +``` + +#### hidden field +```php +$form->hidden($column); +``` + +#### switch +`On` and` off` pairs of switches with the values `1` and` 0`: +```php +$form->switch($column[, $label])->states(['on' => 1, 'off' => 0]); +``` + +#### display field +Only display the fields and without any action: +```php +$form->display($column[, $label]); +``` + +#### divide +```php +$form->divide(); +``` + +#### saving callback +Add callback function when saving data, can use it do some operations when saving data. +```php +$form->saving(function(Form $form) { + if($form->password && $form->model()->password != $form->password) + { + $form->password = bcrypt($form->password); + } +}); +``` \ No newline at end of file diff --git a/docs/en/model-grid.md b/docs/en/model-grid.md new file mode 100644 index 0000000000..21fa1e3cba --- /dev/null +++ b/docs/en/model-grid.md @@ -0,0 +1,174 @@ +# Model-Grid + +Class `Encore\Admin\Grid` is used to generate tables based on the data model,for example,we have a table `movies` in database: + +```sql +CREATE TABLE `movies` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `director` int(10) unsigned NOT NULL, + `describe` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `rate` tinyint unsigned NOT NULL, + `released` enum(0, 1), + `release_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +``` + +And the model of this table is `App\Models\Movie`,The following code can generate the data ggrid for `users`: + +```php + +use App\Models\Movie; +use Encore\Admin\Grid; +use Encore\Admin\Facades\Admin; + +$grid = Admin::grid(Movie::class, function(Grid $grid){ + + // The first column displays the id field and sets the column as a sortable column + $grid->id('ID')->sortable(); + + // The second column shows the title field, because the title field name and the Grid object's title method conflict, so use Grid's column () method instead + $grid->column('title'); + + // The third column shows the director field, which is set by the value($callback) method to display the corresponding user name in the users table + $grid->director()->value(function($userId) { + return User::find($userId)->name; + }); + + // The fourth column appears as the describe field + $grid->describe(); + + // The fifth column is displayed as the rate field + $grid->rate(); + + // The sixth column shows the released field, formatting the display output through the value($callback) method + $grid->released('Release?')->value(function ($released) { + return $released ? 'yes' : 'no'; + }); + + // The following shows the columns for the three time fields + $grid->release_at(); + $grid->created_at(); + $grid->updated_at(); + + // The filter($callback) method is used to set up a simple search box for the table + $grid->filter(function ($filter) { + + // Sets the range query for the created_at field + $filter->between('created_at', 'Created Time')->datetime(); + }); +}); + +// Displays the table contents +echo $grid; + +``` + +## Basic Usage + +#### Set the table title +```php +$grid->title('Movie list'); +``` + +#### Add a column +```php + +// Add the column directly through the field name `username` +$grid->username('Username'); + +// The effect is the same as above +$grid->column('username', 'Username'); + +// Add multiple columns +$grid->columns('email', 'username' ...); +``` + +#### Modify the source data +```php +$grid->model()->where('id', '>', 100); + +$grid->model()->orderBy('id', 'desc'); + +$grid->model()->take(100); + +``` + +#### Sets the number of lines displayed per page + +```php +// The default is 15 per page +$grid->paginate(15); +``` + +#### Modify the display output + +```php +$grid->text()->value(function($text) { + return str_limit($text, 30, '...'); +}); + +$grid->name()->value(function ($name) { + return "$name"; +}); + +$grid->email()->value(function ($email) { + return "mailto:$email"; +}); + +``` + +#### Disable The batch delete button +```php +$grid->disableBatchDeletion(); +``` +#### Modify the row action button +```php +//Opens the edit and delete operations +$grid->actions('edit|delete'); + +//Close all operations +$grid->disableActions(); +``` + +#### Column control +```php +$grid->rows(function($row){ + + //add style to lines which Id less than 10 + if($row->id < 10) { + $row->style('color:red'); + } + + // Open the edit operation for specified column + if($row->id % 3) { + $row->action('edit'); + } + + //Specifies the column to add a custom action button + if($row->id % 2) { + $row->actions()->add(function ($row) { + return "btn"; + }); + } +}); +``` + +#### Add query filters +```php +$grid->filter(function($filter){ + + // sql: ... WHERE `user.name` LIKE "%$name%"; + $filter->like('name', 'name'); + + // sql: ... WHERE `user.email` = $email; + $filter->is('emial', 'Email'); + + // sql: ... WHERE `user.created_at` BETWEEN $start AND $end; + $filter->between('created_at', 'Created Time')->datetime(); +}); +``` \ No newline at end of file diff --git a/docs/en/quick-start.md b/docs/en/quick-start.md new file mode 100644 index 0000000000..21e9fd88f7 --- /dev/null +++ b/docs/en/quick-start.md @@ -0,0 +1,44 @@ +Quick start +------------ + +We use `users` table come with `Laravel` for example,the structure of table is: +```sql +CREATE TABLE `users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `password` varchar(60) COLLATE utf8_unicode_ci NOT NULL, + `remember_token` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY (`id`), + UNIQUE KEY `users_email_unique` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci +``` +And the model for this table is `App\User.php` + +You can follow these steps to setup `CURD` interfaces of table `users`: + +#### 1.add controller + +Use the following command to create a controller for `App\User` model + +```php +php artisan admin:make UserController --model=App\\User +``` +The above command will create the controller in `app/Admin/Controllers/UserController.php`. + +#### 2.add route + +Add a route in `app/Admin/routes.php`: +``` +$router->resource('users', UserController::class); +``` + +#### 3.add left menu item + +Open `http://localhost:8000/admin/auth/menu`, add menu link and refresh the page, then you can find a link item in left menu bar. + +#### 3.build grid and form + +The rest needs to be done is open `app/Admin/Contollers/UserController.php`, find `form()` and `grid()` method and write few lines of code with `model-grid` and `model-form`,for more detail, please read [model-grid](/docs/en/model-grid.md) and [model-form](/docs/en/model-form.md). \ No newline at end of file diff --git a/docs/en/widgets/box.md b/docs/en/widgets/box.md new file mode 100644 index 0000000000..74325ff987 --- /dev/null +++ b/docs/en/widgets/box.md @@ -0,0 +1,38 @@ +# Box + +`Encore\Admin\Widgets\Box` used to generate box components: + +```php +use Encore\Admin\Widgets\Box; + +$box = new Box('Box Title', 'Box content'); + +$box->removable(); + +$box->collapsable(); + +$box->style('info'); + +$box->solid(); + +echo $box; + +``` + +`Box::__construct($title, $content)`,`$title`参数为Box组件的标题,`$content`参数为Box的内容元素,可以是实现了`Illuminate\Contracts\Support\Renderable`接口的对象或者其他可打印变量。 + +The `$content` parameter is the content element of the Box, which can be either an implementation of the `Illuminate\Contracts \ Support\Renderable` interface, or other printable variables. + +The `Box::title($title)` method is used to set the Box component title. + +The `Box::content($content)` method is used to set the content element of a Box component. + +The `Box::removable()` method sets the Box component as removable. + +The `Box::collapsable()` method sets the Box component as collapsable. + +`Box::style($style)` method sets the style of the Box component to fill in `primary`,` info`, `danger`,` warning`, `success`,` default`. + +The `Box::solid()` method adds a border to the Box component. + + diff --git a/docs/en/widgets/carousel.md b/docs/en/widgets/carousel.md new file mode 100644 index 0000000000..24c0b62ce0 --- /dev/null +++ b/docs/en/widgets/carousel.md @@ -0,0 +1,30 @@ +# Carousel + +`Encore\Admin\Widgets\Carousel`Used to generate carousel components: + +```php +use Encore\Admin\Widgets\Carousel; + +$items = [ + [ + 'image' => 'http://xxxx/xxx.jpg', + 'caption' => 'xxxx', + ], + [ + 'image' => 'http://xxxx/xxx.jpg', + 'caption' => 'xxxx', + ], + [ + 'image' => 'http://xxxx/xxx.jpg', + 'caption' => 'xxxx', + ], +]; + +$carousel = new Carousel($items); + +echo $carousel->render(); +``` + +The `Carousel::__construct($items)`, `$items` parameter sets the content element of the sliding album. + + diff --git a/docs/en/widgets/collapse.md b/docs/en/widgets/collapse.md new file mode 100644 index 0000000000..cdfcad5d60 --- /dev/null +++ b/docs/en/widgets/collapse.md @@ -0,0 +1,16 @@ +# Collapse + +`Encore\Admin\Widgets\Collapse` class used to generate folding components: +```php +use Encore\Admin\Widgets\Collapse; + +$collapse = new Collapse(); + +$collapse->add('Bar', 'xxxxx'); +$collapse->add('Orders', new Table()); + +echo $collapse->render(); + +``` + +The `Collapse::add($title, $content)` method is used to add a collapsed item to the collapsing component. The `$title` parameter sets the title of the item. The`$content` parameter is used to . \ No newline at end of file diff --git a/docs/en/widgets/form.md b/docs/en/widgets/form.md new file mode 100644 index 0000000000..db6916b869 --- /dev/null +++ b/docs/en/widgets/form.md @@ -0,0 +1,240 @@ +# Form + +`Encore\Admin\Widgets\Form` class is used to quickly build a form: + +```php + +$form = new Form(); + +$form->action('example'); + +$form->email('email')->default('qwe@aweq.com'); +$form->password('password'); +$form->text('name'); +$form->https://github.com/url('url'); +$form->color('color'); +$form->map('lat', 'lng'); +$form->date('date'); +$form->json('val'); +$form->dateRange('created_at', 'updated_at'); + +echo $form->render(); +``` + +`Form::__construct($data = [])` generates a form object. If the `$data` parameter is passed, the elements in the` $data` array will be filled into the form. + +`Form::action($uri)` method is used to set the form submission address. + +`Form::method($method)` method is used to set the submit method of the form form, the default is `POST` method. + +# The form elements +The `Form` object implements nearly 30 form elements via the magic method `__call()`, which adds the form element with a short call: + +#### Text input + +```php +$form->text($column, [$label]); + +// Add a submission validation rule +$form->text($column, [$label])->rules('required|min:10'); +``` + +#### Select +```php +$form->select($column[, $label])->options([1 => 'foo', 2 => 'bar', 'val' => 'Option name']); +``` + +#### Multiple select +```php +$form->multipleSelect($column[, $label])->options([1 => 'foo', 2 => 'bar', 'val' => 'Option name']); +``` + +#### Textarea +```php +$form->textarea($column[, $label]); +``` + +#### Radio +```php +$form->radio($column[, $label])->values(['m' => 'Female', 'f'=> 'Male'])->default('m'); +``` + +#### Checkbox +The `values()` method is used to set options: +```php +$form->checkbox($column[, $label])->values([1 => 'foo', 2 => 'bar', 'val' => 'Option name']); +``` + +#### Email +```php +$form->email($column[, $label]); +``` + +#### Password +```php +$form->password($column[, $label]); +``` + +#### Url +```php +$form->url(https://github.com/$column[,%20$label]); +``` + +#### Ip +```php +$form->ip($column[, $label]); +``` + +#### Mobile +```php +$form->mobile($column[, $label])->format('999 9999 9999'); +``` + +#### Color +```php +$form->color($column[, $label])->default('#ccc'); +``` + +#### Time +```php +$form->time($column[, $label]); + +// 设置时间格式,更多格式参考http://momentjs.com/docs/#/displaying/format/ +$form->time($column[, $label])->format('HH:mm:ss'); +``` + +#### Date +```php +$form->date($column[, $label]); + +// 设置日期格式,更多格式参考http://momentjs.com/docs/#/displaying/format/ +$form->date($column[, $label])->format('YYYY-MM-DD'); +``` + +#### Datetime +```php +$form->datetime($column[, $label]); + +// Set the date format, more format reference http://momentjs.com/docs/#/displaying/format/ +$form->datetime($column[, $label])->format('YYYY-MM-DD HH:mm:ss'); +``` + +#### Time range +`$startTime`、`$endTime` is the start and end time fields: +```php +$form->timeRange($startTime, $endTime, 'Time Range'); +``` + +#### Date range +`$startDate`、`$endDate` is the start and end date fields: +```php +$form->dateRange($startDate, $endDate, 'Date Range'); +``` + +#### Datetime range +`$startDateTime`、`$endDateTime`is the start and end datetime fields: +```php +$form->datetimeRange($startDateTime, $endDateTime, 'DateTime Range'); +``` + +#### Currency +```php +$form->currency($column[, $label]); + +// Sets the unit symbol +$form->currency($column[, $label])->symbol('¥'); + +``` + +#### Number +```php +$form->number($column[, $label]); +``` + +#### Decimal +```php +$form->decimal($column[, $label]); +``` + +#### Rate +```php +$form->rate($column[, $label]); +``` + +#### Image upload +You can use compression, crop, add watermarks and other methods, please refer to [[Intervention](http://image.intervention.io/getting_started/introduction)],The image upload directory is configured in `upload` in the file `config/admin.php`. If the directory does not exist, it needs to be created and write-enabled. : +```php +$form->image($column[, $label]); + +// Modify the image upload path and file name +$form->image($column[, $label])->move($dir, $name); + +// Crop picture +$form->image($column[, $label])->crop(int $width, int $height, [int $x, int $y]); + +// Add a watermark +$form->image($column[, $label])->insert($watermark, 'center'); +``` + +#### File upload +文件上传目录在文件`config/admin.php`中的`upload.file`中配置,如果目录不存在,需要创建该目录并开放写权限。 +The file upload directory is configured in `upload` in the file `config/admin.php`. If the directory does not exist, create the directory and open the write permissions. +```php +$form->file($column[, $label]); + +// Modify the file upload path and file name +$form->file($column[, $label])->move($dir, $name); + +// And set the upload file type +$form->file($column[, $label])->rules('mimes:doc,docx,xlsx'); +``` + +#### Map +Map element is used to select the latitude and longitude, `$latitude`,`$longitude` for the latitude and longitude field, using Tencent map if the `locale` in `config/app.php` is set to` zh_CN`,otherwise use Google Maps: +```php +$form->map($latitude, $longitude, $label); + +// Use Tencent map +$form->map($latitude, $longitude, $label)->useTencentMap(); + +// Use Google Maps +$form->map($latitude, $longitude, $label)->useGoogleMap(); +``` + +#### Slider +Can be used to select the type of digital fields, such as age: +```php +$form->slider($column[, $label])->options(['max' => 100, 'min' => 1, 'step' => 1, 'postfix' => 'years old']); +``` + +#### Editor +```php +$form->editor($column[, $label]); +``` + +#### Json Editor +```php +$form->json($column[, $label]); +``` + +#### Hidden +```php +$form->hidden($column); +``` + +#### Switch +`on` and `off` pairs of switches with the values `1` and` 0`: +```php +$form->switch($column[, $label])->states(['on' => 1, 'off' => 0]); +``` + +#### Display +Only the fields are displayed without any action: +```php +$form->display($column[, $label]); +``` + +#### Divide line +```php +$form->divide(); +``` \ No newline at end of file diff --git a/docs/en/widgets/info-box.md b/docs/en/widgets/info-box.md new file mode 100644 index 0000000000..558e49521b --- /dev/null +++ b/docs/en/widgets/info-box.md @@ -0,0 +1,14 @@ +# Infobox + +The `Encore\Admin\Widgets\InfoBox` class is used to generate the information presentation block: + +```php +use Encore\Admin\Widgets\InfoBox; + +$infoBox = new InfoBox('New Users', 'users', 'aqua', '/admin/users', '1024'); + +echo $infoBox->render(); + +``` + +Refer to the section on the `InfoBox` in the` index()`method of the home page layout file [HomeController.php](/src/Commands/stubs/ExampleController.stub). \ No newline at end of file diff --git a/docs/en/widgets/tab.md b/docs/en/widgets/tab.md new file mode 100644 index 0000000000..48e3efb151 --- /dev/null +++ b/docs/en/widgets/tab.md @@ -0,0 +1,18 @@ +# Tab component + +The `Encore\Admin\Widgets\Tab` class is used to generate the tab components: + +```php +use Encore\Admin\Widgets\Tab; + +$tab = new Tab(); + +$tab->add('Pie', $pie); +$tab->add('Table', new Table()); +$tab->add('Text', 'blablablabla....'); + +echo $tab->render(); + +``` + +The `Tab::ad ($title, $content)` method is used to add a tab, `$title` for the option title, and the` $content` tab for the content. \ No newline at end of file diff --git a/docs/en/widgets/table.md b/docs/en/widgets/table.md new file mode 100644 index 0000000000..f3ebdee97e --- /dev/null +++ b/docs/en/widgets/table.md @@ -0,0 +1,36 @@ +# Table + +`Encore\Admin\Widgets\Table` class is used to generate forms: + +```php +use Encore\Admin\Widgets\Table; + +// table 1 +$headers = ['Id', 'Email', 'Name', 'Company']; +$rows = [ + [1, 'labore21@yahoo.com', 'Ms. Clotilde Gibson', 'Goodwin-Watsica'], + [2, 'omnis.in@hotmail.com', 'Allie Kuhic', 'Murphy, Koepp and Morar'], + [3, 'quia65@hotmail.com', 'Prof. Drew Heller', 'Kihn LLC'], + [4, 'xet@yahoo.com', 'William Koss', 'Becker-Raynor'], + [5, 'ipsa.aut@gmail.com', 'Ms. Antonietta Kozey Jr.'], +]; + +$table = new Table($headers, $rows); + +echo $table->render(); + +// table 2 +$headers = ['Keys', 'Values']; +$rows = [ + 'name' => 'Joe', + 'age' => 25, + 'gender' => 'Male', + 'birth' => '1989-12-05', +]; + +$table = new Table($headers, $rows); + +echo $table->render(); + +``` + diff --git a/docs/zh/README.md b/docs/zh/README.md index 1ec47fc7c6..632aacd701 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -3,6 +3,7 @@ laravel-admin [![Build Status](https://travis-ci.org/z-song/laravel-admin.svg?branch=master)](https://travis-ci.org/z-song/laravel-admin) [![StyleCI](https://styleci.io/repos/48796179/shield)](https://styleci.io/repos/48796179) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/z-song/laravel-admin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/z-song/laravel-admin/?branch=master) [![Packagist](https://img.shields.io/packagist/l/encore/laravel-admin.svg?maxAge=2592000)](https://packagist.org/packages/encore/laravel-admin) [![Total Downloads](https://img.shields.io/packagist/dt/encore/laravel-admin.svg?style=flat-square)](https://packagist.org/packages/encore/laravel-admin) @@ -10,12 +11,12 @@ laravel-admin [Demo](http://120.26.143.106/admin) 账号/密码:admin/admin +Inspired by [SleepingOwlAdmin](https://github.com/sleeping-owl/admin) and [rapyd-laravel](https://github.com/zofe/rapyd-laravel). + 截图 ------------ -![grid](https://cloud.githubusercontent.com/assets/1479100/16609399/894e0832-4386-11e6-8709-1cc7ce429e7c.png) - -![form](https://cloud.githubusercontent.com/assets/1479100/12708198/fc6725a8-c8d7-11e5-876f-5c4f00ded0ff.png) +![laravel-admin](https://cloud.githubusercontent.com/assets/1479100/19625297/3b3deb64-9947-11e6-807c-cffa999004be.jpg) 安装 ------------ @@ -56,14 +57,14 @@ php artisan admin:install - [数据模型表格](/docs/zh/model-grid.md) - [数据模型表单](/docs/zh/model-form.md) - [图片/文件上传](/docs/zh/form-upload.md) -- [组件](/docs/zh/table.md) - - [表格](/docs/zh/table.md) - - [表单](/docs/zh/form.md) - - [盒子](/docs/zh/box.md) - - [信息盒子](/docs/zh/info-box.md) - - [选项卡](/docs/zh/tab.md) - - [滑动相册](/docs/zh/carousel.md) - - [折叠容器](/docs/zh/collapse.md) +- [组件](/docs/zh/widgets/table.md) + - [表格](/docs/zh/widgets/table.md) + - [表单](/docs/zh/widgets/form.md) + - [盒子](/docs/zh/widgets/box.md) + - [信息盒子](/docs/zh/widgets/info-box.md) + - [选项卡](/docs/zh/widgets/tab.md) + - [滑动相册](/docs/zh/widgets/carousel.md) + - [折叠容器](/docs/zh/widgets/collapse.md) - 数据图表 TODO - [权限控制](/docs/zh/permission.md) @@ -73,9 +74,6 @@ php artisan admin:install `app/Admin/routes.php`文件用来配置后台路由,详细使用请阅读[路由配置](/docs/zh/router.md)。 -`app/Admin/menu.php`文件用来配置后台左侧菜单栏,详细使用请阅读[菜单栏配置](/docs/zh/menu.md)。 - - `app/Admin/Controllers`目录用来存放后台路由器文件,该目录下的`HomeController.php`文件是后台首页的显示控制器,`ExampleController.php`为实例文件。 快速开始 @@ -100,114 +98,14 @@ CREATE TABLE `users` ( `laravel-admin`可以通过使用以下几步来快速生成`users`表的`CURD`操作页面: ### 1.添加路由器 -```php -header('header'); - $content->description('description'); - - $content->body($this->grid()); - }); - } - - /** - * Edit interface. - * - * @param $id - * @return Content - */ - public function edit($id) - { - return Admin::content(function (Content $content) use ($id) { - - $content->header('header'); - $content->description('description'); - - $content->body($this->form()->edit($id)); - }); - } - - /** - * Create interface. - * - * @return Content - */ - public function create() - { - return Admin::content(function (Content $content) { - - $content->header('header'); - $content->description('description'); - - $content->body($this->form()); - }); - } - - /** - * Make a grid builder. - * - * @return Grid - */ - protected function grid() - { - return Admin::grid(User::class, function (Grid $grid) { - - $grid->id('ID')->sortable(); - - $grid->name('用户名'); - $grid->email('邮箱'); - - $grid->created_at(); - $grid->updated_at(); - }); - } - - /** - * Make a form builder. - * - * @return Form - */ - protected function form() - { - return Admin::form(User::class, function (Form $form) { - - $form->display('id', 'ID'); - - $form->text('name', '用户名'); - $form->email('email', '用户邮箱'); - $form->password('password', '密码'); - - $form->dateTime('created_at', 'Created At'); - $form->dateTime('updated_at', 'Updated At'); - }); - } -} +使用下面的命令来创建一个对应`App\User`模型的路由器 +```php +php artisan admin:make UserController --model=App\\User ``` +上面的命令会创建路由器文件`app/Admin/Controllers/UserController.php`. + ### 2.添加路由配置 在`laravel-admin`的路由配置文件`app/Admin/routes.php`里添加一行: @@ -217,22 +115,11 @@ $router->resource('users', UserController::class); ### 3.添加左侧菜单栏连接 -打开文件`app/Admin/menu.php`,添加以下数据: - -``` -... -[ - 'title' => '用户列表', - 'url' => 'users', - 'icon' => 'fa-users', -], -... - -``` +打开`http://localhost:8000/admin/auth/menu`,添加对应的menu 然后就能在后台管理页面的左侧边栏看到用户管理页面的链接入口了。 -对于数据表格(model-grid)和数据表单(model-form)的详细使用请查看[model-grid]()和[model-form]()。 +对于数据表格(model-grid)和数据表单(model-form)的详细使用请查看[model-grid](/docs/zh/model-grid.md)和[model-form](/docs/zh/model-form.md)。 其它 ------------ @@ -249,8 +136,8 @@ $router->resource('users', UserController::class); + [Tencent map](http://lbs.qq.com/) + [bootstrap-fileinput](https://github.com/kartik-v/bootstrap-fileinput) + [jquery-pjax](https://github.com/defunkt/jquery-pjax) - -Inspired by [SleepingOwlAdmin](https://github.com/sleeping-owl/admin) and [rapyd-laravel](https://github.com/zofe/rapyd-laravel). ++ [Nestable](http://dbushell.github.io/Nestable/) ++ [noty](http://ned.im/noty/) 交流 ------------ diff --git a/docs/zh/box.md b/docs/zh/widgets/box.md similarity index 100% rename from docs/zh/box.md rename to docs/zh/widgets/box.md diff --git a/docs/zh/carousel.md b/docs/zh/widgets/carousel.md similarity index 100% rename from docs/zh/carousel.md rename to docs/zh/widgets/carousel.md diff --git a/docs/zh/collapse.md b/docs/zh/widgets/collapse.md similarity index 100% rename from docs/zh/collapse.md rename to docs/zh/widgets/collapse.md diff --git a/docs/zh/form.md b/docs/zh/widgets/form.md similarity index 100% rename from docs/zh/form.md rename to docs/zh/widgets/form.md diff --git a/docs/zh/info-box.md b/docs/zh/widgets/info-box.md similarity index 100% rename from docs/zh/info-box.md rename to docs/zh/widgets/info-box.md diff --git a/docs/zh/tab.md b/docs/zh/widgets/tab.md similarity index 100% rename from docs/zh/tab.md rename to docs/zh/widgets/tab.md diff --git a/docs/zh/table.md b/docs/zh/widgets/table.md similarity index 100% rename from docs/zh/table.md rename to docs/zh/widgets/table.md From eadf722e1621b8f21a037e6cf250e2897498f4c7 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 24 Oct 2016 11:51:18 +0800 Subject: [PATCH 0047/2161] fix url issue --- src/Admin.php | 4 ++++ src/Form.php | 16 ++++++++++------ src/Form/Builder.php | 12 ++++++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Admin.php b/src/Admin.php index 2459204c34..5023a4525b 100644 --- a/src/Admin.php +++ b/src/Admin.php @@ -121,6 +121,10 @@ public static function url(https://github.com/$url) { $prefix = (string) config('admin.prefix'); + if (empty($prefix) || $prefix == '/') { + return '/'.trim($url, '/'); + } + return "/$prefix/".trim($url, '/'); } diff --git a/src/Form.php b/src/Form.php index d1cedf6838..7f60cef836 100644 --- a/src/Form.php +++ b/src/Form.php @@ -271,7 +271,7 @@ public function store() $this->complete($this->saved); - return redirect($this->resource()); + return redirect($this->resource(0)); } /** @@ -407,7 +407,7 @@ public function update($id) $this->complete($this->saved); - return redirect($this->resource()); + return redirect($this->resource(-1)); } /** @@ -709,16 +709,20 @@ public function getRelations() /** * Get current resource route url. * + * @param int $slice * @return string */ - public function resource() + public function resource($slice = -2) { $route = app('router')->current(); - $prefix = $route->getPrefix(); - $resource = trim(preg_replace("#$prefix#", '', $route->getUri(), 1), '/').'/'; + $segments = explode('/', trim($route->getUri(), '/')); - return "/$prefix/".substr($resource, 0, strpos($resource, '/')); + if ($slice != 0) { + $segments = array_slice($segments, 0, $slice); + } + + return '/'.join('/', $segments); } /** diff --git a/src/Form/Builder.php b/src/Form/Builder.php index 163984389e..9589a7b038 100644 --- a/src/Form/Builder.php +++ b/src/Form/Builder.php @@ -140,13 +140,15 @@ public function hasFile() */ public function open($options = []) { + $attributes = []; + if ($this->mode == self::MODE_EDIT) { $attributes['action'] = $this->form->resource().'/'.$this->id; $this->form->hidden('_method')->value('PUT'); } if ($this->mode == self::MODE_CREATE) { - $attributes['action'] = $this->form->resource(); + $attributes['action'] = $this->form->resource(-1); } $attributes['method'] = array_get($options, 'method', 'post'); @@ -193,13 +195,15 @@ public function render() $confirm = trans('admin::lang.delete_confirm'); $token = csrf_token(); - $location = '/'.trim($this->form->resource(), '/'); + $slice = $this->mode == static::MODE_CREATE ? -1 : -2; + + $location = '/'.trim($this->form->resource($slice), '/'); $script = << + @if(config('app.locale') == 'zh_CN') @@ -89,6 +91,16 @@ + @if(config('app.locale') == 'zh_CN') @@ -89,6 +91,16 @@ @else - + @endif @else - + @endif @else - + @endif - - - - - - - - - - - - - - - -@if(config('app.locale') == 'zh_CN') - -@else - -@endif +{!! Admin::js() !!} - @else - - @endif + @endforeach \ No newline at end of file From b0cbd2249d9b4a9bdbe71351b9b2ae0ab9d9808d Mon Sep 17 00:00:00 2001 From: Song Date: Sun, 20 Nov 2016 16:41:29 +0000 Subject: [PATCH 0127/2161] Applied fixes from StyleCI --- config/admin.php | 14 +++++++------- src/Admin.php | 6 ++++-- src/Auth/Database/Administrator.php | 1 + src/Form.php | 3 +-- tests/InstallTest.php | 4 ++-- tests/UsersTest.php | 6 +++--- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/config/admin.php b/config/admin.php index 101a051124..32d85d0e5b 100644 --- a/config/admin.php +++ b/config/admin.php @@ -2,27 +2,27 @@ return [ - /** + /* * Laravel-admin name. */ 'name' => 'Laravel-admin', - /** + /* * Laravel-admin url prefix. */ 'prefix' => 'admin', - /** + /* * Laravel-admin install directory. */ 'directory' => app_path('Admin'), - /** + /* * Laravel-admin title. */ 'title' => 'Admin', - /** + /* * Laravel-admin auth setting. */ 'auth' => [ @@ -31,7 +31,7 @@ 'model' => Encore\Admin\Auth\Database\Administrator::class, ], - /** + /* * Laravel-admin upload setting. */ 'upload' => [ @@ -46,7 +46,7 @@ 'host' => 'http://localhost:8000/upload/', ], - /** + /* * Laravel-admin database setting. */ 'database' => [ diff --git a/src/Admin.php b/src/Admin.php index e986cd4886..efc6417a91 100644 --- a/src/Admin.php +++ b/src/Admin.php @@ -31,7 +31,7 @@ class Admin public static $js = []; /** - * Initialize + * Initialize. */ public static function init() { @@ -49,7 +49,7 @@ public static function bootstrap() Form::collectFieldAssets(); } - + /** * @param $model * @param Closure $callable @@ -132,6 +132,7 @@ public function controllerNamespace() * Add css or get all css. * * @param null $css + * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|void */ public static function css($css = null) @@ -153,6 +154,7 @@ public static function css($css = null) * Add js or get all js. * * @param null $js + * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|void */ public static function js($js = null) diff --git a/src/Auth/Database/Administrator.php b/src/Auth/Database/Administrator.php index bc19208fe6..00ce1b8e4c 100644 --- a/src/Auth/Database/Administrator.php +++ b/src/Auth/Database/Administrator.php @@ -119,6 +119,7 @@ public function isRole($role) * Check if user in $roles. * * @param array $roles + * * @return mixed */ public function inRoles($roles = []) diff --git a/src/Form.php b/src/Form.php index ac6d856559..671df1fe0b 100644 --- a/src/Form.php +++ b/src/Form.php @@ -934,7 +934,7 @@ public static function findFieldClass($method) */ public static function collectFieldAssets() { - if (! empty(static::$collectedAssets)) { + if (!empty(static::$collectedAssets)) { return static::$collectedAssets; } @@ -942,7 +942,6 @@ public static function collectFieldAssets() $js = collect(); foreach (static::$availableFields as $field) { - if (!method_exists($field, 'getAssets')) { continue; } diff --git a/tests/InstallTest.php b/tests/InstallTest.php index bbe7062ae5..4f6ae30201 100644 --- a/tests/InstallTest.php +++ b/tests/InstallTest.php @@ -4,7 +4,7 @@ * Created by PhpStorm. * User: encore * Date: 16/11/20 - * Time: 下午6:44 + * Time: 下午6:44. */ class InstallTest extends TestCase { @@ -26,4 +26,4 @@ public function testInstalledDirectories() $this->assertFileExists(public_path('packages/admin')); } -} \ No newline at end of file +} diff --git a/tests/UsersTest.php b/tests/UsersTest.php index dd3ec15023..2bd4c56e8f 100644 --- a/tests/UsersTest.php +++ b/tests/UsersTest.php @@ -43,16 +43,16 @@ public function testCreateUser() ->seeIsAuthenticated('admin') ->seePageIs('admin'); - $this->assertFalse($this->app['auth']->guard('admin')->getUser()->isAdministrator()); + $this->assertFalse($this->app['auth']->guard('admin')->getUser()->isAdministrator()); - $this + $this ->dontSee('Users') ->dontSee('Roles') ->dontSee('Permission') ->dontSee('Operation log') ->dontSee('Menu'); } - + public function testUpdateUser() { $this->visit('admin/auth/users/'.$this->user->id.'/edit') From 4ad6a03a9137833881c4987b644f368a23456676 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 21 Nov 2016 11:40:49 +0800 Subject: [PATCH 0128/2161] file upload issue --- src/Form/Field/File.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Form/Field/File.php b/src/Form/Field/File.php index bf28899825..e5dbf415b5 100644 --- a/src/Form/Field/File.php +++ b/src/Form/Field/File.php @@ -135,8 +135,6 @@ public function objectUrl($path) public function render() { - $this->js[] = 'bootstrap-fileinput/js/fileinput_locale_'.config('app.locale').'.js'; - $this->options['initialCaption'] = basename($this->value); if (!empty($this->value)) { From 3ea242472f2071d5eb93a5d24e724069537331a6 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 21 Nov 2016 12:27:49 +0800 Subject: [PATCH 0129/2161] form submit issue --- src/Admin.php | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Admin.php b/src/Admin.php index efc6417a91..cdd0c8428a 100644 --- a/src/Admin.php +++ b/src/Admin.php @@ -30,12 +30,26 @@ class Admin */ public static $js = []; + /** + * @var bool + */ + protected static $initialized = false; + + /** + * @var bool + */ + protected static $bootstrapped = false; + /** * Initialize. */ public static function init() { - Form::registerBuiltinFields(); + if (!static::$initialized) { + Form::registerBuiltinFields(); + + static::$initialized = true; + } } /** @@ -43,11 +57,13 @@ public static function init() */ public static function bootstrap() { - if (file_exists($bootstrap = admin_path('bootstrap.php'))) { - require $bootstrap; - } + if (!static::$bootstrapped) { + if (file_exists($bootstrap = admin_path('bootstrap.php'))) { + require $bootstrap; + } - Form::collectFieldAssets(); + static::$bootstrapped = true; + } } /** @@ -69,6 +85,9 @@ public function grid($model, Closure $callable) */ public function form($model, Closure $callable) { + static::init(); + static::bootstrap(); + return new Form($this->getModel($model), $callable); } @@ -92,9 +111,10 @@ public function tree($model) public function content(Closure $callable) { static::init(); - static::bootstrap(); + Form::collectFieldAssets(); + return new Content($callable); } From bc9fb4b1c5dd866a11e80be2bab05a202ee2c175 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 21 Nov 2016 13:08:11 +0800 Subject: [PATCH 0130/2161] update docs [skip ci] --- docs/zh/README.md | 1 + docs/zh/field-management.md | 124 ++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 docs/zh/field-management.md diff --git a/docs/zh/README.md b/docs/zh/README.md index d33bba6f73..52aa8f790d 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -64,6 +64,7 @@ php artisan admin:install - [数据模型表格](/docs/zh/model-grid.md) - [数据模型表单](/docs/zh/model-form.md) - [图片/文件上传](/docs/zh/form-upload.md) + - [form组件管理](/docs/zh/field-management.md) - [组件](/docs/zh/widgets/table.md) - [表格](/docs/zh/widgets/table.md) - [表单](/docs/zh/widgets/form.md) diff --git a/docs/zh/field-management.md b/docs/zh/field-management.md new file mode 100644 index 0000000000..ae798a4ce4 --- /dev/null +++ b/docs/zh/field-management.md @@ -0,0 +1,124 @@ +# 组件管理 + + +## 移除已有组件 + +form表单内置的`map`和`editor`组件通过cdn的方式引用了前端文件,如果网络方面有问题,可以通过下面的方式将它们移除 + +找到文件`app/Admin/bootstrap.php`,如果文件不存在,请更新`laravel-admin`,然后新建该文件 + +```php + +script = <<id}"), { + lineNumbers: true, + mode: "text/x-php", + extraKeys: { + "Tab": function(cm){ + cm.replaceSelection(" " , "end"); + } + } +}); + +EOT; + return parent::render(); + + } +} + +``` + +类中的静态资源也同样可以从外部引入,参考[Editor.php](https://github.com/z-song/laravel-admin/blob/1.3/src/Form/Field/Editor.php) + +创建视图`resources/views/admin/php-editor.blade.php`: + +```php + +
    + + + +
    + + @include('admin::form.error') + + +
    +
    + +``` + +最后找到文件`app/Admin/bootstrap.php`,如果文件不存在,请更新`laravel-admin`,然后新建该文件,添加下面代码: + +``` +php('code'); + +``` + +通过这种方式,可以添加任意你想要添加的form组件。 \ No newline at end of file From ddf382c3dbb0fea3a16f0c39e7640830f2d9b57a Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 21 Nov 2016 13:10:57 +0800 Subject: [PATCH 0131/2161] update docs [skip ci] --- docs/zh/README.md | 1 + docs/zh/field-management.md | 124 ++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 docs/zh/field-management.md diff --git a/docs/zh/README.md b/docs/zh/README.md index d33bba6f73..52aa8f790d 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -64,6 +64,7 @@ php artisan admin:install - [数据模型表格](/docs/zh/model-grid.md) - [数据模型表单](/docs/zh/model-form.md) - [图片/文件上传](/docs/zh/form-upload.md) + - [form组件管理](/docs/zh/field-management.md) - [组件](/docs/zh/widgets/table.md) - [表格](/docs/zh/widgets/table.md) - [表单](/docs/zh/widgets/form.md) diff --git a/docs/zh/field-management.md b/docs/zh/field-management.md new file mode 100644 index 0000000000..ae798a4ce4 --- /dev/null +++ b/docs/zh/field-management.md @@ -0,0 +1,124 @@ +# 组件管理 + + +## 移除已有组件 + +form表单内置的`map`和`editor`组件通过cdn的方式引用了前端文件,如果网络方面有问题,可以通过下面的方式将它们移除 + +找到文件`app/Admin/bootstrap.php`,如果文件不存在,请更新`laravel-admin`,然后新建该文件 + +```php + +script = <<id}"), { + lineNumbers: true, + mode: "text/x-php", + extraKeys: { + "Tab": function(cm){ + cm.replaceSelection(" " , "end"); + } + } +}); + +EOT; + return parent::render(); + + } +} + +``` + +类中的静态资源也同样可以从外部引入,参考[Editor.php](https://github.com/z-song/laravel-admin/blob/1.3/src/Form/Field/Editor.php) + +创建视图`resources/views/admin/php-editor.blade.php`: + +```php + +
    + + + +
    + + @include('admin::form.error') + + +
    +
    + +``` + +最后找到文件`app/Admin/bootstrap.php`,如果文件不存在,请更新`laravel-admin`,然后新建该文件,添加下面代码: + +``` +php('code'); + +``` + +通过这种方式,可以添加任意你想要添加的form组件。 \ No newline at end of file From 57fa62f5b04e1a2d129b71f6c881552325999b44 Mon Sep 17 00:00:00 2001 From: Song Date: Mon, 21 Nov 2016 13:36:16 +0800 Subject: [PATCH 0132/2161] Update field-management.md [skip ci] --- docs/zh/field-management.md | 68 ++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/docs/zh/field-management.md b/docs/zh/field-management.md index ae798a4ce4..617c131bd5 100644 --- a/docs/zh/field-management.md +++ b/docs/zh/field-management.md @@ -121,4 +121,70 @@ $form->php('code'); ``` -通过这种方式,可以添加任意你想要添加的form组件。 \ No newline at end of file +通过这种方式,可以添加任意你想要添加的form组件。 + +### 集成富文本编辑器wangEditor + +[wangEditor](http://www.wangeditor.com/)是一个优秀的国产的轻量级富文本编辑器,如果`laravel-admin`自带的基于`ckeditor`的编辑器组件使用上有问题,可以通过下面的步骤可以集成它,并覆盖掉`ckeditor`: + +先下载前端库文件[wangEditor](https://github.com/wangfupeng1988/wangEditor/releases),解压到目录`public/packages/wangEditor-2.1.22`。 + +然后新建组件类`app/Admin/Extensions/WangEditor.php`。 + +```php + +script = <<id}'); + editor.create(); + +EOT; + return parent::render(); + + } +} + +``` + +然后注册进`laravel-admin`,在`app/Admin/bootstrap.php`中添加以下代码: + +```php + +editor('content'); + +``` + +> 组件类中指定了`admin::form.editor`作为视图文件,视图文件路径在`vendor/encore/laravel-admin/views/form/editor.blade.php`,如果需要修改视图文件,可以将上述视图文件拷贝到`resources/views`目录下自行修改,然后在组件类`app/Admin/Extensions/WangEditor.php`的`$view`属性指定刚才修改的view即可。 From 12d5cffcd05e2fd855f12b6e9cf1a6591cd8d8e1 Mon Sep 17 00:00:00 2001 From: Song Date: Mon, 21 Nov 2016 13:40:20 +0800 Subject: [PATCH 0133/2161] Update field-management.md [skip ci] --- docs/zh/field-management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/field-management.md b/docs/zh/field-management.md index 617c131bd5..892f9f4828 100644 --- a/docs/zh/field-management.md +++ b/docs/zh/field-management.md @@ -81,7 +81,7 @@ EOT; ``` -类中的静态资源也同样可以从外部引入,参考[Editor.php](https://github.com/z-song/laravel-admin/blob/1.3/src/Form/Field/Editor.php) +>类中的静态资源也同样可以从外部引入,参考[Editor.php](https://github.com/z-song/laravel-admin/blob/1.3/src/Form/Field/Editor.php) 创建视图`resources/views/admin/php-editor.blade.php`: From c8393d4acdcfbb0a54a2dd3c6eefddc5bf798f16 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 21 Nov 2016 14:25:16 +0800 Subject: [PATCH 0134/2161] fix date range issue --- src/Form/Field/DateRange.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Form/Field/DateRange.php b/src/Form/Field/DateRange.php index c356278c48..8dfcc4b33c 100644 --- a/src/Form/Field/DateRange.php +++ b/src/Form/Field/DateRange.php @@ -31,12 +31,8 @@ public function __construct($column, $arguments) public function prepare($value) { - if ($value['start'] === '') { - $value['start'] = null; - } - - if ($value['end'] === '') { - $value['end'] = null; + if ($value === '') { + $value = null; } return $value; From 221dafc30d09e4e3903933706129dfcb1a5760f2 Mon Sep 17 00:00:00 2001 From: Song Date: Mon, 21 Nov 2016 14:31:44 +0800 Subject: [PATCH 0135/2161] Update field-management.md [skip ci] --- docs/zh/field-management.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/zh/field-management.md b/docs/zh/field-management.md index 892f9f4828..a428c2d5c1 100644 --- a/docs/zh/field-management.md +++ b/docs/zh/field-management.md @@ -1,5 +1,6 @@ # 组件管理 +> 目前暂时只支持larave 5.3 ## 移除已有组件 From 10b38d0aca282f7882729805377ea47d300d56e7 Mon Sep 17 00:00:00 2001 From: Song Date: Mon, 21 Nov 2016 14:32:10 +0800 Subject: [PATCH 0136/2161] Update field-management.md [skip ci] --- docs/zh/field-management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/field-management.md b/docs/zh/field-management.md index a428c2d5c1..5ffa750406 100644 --- a/docs/zh/field-management.md +++ b/docs/zh/field-management.md @@ -1,6 +1,6 @@ # 组件管理 -> 目前暂时只支持larave 5.3 +> 目前暂时只支持Larave 5.3 ## 移除已有组件 From f8fd3d038d2a9499627b1dea686ca40a502750d2 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 21 Nov 2016 16:02:25 +0800 Subject: [PATCH 0137/2161] fix issue #113 --- assets/AdminLTE/plugins/slimScroll/jquery.slimscroll.min.js | 1 + views/index.blade.php | 1 + 2 files changed, 2 insertions(+) create mode 100644 assets/AdminLTE/plugins/slimScroll/jquery.slimscroll.min.js diff --git a/assets/AdminLTE/plugins/slimScroll/jquery.slimscroll.min.js b/assets/AdminLTE/plugins/slimScroll/jquery.slimscroll.min.js new file mode 100644 index 0000000000..298aa1cd2b --- /dev/null +++ b/assets/AdminLTE/plugins/slimScroll/jquery.slimscroll.min.js @@ -0,0 +1 @@ +(function($){$.fn.extend({slimScroll:function(options){var defaults={width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:.4,alwaysVisible:false,disableFadeOut:false,railVisible:false,railColor:"#333",railOpacity:.2,railDraggable:true,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:false,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px"};var o=$.extend(defaults,options);this.each(function(){var isOverPanel,isOverBar,isDragg,queueHide,touchDif,barHeight,percentScroll,lastScroll,divS="
    ",minBarHeight=30,releaseScroll=false;var me=$(this);if(me.parent().hasClass(o.wrapperClass)){var offset=me.scrollTop();bar=me.parent().find("."+o.barClass);rail=me.parent().find("."+o.railClass);getBarHeight();if($.isPlainObject(options)){if("height"in options&&options.height=="auto"){me.parent().css("height","auto");me.css("height","auto");var height=me.parent().parent().height();me.parent().css("height",height);me.css("height",height)}if("scrollTo"in options){offset=parseInt(o.scrollTo)}else if("scrollBy"in options){offset+=parseInt(o.scrollBy)}else if("destroy"in options){bar.remove();rail.remove();me.unwrap();return}scrollContent(offset,false,true)}return}else if($.isPlainObject(options)){if("destroy"in options){return}}o.height=o.height=="auto"?me.parent().height():o.height;var wrapper=$(divS).addClass(o.wrapperClass).css({position:"relative",overflow:"hidden",width:o.width,height:o.height});me.css({overflow:"hidden",width:o.width,height:o.height,"-ms-touch-action":"none"});var rail=$(divS).addClass(o.railClass).css({width:o.size,height:"100%",position:"absolute",top:0,display:o.alwaysVisible&&o.railVisible?"block":"none","border-radius":o.railBorderRadius,background:o.railColor,opacity:o.railOpacity,zIndex:90});var bar=$(divS).addClass(o.barClass).css({background:o.color,width:o.size,position:"absolute",top:0,opacity:o.opacity,display:o.alwaysVisible?"block":"none","border-radius":o.borderRadius,BorderRadius:o.borderRadius,MozBorderRadius:o.borderRadius,WebkitBorderRadius:o.borderRadius,zIndex:99});var posCss=o.position=="right"?{right:o.distance}:{left:o.distance};rail.css(posCss);bar.css(posCss);me.wrap(wrapper);me.parent().append(bar);me.parent().append(rail);if(o.railDraggable){bar.bind("mousedown",function(e){var $doc=$(document);isDragg=true;t=parseFloat(bar.css("top"));pageY=e.pageY;$doc.bind("mousemove.slimscroll",function(e){currTop=t+e.pageY-pageY;bar.css("top",currTop);scrollContent(0,bar.position().top,false)});$doc.bind("mouseup.slimscroll",function(e){isDragg=false;hideBar();$doc.unbind(".slimscroll")});return false}).bind("selectstart.slimscroll",function(e){e.stopPropagation();e.preventDefault();return false})}rail.hover(function(){showBar()},function(){hideBar()});bar.hover(function(){isOverBar=true},function(){isOverBar=false});me.hover(function(){isOverPanel=true;showBar();hideBar()},function(){isOverPanel=false;hideBar()});if(window.navigator.msPointerEnabled){me.bind("MSPointerDown",function(e,b){if(e.originalEvent.targetTouches.length){touchDif=e.originalEvent.targetTouches[0].pageY}});me.bind("MSPointerMove",function(e){e.originalEvent.preventDefault();if(e.originalEvent.targetTouches.length){var diff=(touchDif-e.originalEvent.targetTouches[0].pageY)/o.touchScrollStep;scrollContent(diff,true);touchDif=e.originalEvent.targetTouches[0].pageY}})}else{me.bind("touchstart",function(e,b){if(e.originalEvent.touches.length){touchDif=e.originalEvent.touches[0].pageY}});me.bind("touchmove",function(e){if(!releaseScroll){e.originalEvent.preventDefault()}if(e.originalEvent.touches.length){var diff=(touchDif-e.originalEvent.touches[0].pageY)/o.touchScrollStep;scrollContent(diff,true);touchDif=e.originalEvent.touches[0].pageY}})}getBarHeight();if(o.start==="bottom"){bar.css({top:me.outerHeight()-bar.outerHeight()});scrollContent(0,true)}else if(o.start!=="top"){scrollContent($(o.start).position().top,null,true);if(!o.alwaysVisible){bar.hide()}}attachWheel();function _onWheel(e){if(!isOverPanel){return}var e=e||window.event;var delta=0;if(e.wheelDelta){delta=-e.wheelDelta/120}if(e.detail){delta=e.detail/3}var target=e.target||e.srcTarget||e.srcElement;if($(target).closest("."+o.wrapperClass).is(me.parent())){scrollContent(delta,true)}if(e.preventDefault&&!releaseScroll){e.preventDefault()}if(!releaseScroll){e.returnValue=false}}function scrollContent(y,isWheel,isJump){releaseScroll=false;var delta=y;var maxTop=me.outerHeight()-bar.outerHeight();if(isWheel){delta=parseInt(bar.css("top"))+y*parseInt(o.wheelStep)/100*bar.outerHeight();delta=Math.min(Math.max(delta,0),maxTop);delta=y>0?Math.ceil(delta):Math.floor(delta);bar.css({top:delta+"px"})}percentScroll=parseInt(bar.css("top"))/(me.outerHeight()-bar.outerHeight());delta=percentScroll*(me[0].scrollHeight-me.outerHeight());if(isJump){delta=y;var offsetTop=delta/me[0].scrollHeight*me.outerHeight();offsetTop=Math.min(Math.max(offsetTop,0),maxTop);bar.css({top:offsetTop+"px"})}me.scrollTop(delta);me.trigger("slimscrolling",~~delta);showBar();hideBar()}function attachWheel(){if(window.addEventListener){this.addEventListener("DOMMouseScroll",_onWheel,false);this.addEventListener("mousewheel",_onWheel,false)}else{document.attachEvent("onmousewheel",_onWheel)}}function getBarHeight(){barHeight=Math.max(me.outerHeight()/me[0].scrollHeight*me.outerHeight(),minBarHeight);bar.css({height:barHeight+"px"});var display=barHeight==me.outerHeight()?"none":"block";bar.css({display:display})}function showBar(){getBarHeight();clearTimeout(queueHide);if(percentScroll==~~percentScroll){releaseScroll=o.allowPageScroll;if(lastScroll!=percentScroll){var msg=~~percentScroll==0?"top":"bottom";me.trigger("slimscroll",msg)}}else{releaseScroll=false}lastScroll=percentScroll;if(barHeight>=me.outerHeight()){releaseScroll=true;return}bar.stop(true,true).fadeIn("fast");if(o.railVisible){rail.stop(true,true).fadeIn("fast")}}function hideBar(){if(!o.alwaysVisible){queueHide=setTimeout(function(){if(!(o.disableFadeOut&&isOverPanel)&&!isOverBar&&!isDragg){bar.fadeOut("slow");rail.fadeOut("slow")}},1e3)}}});return this}});$.fn.extend({slimscroll:$.fn.slimScroll})})(jQuery); \ No newline at end of file diff --git a/views/index.blade.php b/views/index.blade.php index 6adde38967..3f3820b0d1 100644 --- a/views/index.blade.php +++ b/views/index.blade.php @@ -23,6 +23,7 @@ + From 37c3d5a3096ea1d852c1e5afc85cbfa12c03af09 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 21 Nov 2016 16:30:45 +0800 Subject: [PATCH 0138/2161] remove markdown editor --- src/Form.php | 1 - src/Form/Field/Markdown.php | 9 --------- views/form/markdown.blade.php | 11 ----------- 3 files changed, 21 deletions(-) delete mode 100644 src/Form/Field/Markdown.php delete mode 100644 views/form/markdown.blade.php diff --git a/src/Form.php b/src/Form.php index 671df1fe0b..1f358bdad0 100644 --- a/src/Form.php +++ b/src/Form.php @@ -862,7 +862,6 @@ public static function registerBuiltinFields() 'ip' => \Encore\Admin\Form\Field\Ip::class, 'json' => \Encore\Admin\Form\Field\Json::class, 'map' => \Encore\Admin\Form\Field\Map::class, - 'markdown' => \Encore\Admin\Form\Field\Markdown::class, 'mobile' => \Encore\Admin\Form\Field\Mobile::class, 'month' => \Encore\Admin\Form\Field\Month::class, 'multipleSelect' => \Encore\Admin\Form\Field\MultipleSelect::class, diff --git a/src/Form/Field/Markdown.php b/src/Form/Field/Markdown.php deleted file mode 100644 index c2bb1ccba4..0000000000 --- a/src/Form/Field/Markdown.php +++ /dev/null @@ -1,9 +0,0 @@ -has($label) ?: 'has-error' !!}"> - - - -
    - - @include('admin::form.error') - - -
    -
    \ No newline at end of file From 503704840df6e36ab01f186bee85ad7963c4c1e8 Mon Sep 17 00:00:00 2001 From: Song Date: Mon, 21 Nov 2016 16:38:46 +0800 Subject: [PATCH 0139/2161] Update README.md [skip ci] --- docs/zh/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/zh/README.md b/docs/zh/README.md index 52aa8f790d..94b5b1d770 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -145,7 +145,6 @@ $router->resource('users', UserController::class); + [Laravel](https://laravel.com/) + [AdminLTE](https://almsaeedstudio.com/) -+ [Bootstrap Markdown](http://toopay.github.io/bootstrap-markdown/) + [Datetimepicker](http://eonasdan.github.io/bootstrap-datetimepicker/) + [CodeMirror](https://codemirror.net/) + [font-awesome](http://fontawesome.io) @@ -156,6 +155,8 @@ $router->resource('users', UserController::class); + [jquery-pjax](https://github.com/defunkt/jquery-pjax) + [Nestable](http://dbushell.github.io/Nestable/) + [noty](http://ned.im/noty/) ++ [X-editable](http://github.com/vitalets/x-editable) ++ [bootstrap-number-input](https://github.com/wpic/bootstrap-number-input) 交流 ------------ From 7f3ef39c9489d6709a12b009d85df2a215b39843 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 21 Nov 2016 16:40:25 +0800 Subject: [PATCH 0140/2161] remove bootstrap-markdown assets --- .../css/bootstrap-markdown.min.css | 1 - .../js/bootstrap-markdown.js | 1362 ----------------- .../locale/bootstrap-markdown.ar.js | 24 - .../locale/bootstrap-markdown.de.js | 27 - .../locale/bootstrap-markdown.es.js | 24 - .../locale/bootstrap-markdown.fr.js | 24 - .../locale/bootstrap-markdown.ja.js | 29 - .../locale/bootstrap-markdown.kr.js | 24 - .../locale/bootstrap-markdown.nb.js | 24 - .../locale/bootstrap-markdown.nl.js | 24 - .../locale/bootstrap-markdown.pl.js | 27 - .../locale/bootstrap-markdown.ru.js | 30 - .../locale/bootstrap-markdown.sl.js | 27 - .../locale/bootstrap-markdown.sv.js | 24 - .../locale/bootstrap-markdown.tr.js | 31 - .../locale/bootstrap-markdown.ua.js | 24 - .../locale/bootstrap-markdown.zh.js | 32 - 17 files changed, 1758 deletions(-) delete mode 100644 assets/bootstrap-markdown/css/bootstrap-markdown.min.css delete mode 100644 assets/bootstrap-markdown/js/bootstrap-markdown.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.ar.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.de.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.es.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.fr.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.ja.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.kr.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.nb.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.nl.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.pl.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.ru.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.sl.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.sv.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.tr.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.ua.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.zh.js diff --git a/assets/bootstrap-markdown/css/bootstrap-markdown.min.css b/assets/bootstrap-markdown/css/bootstrap-markdown.min.css deleted file mode 100644 index 388b2f5f64..0000000000 --- a/assets/bootstrap-markdown/css/bootstrap-markdown.min.css +++ /dev/null @@ -1 +0,0 @@ -.md-editor{display:block;border:1px solid #ddd}.md-editor .md-footer,.md-editor>.md-header{display:block;padding:6px 4px;background:#f5f5f5}.md-editor>.md-header{margin:0}.md-editor>.md-preview{background:#fff;border-top:1px dashed #ddd;border-bottom:1px dashed #ddd;min-height:10px;overflow:auto}.md-editor>textarea{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:14px;outline:0;margin:0;display:block;padding:0;width:100%;border:0;border-top:1px dashed #ddd;border-bottom:1px dashed #ddd;border-radius:0;box-shadow:none;background:#eee}.md-editor>textarea:focus{box-shadow:none;background:#fff}.md-editor.active{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.md-editor .md-controls{float:right;padding:3px}.md-editor .md-controls .md-control{right:5px;color:#bebebe;padding:3px 3px 3px 10px}.md-editor .md-controls .md-control:hover{color:#333}.md-editor.md-fullscreen-mode{width:100%;height:100%;position:fixed;top:0;left:0;z-index:99999;padding:60px 30px 15px;background:#fff!important;border:0!important}.md-editor.md-fullscreen-mode .md-footer{display:none}.md-editor.md-fullscreen-mode .md-input,.md-editor.md-fullscreen-mode .md-preview{margin:0 auto!important;height:100%!important;font-size:20px!important;padding:20px!important;color:#999;line-height:1.6em!important;resize:none!important;box-shadow:none!important;background:#fff!important;border:0!important}.md-editor.md-fullscreen-mode .md-preview{color:#333;overflow:auto}.md-editor.md-fullscreen-mode .md-input:focus,.md-editor.md-fullscreen-mode .md-input:hover{color:#333;background:#fff!important}.md-editor.md-fullscreen-mode .md-header{background:0 0;text-align:center;position:fixed;width:100%;top:20px}.md-editor.md-fullscreen-mode .btn-group{float:none}.md-editor.md-fullscreen-mode .btn{border:0;background:0 0;color:#b3b3b3}.md-editor.md-fullscreen-mode .btn.active,.md-editor.md-fullscreen-mode .btn:active,.md-editor.md-fullscreen-mode .btn:focus,.md-editor.md-fullscreen-mode .btn:hover{box-shadow:none;color:#333}.md-editor.md-fullscreen-mode .md-fullscreen-controls{position:absolute;top:20px;right:20px;text-align:right;z-index:1002;display:block}.md-editor.md-fullscreen-mode .md-fullscreen-controls a{color:#b3b3b3;clear:right;margin:10px;width:30px;height:30px;text-align:center}.md-editor.md-fullscreen-mode .md-fullscreen-controls a:hover{color:#333;text-decoration:none}.md-editor.md-fullscreen-mode .md-editor{height:100%!important;position:relative}.md-editor .md-fullscreen-controls{display:none}.md-nooverflow{overflow:hidden;position:fixed;width:100%} \ No newline at end of file diff --git a/assets/bootstrap-markdown/js/bootstrap-markdown.js b/assets/bootstrap-markdown/js/bootstrap-markdown.js deleted file mode 100644 index bbe0706f24..0000000000 --- a/assets/bootstrap-markdown/js/bootstrap-markdown.js +++ /dev/null @@ -1,1362 +0,0 @@ -/* =================================================== - * bootstrap-markdown.js v2.9.0 - * http://github.com/toopay/bootstrap-markdown - * =================================================== - * Copyright 2013-2015 Taufan Aditya - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - -!function ($) { - - "use strict"; // jshint ;_; - - /* MARKDOWN CLASS DEFINITION - * ========================== */ - - var Markdown = function (element, options) { - // @TODO : remove this BC on next major release - // @see : https://github.com/toopay/bootstrap-markdown/issues/109 - var opts = ['autofocus', 'savable', 'hideable', 'width', - 'height', 'resize', 'iconlibrary', 'language', - 'footer', 'fullscreen', 'hiddenButtons', 'disabledButtons']; - $.each(opts,function(_, opt){ - if (typeof $(element).data(opt) !== 'undefined') { - options = typeof options == 'object' ? options : {} - options[opt] = $(element).data(opt) - } - }); - // End BC - - // Class Properties - this.$ns = 'bootstrap-markdown'; - this.$element = $(element); - this.$editable = {el:null, type:null,attrKeys:[], attrValues:[], content:null}; - this.$options = $.extend(true, {}, $.fn.markdown.defaults, options, this.$element.data('options')); - this.$oldContent = null; - this.$isPreview = false; - this.$isFullscreen = false; - this.$editor = null; - this.$textarea = null; - this.$handler = []; - this.$callback = []; - this.$nextTab = []; - - this.showEditor(); - }; - - Markdown.prototype = { - - constructor: Markdown - - , __alterButtons: function(name,alter) { - var handler = this.$handler, isAll = (name == 'all'),that = this; - - $.each(handler,function(k,v) { - var halt = true; - if (isAll) { - halt = false; - } else { - halt = v.indexOf(name) < 0; - } - - if (halt === false) { - alter(that.$editor.find('button[data-handler="'+v+'"]')); - } - }); - } - - , __buildButtons: function(buttonsArray, container) { - var i, - ns = this.$ns, - handler = this.$handler, - callback = this.$callback; - - for (i=0;i', { - 'class': 'btn-group' - }); - - for (z=0;z'); - buttonContainer.text(' ' + this.__localize(btnText)).addClass('btn-default btn-sm').addClass(btnClass); - if(btnClass.match(/btn\-(primary|success|info|warning|danger|link)/)){ - buttonContainer.removeClass('btn-default'); - } - buttonContainer.attr({ - 'type': 'button', - 'title': this.__localize(button.title) + hotkeyCaption, - 'tabindex': tabIndex, - 'data-provider': ns, - 'data-handler': buttonHandler, - 'data-hotkey': hotkey - }); - if (button.toggle === true){ - buttonContainer.attr('data-toggle', 'button'); - } - buttonIconContainer = $(''); - buttonIconContainer.addClass(buttonIcon); - buttonIconContainer.prependTo(buttonContainer); - - // Attach the button object - btnGroupContainer.append(buttonContainer); - - // Register handler and callback - handler.push(buttonHandler); - callback.push(button.callback); - } - - // Attach the button group into container dom - container.append(btnGroupContainer); - } - } - - return container; - } - , __setListener: function() { - // Set size and resizable Properties - var hasRows = typeof this.$textarea.attr('rows') !== 'undefined', - maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5', - rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows; - - this.$textarea.attr('rows',rowsVal); - if (this.$options.resize) { - this.$textarea.css('resize',this.$options.resize); - } - - this.$textarea - .on('focus', $.proxy(this.focus, this)) - .on('keypress', $.proxy(this.keypress, this)) - .on('keyup', $.proxy(this.keyup, this)) - .on('change', $.proxy(this.change, this)) - .on('select', $.proxy(this.select, this)); - - if (this.eventSupported('keydown')) { - this.$textarea.on('keydown', $.proxy(this.keydown, this)); - } - - // Re-attach markdown data - this.$textarea.data('markdown',this); - } - - , __handle: function(e) { - var target = $(e.currentTarget), - handler = this.$handler, - callback = this.$callback, - handlerName = target.attr('data-handler'), - callbackIndex = handler.indexOf(handlerName), - callbackHandler = callback[callbackIndex]; - - // Trigger the focusin - $(e.currentTarget).focus(); - - callbackHandler(this); - - // Trigger onChange for each button handle - this.change(this); - - // Unless it was the save handler, - // focusin the textarea - if (handlerName.indexOf('cmdSave') < 0) { - this.$textarea.focus(); - } - - e.preventDefault(); - } - - , __localize: function(string) { - var messages = $.fn.markdown.messages, - language = this.$options.language; - if ( - typeof messages !== 'undefined' && - typeof messages[language] !== 'undefined' && - typeof messages[language][string] !== 'undefined' - ) { - return messages[language][string]; - } - return string; - } - - , __getIcon: function(src) { - return typeof src == 'object' ? src[this.$options.iconlibrary] : src; - } - - , setFullscreen: function(mode) { - var $editor = this.$editor, - $textarea = this.$textarea; - - if (mode === true) { - $editor.addClass('md-fullscreen-mode'); - $('body').addClass('md-nooverflow'); - this.$options.onFullscreen(this); - } else { - $editor.removeClass('md-fullscreen-mode'); - $('body').removeClass('md-nooverflow'); - - if (this.$isPreview == true) this.hidePreview().showPreview() - } - - this.$isFullscreen = mode; - $textarea.focus(); - } - - , showEditor: function() { - var instance = this, - textarea, - ns = this.$ns, - container = this.$element, - originalHeigth = container.css('height'), - originalWidth = container.css('width'), - editable = this.$editable, - handler = this.$handler, - callback = this.$callback, - options = this.$options, - editor = $( '
    ', { - 'class': 'md-editor', - click: function() { - instance.focus(); - } - }); - - // Prepare the editor - if (this.$editor === null) { - // Create the panel - var editorHeader = $('
    ', { - 'class': 'md-header btn-toolbar' - }); - - // Merge the main & additional button groups together - var allBtnGroups = []; - if (options.buttons.length > 0) allBtnGroups = allBtnGroups.concat(options.buttons[0]); - if (options.additionalButtons.length > 0) allBtnGroups = allBtnGroups.concat(options.additionalButtons[0]); - - // Reduce and/or reorder the button groups - if (options.reorderButtonGroups.length > 0) { - allBtnGroups = allBtnGroups - .filter(function(btnGroup) { - return options.reorderButtonGroups.indexOf(btnGroup.name) > -1; - }) - .sort(function(a, b) { - if (options.reorderButtonGroups.indexOf(a.name) < options.reorderButtonGroups.indexOf(b.name)) return -1; - if (options.reorderButtonGroups.indexOf(a.name) > options.reorderButtonGroups.indexOf(b.name)) return 1; - return 0; - }); - } - - // Build the buttons - if (allBtnGroups.length > 0) { - editorHeader = this.__buildButtons([allBtnGroups], editorHeader); - } - - if (options.fullscreen.enable) { - editorHeader.append('
    ').on('click', '.md-control-fullscreen', function(e) { - e.preventDefault(); - instance.setFullscreen(true); - }); - } - - editor.append(editorHeader); - - // Wrap the textarea - if (container.is('textarea')) { - container.before(editor); - textarea = container; - textarea.addClass('md-input'); - editor.append(textarea); - } else { - var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(), - currentContent = $.trim(rawContent); - - // This is some arbitrary content that could be edited - textarea = $(' +
    \ No newline at end of file diff --git a/views/form/color.blade.php b/views/form/color.blade.php index eb02a5f64e..4c677729ce 100644 --- a/views/form/color.blade.php +++ b/views/form/color.blade.php @@ -8,7 +8,7 @@
    - +
    \ No newline at end of file diff --git a/views/form/currency.blade.php b/views/form/currency.blade.php index 2058ca50a3..2ede76a9a6 100644 --- a/views/form/currency.blade.php +++ b/views/form/currency.blade.php @@ -8,7 +8,7 @@
    {{$symbol}} - +
    \ No newline at end of file diff --git a/views/form/date.blade.php b/views/form/date.blade.php index e200727c41..7788554978 100644 --- a/views/form/date.blade.php +++ b/views/form/date.blade.php @@ -8,7 +8,7 @@
    - +
    \ No newline at end of file diff --git a/views/form/daterange.blade.php b/views/form/daterange.blade.php index 93e61f5219..6c3bc3212f 100644 --- a/views/form/daterange.blade.php +++ b/views/form/daterange.blade.php @@ -10,14 +10,14 @@
    - +
    - +
    diff --git a/views/form/datetime.blade.php b/views/form/datetime.blade.php index 49b0860972..12facf133d 100644 --- a/views/form/datetime.blade.php +++ b/views/form/datetime.blade.php @@ -8,7 +8,7 @@
    - +
    \ No newline at end of file diff --git a/views/form/datetimerange.blade.php b/views/form/datetimerange.blade.php index efaadaf9d2..7090500180 100644 --- a/views/form/datetimerange.blade.php +++ b/views/form/datetimerange.blade.php @@ -10,14 +10,14 @@
    - +
    - +
    diff --git a/views/form/decimal.blade.php b/views/form/decimal.blade.php index 729cb98844..1d63b49eaa 100644 --- a/views/form/decimal.blade.php +++ b/views/form/decimal.blade.php @@ -10,7 +10,7 @@
    - + \ No newline at end of file diff --git a/views/form/editor.blade.php b/views/form/editor.blade.php index 2bff543f72..bf5208b82d 100644 --- a/views/form/editor.blade.php +++ b/views/form/editor.blade.php @@ -6,6 +6,6 @@ @include('admin::form.error') - + \ No newline at end of file diff --git a/views/form/email.blade.php b/views/form/email.blade.php index 562f06b968..0875018e7c 100644 --- a/views/form/email.blade.php +++ b/views/form/email.blade.php @@ -8,7 +8,7 @@
    - +
    \ No newline at end of file diff --git a/views/form/file.blade.php b/views/form/file.blade.php index b895a2b7b3..2ea638af70 100644 --- a/views/form/file.blade.php +++ b/views/form/file.blade.php @@ -5,7 +5,7 @@
    @include('admin::form.error') - +
    \ No newline at end of file diff --git a/views/form/hidden.blade.php b/views/form/hidden.blade.php index 42bcc3ee45..3a54653fe2 100644 --- a/views/form/hidden.blade.php +++ b/views/form/hidden.blade.php @@ -1 +1 @@ - + diff --git a/views/form/id.blade.php b/views/form/id.blade.php index 7f6071f2f9..e9ab1fbfad 100644 --- a/views/form/id.blade.php +++ b/views/form/id.blade.php @@ -2,6 +2,6 @@
    - +
    \ No newline at end of file diff --git a/views/form/image.blade.php b/views/form/image.blade.php index b895a2b7b3..2ea638af70 100644 --- a/views/form/image.blade.php +++ b/views/form/image.blade.php @@ -5,7 +5,7 @@
    @include('admin::form.error') - +
    \ No newline at end of file diff --git a/views/form/ip.blade.php b/views/form/ip.blade.php index fb755767ee..dbda44e2a4 100644 --- a/views/form/ip.blade.php +++ b/views/form/ip.blade.php @@ -10,7 +10,7 @@
    - + \ No newline at end of file diff --git a/views/form/json.blade.php b/views/form/json.blade.php index 7a7ccd0bc2..017767e08c 100644 --- a/views/form/json.blade.php +++ b/views/form/json.blade.php @@ -6,6 +6,6 @@ @include('admin::form.error') - + \ No newline at end of file diff --git a/views/form/map.blade.php b/views/form/map.blade.php index e6783a2734..1bcc1d36ee 100644 --- a/views/form/map.blade.php +++ b/views/form/map.blade.php @@ -7,7 +7,7 @@ @include('admin::form.error')
    - - + + \ No newline at end of file diff --git a/views/form/markdown.blade.php b/views/form/markdown.blade.php index fb53b2d335..fce764ff06 100644 --- a/views/form/markdown.blade.php +++ b/views/form/markdown.blade.php @@ -6,6 +6,6 @@ @include('admin::form.error') - + \ No newline at end of file diff --git a/views/form/mobile.blade.php b/views/form/mobile.blade.php index c2333fee56..90ed3ba762 100644 --- a/views/form/mobile.blade.php +++ b/views/form/mobile.blade.php @@ -10,7 +10,7 @@
    - + \ No newline at end of file diff --git a/views/form/multipleselect.blade.php b/views/form/multipleselect.blade.php index 19eca95912..42270409cc 100644 --- a/views/form/multipleselect.blade.php +++ b/views/form/multipleselect.blade.php @@ -6,11 +6,11 @@ @include('admin::form.error') - @foreach($options as $select => $option) @endforeach - + \ No newline at end of file diff --git a/views/form/number.blade.php b/views/form/number.blade.php index 03c8d16714..493aacaa41 100644 --- a/views/form/number.blade.php +++ b/views/form/number.blade.php @@ -7,7 +7,7 @@ @include('admin::form.error')
    - +
    \ No newline at end of file diff --git a/views/form/password.blade.php b/views/form/password.blade.php index 334b6a5fc5..336fc198ff 100644 --- a/views/form/password.blade.php +++ b/views/form/password.blade.php @@ -10,7 +10,7 @@
    - + \ No newline at end of file diff --git a/views/form/radio.blade.php b/views/form/radio.blade.php index fb7509e6f5..60699a53eb 100644 --- a/views/form/radio.blade.php +++ b/views/form/radio.blade.php @@ -7,7 +7,7 @@ @include('admin::form.error') @foreach($values as $option => $label) -  {{$label}}   +  {{$label}}   @endforeach \ No newline at end of file diff --git a/views/form/rate.blade.php b/views/form/rate.blade.php index 246c29ac61..4b0c4ad0cb 100644 --- a/views/form/rate.blade.php +++ b/views/form/rate.blade.php @@ -7,7 +7,7 @@ @include('admin::form.error')
    - + %
    diff --git a/views/form/select.blade.php b/views/form/select.blade.php index b434bb9e53..feab53a38d 100644 --- a/views/form/select.blade.php +++ b/views/form/select.blade.php @@ -6,7 +6,7 @@ @include('admin::form.error') - @foreach($options as $select => $option) @endforeach diff --git a/views/form/slider.blade.php b/views/form/slider.blade.php index 789e9eac75..889f1c637e 100644 --- a/views/form/slider.blade.php +++ b/views/form/slider.blade.php @@ -6,6 +6,6 @@ @include('admin::form.error') - + \ No newline at end of file diff --git a/views/form/switchfield.blade.php b/views/form/switchfield.blade.php index eef2ef7b79..7868322c9b 100644 --- a/views/form/switchfield.blade.php +++ b/views/form/switchfield.blade.php @@ -6,8 +6,8 @@ @include('admin::form.error') - - + + \ No newline at end of file diff --git a/views/form/text.blade.php b/views/form/text.blade.php index a5f28870e2..6603406a30 100644 --- a/views/form/text.blade.php +++ b/views/form/text.blade.php @@ -8,7 +8,7 @@
    - +
    \ No newline at end of file diff --git a/views/form/textarea.blade.php b/views/form/textarea.blade.php index b0d359aab2..017767e08c 100644 --- a/views/form/textarea.blade.php +++ b/views/form/textarea.blade.php @@ -6,6 +6,6 @@ @include('admin::form.error') - + \ No newline at end of file diff --git a/views/form/time.blade.php b/views/form/time.blade.php index 549cc5a1f8..09f9725111 100644 --- a/views/form/time.blade.php +++ b/views/form/time.blade.php @@ -8,7 +8,7 @@
    - +
    diff --git a/views/form/timerange.blade.php b/views/form/timerange.blade.php index 93e61f5219..6c3bc3212f 100644 --- a/views/form/timerange.blade.php +++ b/views/form/timerange.blade.php @@ -10,14 +10,14 @@
    - +
    - +
    diff --git a/views/form/url.blade.php b/views/form/url.blade.php index e90e5ca280..c6361aa4d3 100644 --- a/views/form/url.blade.php +++ b/views/form/url.blade.php @@ -8,7 +8,7 @@
    - +
    \ No newline at end of file From e8ca763e79ffe6f89b541637fadfe1bb16c505bd Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 9 Nov 2016 11:04:10 +0800 Subject: [PATCH 0143/2161] add comments & remove unused method --- src/Grid.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Grid.php b/src/Grid.php index 25c1cdc06c..8806240d4c 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -246,6 +246,13 @@ protected function addColumn($column = '', $label = '') return $this->columns[] = $column; } + /** + * Add a blank column. + * + * @param $label + * + * @return Column + */ public function blank($label) { return $this->addColumn('blank', $label); @@ -535,15 +542,6 @@ public function resource($path = null) return app('router')->current()->getPath(); } - public function pathOfCreate() - { - $path = $query = ''; - - extract(parse_url(https://github.com/$this-%3Eresource())); - - return '/'.trim($path, '/').'/create'.$query; - } - /** * Add variables to grid view. * From b787a8536a124c51ab95285e38a765e48e66c9ba Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 14 Nov 2016 18:22:24 +0800 Subject: [PATCH 0144/2161] fix google map api key missing problem --- views/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/index.blade.php b/views/index.blade.php index 0715fac3cf..2a6c61453b 100644 --- a/views/index.blade.php +++ b/views/index.blade.php @@ -86,7 +86,7 @@ @if(config('app.locale') == 'zh_CN') @else - + @endif @else - + @endif + From 56426e60bbfefdb6868812ff6067d81c1afa6351 Mon Sep 17 00:00:00 2001 From: z-song Date: Sun, 20 Nov 2016 23:11:53 +0800 Subject: [PATCH 0164/2161] Add comments for config. [skip ci] --- config/admin.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/config/admin.php b/config/admin.php index e633410e71..cd1ddc8407 100644 --- a/config/admin.php +++ b/config/admin.php @@ -2,20 +2,38 @@ return [ + /** + * Laravel-admin name. + */ 'name' => 'Laravel-admin', + /** + * Laravel-admin url prefix. + */ 'prefix' => 'admin', + /** + * Laravel-admin install directory. + */ 'directory' => app_path('Admin'), + /** + * Laravel-admin title. + */ 'title' => 'Admin', + /** + * Laravel-admin auth setting. + */ 'auth' => [ 'driver' => 'session', 'provider' => '', 'model' => Encore\Admin\Auth\Database\Administrator::class, ], + /** + * Laravel-admin upload setting. + */ 'upload' => [ 'disk' => 'admin', @@ -28,6 +46,9 @@ 'host' => 'http://localhost:8000/upload/', ], + /** + * Laravel-admin database setting. + */ 'database' => [ 'users_table' => 'admin_users', 'users_model' => Encore\Admin\Auth\Database\Administrator::class, From 6ea48b1bd98bdaa426c5e0539553215407206a93 Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 18 Nov 2016 14:48:19 +0800 Subject: [PATCH 0165/2161] remove empty options submit from multiple select. --- src/Form.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Form.php b/src/Form.php index e03ad9ec9d..f17129fb5c 100644 --- a/src/Form.php +++ b/src/Form.php @@ -708,7 +708,14 @@ protected function validate($input) continue; } - $data[$field->label()] = array_get($input, $columns); + $value = array_get($input, $columns); + + // remove empty options from multiple select. + if ($field instanceof Field\MultipleSelect) { + $value = array_filter($value); + } + + $data[$field->label()] = $value; $rules[$field->label()] = $rule; } From 2fb70e18631445e6b863c027bf296c459122d223 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 21 Nov 2016 16:30:45 +0800 Subject: [PATCH 0166/2161] remove markdown editor --- src/Form/Field/Markdown.php | 9 --------- views/form/markdown.blade.php | 11 ----------- 2 files changed, 20 deletions(-) delete mode 100644 src/Form/Field/Markdown.php delete mode 100644 views/form/markdown.blade.php diff --git a/src/Form/Field/Markdown.php b/src/Form/Field/Markdown.php deleted file mode 100644 index c2bb1ccba4..0000000000 --- a/src/Form/Field/Markdown.php +++ /dev/null @@ -1,9 +0,0 @@ -has($label) ?: 'has-error' !!}"> - - - -
    - - @include('admin::form.error') - - -
    - \ No newline at end of file From c65b23107499d2c9a700d50d86ca75fbf35bdc48 Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 21 Nov 2016 16:40:25 +0800 Subject: [PATCH 0167/2161] remove bootstrap-markdown assets --- .../css/bootstrap-markdown.min.css | 1 - .../js/bootstrap-markdown.js | 1362 ----------------- .../locale/bootstrap-markdown.ar.js | 24 - .../locale/bootstrap-markdown.de.js | 27 - .../locale/bootstrap-markdown.es.js | 24 - .../locale/bootstrap-markdown.fr.js | 24 - .../locale/bootstrap-markdown.ja.js | 29 - .../locale/bootstrap-markdown.kr.js | 24 - .../locale/bootstrap-markdown.nb.js | 24 - .../locale/bootstrap-markdown.nl.js | 24 - .../locale/bootstrap-markdown.pl.js | 27 - .../locale/bootstrap-markdown.ru.js | 30 - .../locale/bootstrap-markdown.sl.js | 27 - .../locale/bootstrap-markdown.sv.js | 24 - .../locale/bootstrap-markdown.tr.js | 31 - .../locale/bootstrap-markdown.ua.js | 24 - .../locale/bootstrap-markdown.zh.js | 32 - 17 files changed, 1758 deletions(-) delete mode 100644 assets/bootstrap-markdown/css/bootstrap-markdown.min.css delete mode 100644 assets/bootstrap-markdown/js/bootstrap-markdown.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.ar.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.de.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.es.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.fr.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.ja.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.kr.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.nb.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.nl.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.pl.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.ru.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.sl.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.sv.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.tr.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.ua.js delete mode 100644 assets/bootstrap-markdown/locale/bootstrap-markdown.zh.js diff --git a/assets/bootstrap-markdown/css/bootstrap-markdown.min.css b/assets/bootstrap-markdown/css/bootstrap-markdown.min.css deleted file mode 100644 index 388b2f5f64..0000000000 --- a/assets/bootstrap-markdown/css/bootstrap-markdown.min.css +++ /dev/null @@ -1 +0,0 @@ -.md-editor{display:block;border:1px solid #ddd}.md-editor .md-footer,.md-editor>.md-header{display:block;padding:6px 4px;background:#f5f5f5}.md-editor>.md-header{margin:0}.md-editor>.md-preview{background:#fff;border-top:1px dashed #ddd;border-bottom:1px dashed #ddd;min-height:10px;overflow:auto}.md-editor>textarea{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:14px;outline:0;margin:0;display:block;padding:0;width:100%;border:0;border-top:1px dashed #ddd;border-bottom:1px dashed #ddd;border-radius:0;box-shadow:none;background:#eee}.md-editor>textarea:focus{box-shadow:none;background:#fff}.md-editor.active{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.md-editor .md-controls{float:right;padding:3px}.md-editor .md-controls .md-control{right:5px;color:#bebebe;padding:3px 3px 3px 10px}.md-editor .md-controls .md-control:hover{color:#333}.md-editor.md-fullscreen-mode{width:100%;height:100%;position:fixed;top:0;left:0;z-index:99999;padding:60px 30px 15px;background:#fff!important;border:0!important}.md-editor.md-fullscreen-mode .md-footer{display:none}.md-editor.md-fullscreen-mode .md-input,.md-editor.md-fullscreen-mode .md-preview{margin:0 auto!important;height:100%!important;font-size:20px!important;padding:20px!important;color:#999;line-height:1.6em!important;resize:none!important;box-shadow:none!important;background:#fff!important;border:0!important}.md-editor.md-fullscreen-mode .md-preview{color:#333;overflow:auto}.md-editor.md-fullscreen-mode .md-input:focus,.md-editor.md-fullscreen-mode .md-input:hover{color:#333;background:#fff!important}.md-editor.md-fullscreen-mode .md-header{background:0 0;text-align:center;position:fixed;width:100%;top:20px}.md-editor.md-fullscreen-mode .btn-group{float:none}.md-editor.md-fullscreen-mode .btn{border:0;background:0 0;color:#b3b3b3}.md-editor.md-fullscreen-mode .btn.active,.md-editor.md-fullscreen-mode .btn:active,.md-editor.md-fullscreen-mode .btn:focus,.md-editor.md-fullscreen-mode .btn:hover{box-shadow:none;color:#333}.md-editor.md-fullscreen-mode .md-fullscreen-controls{position:absolute;top:20px;right:20px;text-align:right;z-index:1002;display:block}.md-editor.md-fullscreen-mode .md-fullscreen-controls a{color:#b3b3b3;clear:right;margin:10px;width:30px;height:30px;text-align:center}.md-editor.md-fullscreen-mode .md-fullscreen-controls a:hover{color:#333;text-decoration:none}.md-editor.md-fullscreen-mode .md-editor{height:100%!important;position:relative}.md-editor .md-fullscreen-controls{display:none}.md-nooverflow{overflow:hidden;position:fixed;width:100%} \ No newline at end of file diff --git a/assets/bootstrap-markdown/js/bootstrap-markdown.js b/assets/bootstrap-markdown/js/bootstrap-markdown.js deleted file mode 100644 index bbe0706f24..0000000000 --- a/assets/bootstrap-markdown/js/bootstrap-markdown.js +++ /dev/null @@ -1,1362 +0,0 @@ -/* =================================================== - * bootstrap-markdown.js v2.9.0 - * http://github.com/toopay/bootstrap-markdown - * =================================================== - * Copyright 2013-2015 Taufan Aditya - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - -!function ($) { - - "use strict"; // jshint ;_; - - /* MARKDOWN CLASS DEFINITION - * ========================== */ - - var Markdown = function (element, options) { - // @TODO : remove this BC on next major release - // @see : https://github.com/toopay/bootstrap-markdown/issues/109 - var opts = ['autofocus', 'savable', 'hideable', 'width', - 'height', 'resize', 'iconlibrary', 'language', - 'footer', 'fullscreen', 'hiddenButtons', 'disabledButtons']; - $.each(opts,function(_, opt){ - if (typeof $(element).data(opt) !== 'undefined') { - options = typeof options == 'object' ? options : {} - options[opt] = $(element).data(opt) - } - }); - // End BC - - // Class Properties - this.$ns = 'bootstrap-markdown'; - this.$element = $(element); - this.$editable = {el:null, type:null,attrKeys:[], attrValues:[], content:null}; - this.$options = $.extend(true, {}, $.fn.markdown.defaults, options, this.$element.data('options')); - this.$oldContent = null; - this.$isPreview = false; - this.$isFullscreen = false; - this.$editor = null; - this.$textarea = null; - this.$handler = []; - this.$callback = []; - this.$nextTab = []; - - this.showEditor(); - }; - - Markdown.prototype = { - - constructor: Markdown - - , __alterButtons: function(name,alter) { - var handler = this.$handler, isAll = (name == 'all'),that = this; - - $.each(handler,function(k,v) { - var halt = true; - if (isAll) { - halt = false; - } else { - halt = v.indexOf(name) < 0; - } - - if (halt === false) { - alter(that.$editor.find('button[data-handler="'+v+'"]')); - } - }); - } - - , __buildButtons: function(buttonsArray, container) { - var i, - ns = this.$ns, - handler = this.$handler, - callback = this.$callback; - - for (i=0;i', { - 'class': 'btn-group' - }); - - for (z=0;z'); - buttonContainer.text(' ' + this.__localize(btnText)).addClass('btn-default btn-sm').addClass(btnClass); - if(btnClass.match(/btn\-(primary|success|info|warning|danger|link)/)){ - buttonContainer.removeClass('btn-default'); - } - buttonContainer.attr({ - 'type': 'button', - 'title': this.__localize(button.title) + hotkeyCaption, - 'tabindex': tabIndex, - 'data-provider': ns, - 'data-handler': buttonHandler, - 'data-hotkey': hotkey - }); - if (button.toggle === true){ - buttonContainer.attr('data-toggle', 'button'); - } - buttonIconContainer = $(''); - buttonIconContainer.addClass(buttonIcon); - buttonIconContainer.prependTo(buttonContainer); - - // Attach the button object - btnGroupContainer.append(buttonContainer); - - // Register handler and callback - handler.push(buttonHandler); - callback.push(button.callback); - } - - // Attach the button group into container dom - container.append(btnGroupContainer); - } - } - - return container; - } - , __setListener: function() { - // Set size and resizable Properties - var hasRows = typeof this.$textarea.attr('rows') !== 'undefined', - maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5', - rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows; - - this.$textarea.attr('rows',rowsVal); - if (this.$options.resize) { - this.$textarea.css('resize',this.$options.resize); - } - - this.$textarea - .on('focus', $.proxy(this.focus, this)) - .on('keypress', $.proxy(this.keypress, this)) - .on('keyup', $.proxy(this.keyup, this)) - .on('change', $.proxy(this.change, this)) - .on('select', $.proxy(this.select, this)); - - if (this.eventSupported('keydown')) { - this.$textarea.on('keydown', $.proxy(this.keydown, this)); - } - - // Re-attach markdown data - this.$textarea.data('markdown',this); - } - - , __handle: function(e) { - var target = $(e.currentTarget), - handler = this.$handler, - callback = this.$callback, - handlerName = target.attr('data-handler'), - callbackIndex = handler.indexOf(handlerName), - callbackHandler = callback[callbackIndex]; - - // Trigger the focusin - $(e.currentTarget).focus(); - - callbackHandler(this); - - // Trigger onChange for each button handle - this.change(this); - - // Unless it was the save handler, - // focusin the textarea - if (handlerName.indexOf('cmdSave') < 0) { - this.$textarea.focus(); - } - - e.preventDefault(); - } - - , __localize: function(string) { - var messages = $.fn.markdown.messages, - language = this.$options.language; - if ( - typeof messages !== 'undefined' && - typeof messages[language] !== 'undefined' && - typeof messages[language][string] !== 'undefined' - ) { - return messages[language][string]; - } - return string; - } - - , __getIcon: function(src) { - return typeof src == 'object' ? src[this.$options.iconlibrary] : src; - } - - , setFullscreen: function(mode) { - var $editor = this.$editor, - $textarea = this.$textarea; - - if (mode === true) { - $editor.addClass('md-fullscreen-mode'); - $('body').addClass('md-nooverflow'); - this.$options.onFullscreen(this); - } else { - $editor.removeClass('md-fullscreen-mode'); - $('body').removeClass('md-nooverflow'); - - if (this.$isPreview == true) this.hidePreview().showPreview() - } - - this.$isFullscreen = mode; - $textarea.focus(); - } - - , showEditor: function() { - var instance = this, - textarea, - ns = this.$ns, - container = this.$element, - originalHeigth = container.css('height'), - originalWidth = container.css('width'), - editable = this.$editable, - handler = this.$handler, - callback = this.$callback, - options = this.$options, - editor = $( '
    ', { - 'class': 'md-editor', - click: function() { - instance.focus(); - } - }); - - // Prepare the editor - if (this.$editor === null) { - // Create the panel - var editorHeader = $('
    ', { - 'class': 'md-header btn-toolbar' - }); - - // Merge the main & additional button groups together - var allBtnGroups = []; - if (options.buttons.length > 0) allBtnGroups = allBtnGroups.concat(options.buttons[0]); - if (options.additionalButtons.length > 0) allBtnGroups = allBtnGroups.concat(options.additionalButtons[0]); - - // Reduce and/or reorder the button groups - if (options.reorderButtonGroups.length > 0) { - allBtnGroups = allBtnGroups - .filter(function(btnGroup) { - return options.reorderButtonGroups.indexOf(btnGroup.name) > -1; - }) - .sort(function(a, b) { - if (options.reorderButtonGroups.indexOf(a.name) < options.reorderButtonGroups.indexOf(b.name)) return -1; - if (options.reorderButtonGroups.indexOf(a.name) > options.reorderButtonGroups.indexOf(b.name)) return 1; - return 0; - }); - } - - // Build the buttons - if (allBtnGroups.length > 0) { - editorHeader = this.__buildButtons([allBtnGroups], editorHeader); - } - - if (options.fullscreen.enable) { - editorHeader.append('
    ').on('click', '.md-control-fullscreen', function(e) { - e.preventDefault(); - instance.setFullscreen(true); - }); - } - - editor.append(editorHeader); - - // Wrap the textarea - if (container.is('textarea')) { - container.before(editor); - textarea = container; - textarea.addClass('md-input'); - editor.append(textarea); - } else { - var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(), - currentContent = $.trim(rawContent); - - // This is some arbitrary content that could be edited - textarea = $(' +
    +
    + +``` + +Finally, find the file `app/Admin/bootstrap.php`, if the file does not exist, update `laravel-admin`, and then create this file, add the following code: + +``` +php('code'); + +``` + +In this way, you can add any form components you want to add. \ No newline at end of file From bb60c75fe5b52bcb7174aa4cf45b1db3337f7e52 Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 30 Nov 2016 10:37:15 +0800 Subject: [PATCH 0202/2161] optimize grid per-page selector. --- src/Controllers/MenuController.php | 2 +- src/Grid.php | 32 ++++++++++++++++++---------- src/Grid/Model.php | 34 +++++++++++++++++++++++++++++- tests/UserGridTest.php | 1 + 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/Controllers/MenuController.php b/src/Controllers/MenuController.php index 226dd3290d..ea0bebe1a5 100644 --- a/src/Controllers/MenuController.php +++ b/src/Controllers/MenuController.php @@ -93,7 +93,7 @@ public function edit($id) */ public function update($id) { - if(Request::input('parent_id') == $id) { + if (Request::input('parent_id') == $id) { throw new \Exception(trans('admin::lang.parent_select_error')); } diff --git a/src/Grid.php b/src/Grid.php index 5a2d90f384..12cfcade6f 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -155,6 +155,13 @@ class Grid */ protected $perPages = [10, 20, 30, 50, 100]; + /** + * Default items count per-page. + * + * @var int + */ + protected $perPage = 20; + /** * Create a new grid instance. * @@ -288,8 +295,10 @@ public function model() * * @return void */ - public function paginate($perPage = null) + public function paginate($perPage = 20) { + $this->perPage = $perPage; + $this->model()->paginate($perPage); } @@ -548,18 +557,19 @@ public function perPages(array $perPages) */ public function perPageOptions() { - $perPage = app('request')->input('per_page', 20); - - $options = ''; + $perPage = (int) app('request')->input($this->model->getPerPageName(), $this->perPage); - foreach ($this->perPages as $option) { - $selected = ($option == $perPage) ? 'selected' : ''; - $url = app('request')->fullUrlWithQuery(['per_page' => $option]); - - $options .= "\r\n"; - } + return collect($this->perPages) + ->push($this->perPage) + ->push($perPage) + ->unique() + ->sort() + ->map(function ($option) use ($perPage) { + $selected = ($option == $perPage) ? 'selected' : ''; + $url = app('request')->fullUrlWithQuery([$this->model->getPerPageName() => $option]); - return $options; + return ""; + })->implode("\r\n"); } /** diff --git a/src/Grid/Model.php b/src/Grid/Model.php index f5f093d6bb..b1225b76dd 100644 --- a/src/Grid/Model.php +++ b/src/Grid/Model.php @@ -44,10 +44,19 @@ class Model protected $perPage = 20; /** + * If the model use pagination. + * * @var bool */ protected $usePaginate = true; + /** + * The query string variable used to store the per-page. + * + * @var string + */ + protected $perPageName = 'per_page'; + /** * Create a new grid model instance. * @@ -80,6 +89,29 @@ public function usePaginate($use = true) $this->usePaginate = $use; } + /** + * Get the query string variable used to store the per-page. + * + * @return string + */ + public function getPerPageName() + { + return $this->perPageName; + } + + /** + * Set the query string variable used to store the per-page. + * + * @param string $name + * @return $this + */ + public function setPerPageName($name) + { + $this->perPageName = $name; + + return $this; + } + /** * Build. * @@ -184,7 +216,7 @@ protected function setPaginate() */ protected function resolvePerPage($paginate) { - if ($perPage = app('request')->input('per_page')) { + if ($perPage = app('request')->input($this->perPageName)) { if (is_array($paginate)) { $paginate['arguments'][0] = $perPage; diff --git a/tests/UserGridTest.php b/tests/UserGridTest.php index 9377abccbb..b50f4494ed 100644 --- a/tests/UserGridTest.php +++ b/tests/UserGridTest.php @@ -210,6 +210,7 @@ public function testGridPerPage() $perPage = rand(1, 98); $this->visit('admin/users?per_page='.$perPage) + ->seeInElement('select option[selected]', $perPage) ->assertCount($perPage + 1, $this->crawler()->filter('tr')); } } From 969ae2df0c7a246c766f25ed83bdd6d716eb082d Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 30 Nov 2016 10:42:53 +0800 Subject: [PATCH 0203/2161] optimize grid sort name. --- src/Grid/Column.php | 4 ++-- src/Grid/Model.php | 32 +++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/Grid/Column.php b/src/Grid/Column.php index 8232e55b10..8d53a1c46a 100644 --- a/src/Grid/Column.php +++ b/src/Grid/Column.php @@ -471,7 +471,7 @@ public function sorter() } $query = app('request')->all(); - $query = array_merge($query, ['_sort' => ['column' => $this->name, 'type' => $type]]); + $query = array_merge($query, [$this->grid->model()->getSortName() => ['column' => $this->name, 'type' => $type]]); $url = Url::current().'?'.http_build_query($query); @@ -485,7 +485,7 @@ public function sorter() */ protected function isSorted() { - $this->sort = app('request')->get('_sort'); + $this->sort = app('request')->get($this->grid->model()->getSortName()); if (empty($this->sort)) { return false; diff --git a/src/Grid/Model.php b/src/Grid/Model.php index b1225b76dd..047b5faf17 100644 --- a/src/Grid/Model.php +++ b/src/Grid/Model.php @@ -57,6 +57,13 @@ class Model */ protected $perPageName = 'per_page'; + /** + * The query string variable used to store the sort. + * + * @var string + */ + protected $sortName = '_sort'; + /** * Create a new grid model instance. * @@ -112,6 +119,29 @@ public function setPerPageName($name) return $this; } + /** + * Get the query string variable used to store the sort. + * + * @return string + */ + public function getSortName() + { + return $this->sortName; + } + + /** + * Set the query string variable used to store the sort. + * + * @param string $name + * @return $this + */ + public function setSortName($name) + { + $this->sortName = $name; + + return $this; + } + /** * Build. * @@ -250,7 +280,7 @@ protected function findQueryByMethod($method) */ protected function setSort() { - $this->sort = Input::get('_sort', []); + $this->sort = Input::get($this->sortName, []); if (!is_array($this->sort)) { return; } From 22abcf05980be7175cb2605e29ebe636eb0106b3 Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 30 Nov 2016 10:58:03 +0800 Subject: [PATCH 0204/2161] add `rows()` method to Textarea. --- src/Form/Field/Textarea.php | 28 ++++++++++++++++++++++++++++ tests/UserFormTest.php | 2 +- tests/controllers/UserController.php | 2 +- views/form/textarea.blade.php | 2 +- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/Form/Field/Textarea.php b/src/Form/Field/Textarea.php index 7721d4e7fb..d6e45e6ab8 100644 --- a/src/Form/Field/Textarea.php +++ b/src/Form/Field/Textarea.php @@ -6,4 +6,32 @@ class Textarea extends Field { + /** + * Default rows of textarea. + * + * @var int + */ + protected $rows = 10; + + /** + * Set rows of textarea. + * + * @param int $rows + * + * @return $this + */ + public function rows($rows = 10) + { + $this->rows = $rows; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function render() + { + return parent::render()->with(['rows' => $this->rows]); + } } diff --git a/tests/UserFormTest.php b/tests/UserFormTest.php index 0f26549850..41eb5d1eed 100644 --- a/tests/UserFormTest.php +++ b/tests/UserFormTest.php @@ -23,7 +23,7 @@ public function testCreatePage() ->seeElement("input[type=text][name='profile[first_name]']") ->seeElement("input[type=text][name='profile[last_name]']") ->seeElement("input[type=text][name='profile[postcode]']") - ->seeElement("textarea[name='profile[address]']") + ->seeElement("textarea[name='profile[address]'][rows=15]") ->seeElement("input[type=hidden][name='profile[latitude]']") ->seeElement("input[type=hidden][name='profile[longitude]']") ->seeElement("input[type=text][name='profile[color]']") diff --git a/tests/controllers/UserController.php b/tests/controllers/UserController.php index 0d7fa760a6..ef479cfdff 100644 --- a/tests/controllers/UserController.php +++ b/tests/controllers/UserController.php @@ -135,7 +135,7 @@ protected function form() $form->text('profile.first_name'); $form->text('profile.last_name'); $form->text('profile.postcode'); - $form->textarea('profile.address'); + $form->textarea('profile.address')->rows(15); $form->map('profile.latitude', 'profile.longitude', 'Position'); $form->color('profile.color'); $form->datetime('profile.start_at'); diff --git a/views/form/textarea.blade.php b/views/form/textarea.blade.php index 017767e08c..dde6c47eec 100644 --- a/views/form/textarea.blade.php +++ b/views/form/textarea.blade.php @@ -6,6 +6,6 @@ @include('admin::form.error') - + \ No newline at end of file From 71df3eb50e655807771b19e93af78da9270a4266 Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 30 Nov 2016 12:39:59 +0800 Subject: [PATCH 0205/2161] add filter methods. --- src/Grid.php | 24 +++++++++++++++++++++- src/Grid/Filter.php | 29 ++++++++++++++++++++++++++- tests/ImageUploadTest.php | 6 ++++++ tests/controllers/ImageController.php | 2 ++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/Grid.php b/src/Grid.php index 12cfcade6f..41122f4d7e 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -129,6 +129,13 @@ class Grid */ protected $allowExport = true; + /** + * If use grid filter. + * + * @var bool + */ + protected $useFilter = true; + /** * Is grid rows orderable. * @@ -498,6 +505,14 @@ public function disableExport() $this->allowExport = false; } + /** + * Disable grid filter. + */ + public function disableFilter() + { + $this->useFilter = false; + } + /** * Set grid as orderable. * @@ -537,6 +552,10 @@ public function filter(Closure $callback) */ public function renderFilter() { + if (!$this->useFilter) { + return ''; + } + return $this->filter->render(); } @@ -557,7 +576,10 @@ public function perPages(array $perPages) */ public function perPageOptions() { - $perPage = (int) app('request')->input($this->model->getPerPageName(), $this->perPage); + $perPage = (int) app('request')->input( + $this->model->getPerPageName(), + $this->perPage + ); return collect($this->perPages) ->push($this->perPage) diff --git a/src/Grid/Filter.php b/src/Grid/Filter.php index eda2bf09c5..dc87413cc1 100644 --- a/src/Grid/Filter.php +++ b/src/Grid/Filter.php @@ -41,10 +41,19 @@ class Filter protected $supports = ['is', 'like', 'gt', 'lt', 'between', 'where']; /** + * If use a modal to hold the filters. + * * @var bool */ protected $useModal = false; + /** + * If use id filter. + * + * @var bool + */ + protected $useIdFilter = true; + /** * Create a new filter instance. * @@ -68,6 +77,14 @@ public function useModal() $this->useModal = true; } + /** + * Disable Id filter. + */ + public function disableIdFilter() + { + $this->useIdFilter = false; + } + /** * Get all conditions of the filters. * @@ -123,6 +140,8 @@ public function execute() } /** + * Get parent grid instance. + * * @return Grid */ public function getGrid() @@ -137,11 +156,19 @@ public function getGrid() */ public function render() { + if (!$this->useIdFilter) { + array_shift($this->filters); + } + + if (empty($this->filters)) { + return ''; + } + if ($this->useModal) { return $this->renderModalFilter(); } - return view('admin::grid.filter')->with(['filters' => $this->filters(), 'grid' => $this->grid]); + return view('admin::grid.filter')->with(['filters' => $this->filters, 'grid' => $this->grid]); } /** diff --git a/tests/ImageUploadTest.php b/tests/ImageUploadTest.php index c8dce7e8f4..f4b0ce1478 100644 --- a/tests/ImageUploadTest.php +++ b/tests/ImageUploadTest.php @@ -13,6 +13,12 @@ public function setUp() $this->be(Administrator::first(), 'admin'); } + public function testDisableFilter() + { + $this->visit('admin/images') + ->dontSeeElement('input[name=id]'); + } + public function testImageUploadPage() { $this->visit('admin/images/create') diff --git a/tests/controllers/ImageController.php b/tests/controllers/ImageController.php index 86ef43ade7..e271508408 100644 --- a/tests/controllers/ImageController.php +++ b/tests/controllers/ImageController.php @@ -72,6 +72,8 @@ protected function grid() $grid->created_at(); $grid->updated_at(); + + $grid->disableFilter(); }); } From 242cd5e9f9312f8629493a86af1b20b4da0d0276 Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 30 Nov 2016 18:31:01 +0800 Subject: [PATCH 0206/2161] optimize name setting for file upload. --- src/Form/Field/File.php | 75 ++++++++++++++++++++++++++- src/Form/Field/Image.php | 2 +- tests/ImageUploadTest.php | 2 + tests/controllers/ImageController.php | 6 ++- 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/Form/Field/File.php b/src/Form/Field/File.php index 0198a333fa..0221f82123 100644 --- a/src/Form/Field/File.php +++ b/src/Form/Field/File.php @@ -60,6 +60,13 @@ class File extends Field '/packages/admin/bootstrap-fileinput/js/fileinput.min.js', ]; + /** + * If use unique name to store upload file. + * + * @var bool + */ + protected $useUniqueName = false; + /** * Create a new File instance. * @@ -139,6 +146,32 @@ public function move($directory, $name = null) return $this; } + /** + * Set name of store name. + * + * @param string|callable $name + * + * @return $this + */ + public function name($name) + { + $this->name = $name; + + return $this; + } + + /** + * Use unique name for store upload file. + * + * @return $this + */ + public function uniqueName() + { + $this->useUniqueName = true; + + return $this; + } + /** * Prepare for saving. * @@ -158,11 +191,37 @@ public function prepare(UploadedFile $file = null) $this->directory = $this->directory ?: $this->defaultStorePath(); - $this->name = $this->name ?: $file->getClientOriginalName(); + $this->name = $this->getStoreName($file); return $this->uploadAndDeleteOriginal($file); } + /** + * Get store name of upload file. + * + * @param UploadedFile $file + * + * @return string + */ + protected function getStoreName(UploadedFile $file) + { + if ($this->useUniqueName) { + return $this->generateUniqueName($file); + } + + if (is_callable($this->name)) { + $callback = $this->name->bindTo($this); + + return call_user_func($callback, $file); + } + + if (is_string($this->name)) { + return $this->name; + } + + return $file->getClientOriginalName(); + } + /** * Upload file and delete original file. * @@ -266,6 +325,18 @@ public function isDeleteRequest() return false; } + /** + * Generate a unique name for uploaded file. + * + * @param UploadedFile $file + * + * @return string + */ + protected function generateUniqueName(UploadedFile $file) + { + return md5(uniqid()).'.'.$file->guessExtension(); + } + /** * If name already exists, rename it. * @@ -276,7 +347,7 @@ public function isDeleteRequest() public function renameIfExists(UploadedFile $file) { if ($this->storage->exists("$this->directory/$this->name")) { - $this->name = md5(uniqid()).'.'.$file->guessExtension(); + $this->name = $this->generateUniqueName($file); } } diff --git a/src/Form/Field/Image.php b/src/Form/Field/Image.php index 6586151092..14668dfa77 100644 --- a/src/Form/Field/Image.php +++ b/src/Form/Field/Image.php @@ -28,7 +28,7 @@ public function prepare(UploadedFile $image = null) $this->directory = $this->directory ?: $this->defaultStorePath(); - $this->name = $this->name ?: $image->getClientOriginalName(); + $this->name = $this->getStoreName($image); $this->executeCalls($image->getRealPath()); diff --git a/tests/ImageUploadTest.php b/tests/ImageUploadTest.php index f4b0ce1478..2bb4c4adeb 100644 --- a/tests/ImageUploadTest.php +++ b/tests/ImageUploadTest.php @@ -68,6 +68,8 @@ public function testUploadImage() $this->assertFileExists(public_path('upload/'.$images['image'.$index])); } + $this->assertFileExists(public_path('upload/image/asdasdasdasdasd.jpeg')); + File::cleanDirectory(public_path('upload/image')); } diff --git a/tests/controllers/ImageController.php b/tests/controllers/ImageController.php index e271508408..9d74ab1545 100644 --- a/tests/controllers/ImageController.php +++ b/tests/controllers/ImageController.php @@ -91,8 +91,10 @@ protected function form() $form->image('image2')->rotate(90); $form->image('image3')->flip('v'); $form->image('image4')->move(null, 'renamed.jpeg'); - $form->image('image5'); - $form->image('image6'); + $form->image('image5')->name(function ($file) { + return 'asdasdasdasdasd.'.$file->guessExtension(); + }); + $form->image('image6')->uniqueName(); $form->display('created_at', 'Created At'); $form->display('updated_at', 'Updated At'); From 39c0126e99eee11adfa2facc5a46e43faeca9567 Mon Sep 17 00:00:00 2001 From: Song Date: Wed, 30 Nov 2016 18:41:31 +0800 Subject: [PATCH 0207/2161] Update form-upload.md [skip ci] --- docs/zh/form-upload.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/zh/form-upload.md b/docs/zh/form-upload.md index 86181a6c35..9de62d3616 100644 --- a/docs/zh/form-upload.md +++ b/docs/zh/form-upload.md @@ -7,6 +7,23 @@ $form->file('file_column'); $form->image('image_column'); ``` +### 修改存储路径或文件名 + +```php + +// 修改上传目录 +$form->image('picture')->move('public/upload/image1/'); + +// 使用随机生成文件名 (md5(uniqid()).extension) +$form->image('picture')->uniqueName(); + +// 自定义文件名 +$form->image('picture')->name(function ($file) { + return 'test.'.$file->guessExtension(); +}); + +``` + [model-form](/docs/zh/model-form.md)支持本地和云存储的文件上传 ### 本地上传 From 5c07595de94801b92d61b9894063f3ac46c55c6f Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 1 Dec 2016 13:32:22 +0800 Subject: [PATCH 0208/2161] fix issue #159 --- views/widgets/form.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/widgets/form.blade.php b/views/widgets/form.blade.php index 2ef4c534b0..a115bfeb84 100644 --- a/views/widgets/form.blade.php +++ b/views/widgets/form.blade.php @@ -14,7 +14,7 @@
    - +
    From b1c2f7812dfc088d28583e080b334ce4df61dec7 Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 1 Dec 2016 17:17:02 +0800 Subject: [PATCH 0209/2161] fix issue #158, add `Grid::disablePagination()` and `Grid:: disablePerPageSelector()` methods. --- src/Grid.php | 82 ++++++++++++++++++++++++++++++++++++++ views/grid/image.blade.php | 32 +++++++-------- views/grid/table.blade.php | 33 ++++++++------- 3 files changed, 113 insertions(+), 34 deletions(-) diff --git a/src/Grid.php b/src/Grid.php index 41122f4d7e..6301ee8a01 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -136,6 +136,20 @@ class Grid */ protected $useFilter = true; + /** + * If grid use pagination. + * + * @var bool + */ + protected $usePagination = true; + + /** + * If grid use per-page selector. + * + * @var bool + */ + protected $usePerPageSelector = true; + /** * Is grid rows orderable. * @@ -316,6 +330,10 @@ public function paginate($perPage = 20) */ public function paginator() { + if (!$this->usePagination) { + return ''; + } + $query = Input::all(); return $this->model()->eloquent()->appends($query)->render('admin::pagination'); @@ -445,18 +463,26 @@ public function allowBatchDeletion() /** * Disable batch deletion. + * + * @return $this */ public function disableBatchDeletion() { $this->allowBatchDeletion = false; + + return $this; } /** * Disable creation. + * + * @return $this */ public function disableCreation() { $this->allowCreation = false; + + return $this; } /** @@ -481,10 +507,14 @@ public function allowActions() /** * Disable all actions. + * + * @return $this */ public function disableActions() { $this->allowActions = false; + + return $this; } /** @@ -499,18 +529,70 @@ public function allowExport() /** * Disable export. + * + * @return $this */ public function disableExport() { $this->allowExport = false; + + return $this; } /** * Disable grid filter. + * + * @return $this */ public function disableFilter() { $this->useFilter = false; + + return $this; + } + + /** + * Disable grid pagination. + * + * @return $this + */ + public function disablePagination() + { + $this->model->usePaginate(false); + + $this->usePagination = false; + + return $this; + } + + /** + * If this grid use pagination. + * + * @return bool + */ + public function usePagination() + { + return $this->usePagination; + } + + /** + * Disable grid per-page selector. + */ + public function disablePerPageSelector() + { + $this->usePerPageSelector = false; + + return $this; + } + + /** + * If this grid use per-page selector. + * + * @return bool + */ + public function usePerPageSelector() + { + return $this->usePerPageSelector; } /** diff --git a/views/grid/image.blade.php b/views/grid/image.blade.php index d4902952c7..928e8df0be 100644 --- a/views/grid/image.blade.php +++ b/views/grid/image.blade.php @@ -2,6 +2,7 @@

    + @if($grid->usePagination() && $grid->usePerPageSelector())
    {{ trans('admin::lang.show') }} {{ trans('admin::lang.items') }}
    + @endif -
    - - {!! $grid->renderFilter() !!} - - @if($grid->allowExport()) - - @endif + {!! $grid->renderFilter() !!} - @if($grid->allowCreation()) - - @endif + @if($grid->allowExport()) + + @endif -
    + @if($grid->allowCreation()) + + @endif
    @@ -38,7 +36,7 @@
      {!! isset($text_column) ? $row->column($text_column) : '' !!} - + @if($grid->allowActions()) {!! $row->actions() !!} @@ -53,7 +51,7 @@
    \ No newline at end of file diff --git a/views/form/color.blade.php b/views/form/color.blade.php index d3ea7d5fcc..c7a6c37318 100644 --- a/views/form/color.blade.php +++ b/views/form/color.blade.php @@ -10,5 +10,8 @@ + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/currency.blade.php b/views/form/currency.blade.php index 2ede76a9a6..90903c9977 100644 --- a/views/form/currency.blade.php +++ b/views/form/currency.blade.php @@ -10,5 +10,8 @@ {{$symbol}} + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/date.blade.php b/views/form/date.blade.php index 7788554978..f484d0710d 100644 --- a/views/form/date.blade.php +++ b/views/form/date.blade.php @@ -10,5 +10,8 @@ + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/daterange.blade.php b/views/form/daterange.blade.php index 6c3bc3212f..015f2d3caf 100644 --- a/views/form/daterange.blade.php +++ b/views/form/daterange.blade.php @@ -22,5 +22,7 @@ + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/datetime.blade.php b/views/form/datetime.blade.php index 12facf133d..fc3377d5a4 100644 --- a/views/form/datetime.blade.php +++ b/views/form/datetime.blade.php @@ -10,5 +10,8 @@ + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/datetimerange.blade.php b/views/form/datetimerange.blade.php index 7090500180..8ffc035b2f 100644 --- a/views/form/datetimerange.blade.php +++ b/views/form/datetimerange.blade.php @@ -22,5 +22,7 @@ + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/decimal.blade.php b/views/form/decimal.blade.php index 1d63b49eaa..7295a9e7e5 100644 --- a/views/form/decimal.blade.php +++ b/views/form/decimal.blade.php @@ -12,5 +12,8 @@ + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/display.blade.php b/views/form/display.blade.php index 8efc8811be..d7f81c8def 100644 --- a/views/form/display.blade.php +++ b/views/form/display.blade.php @@ -8,5 +8,7 @@ + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/editor.blade.php b/views/form/editor.blade.php index bf5208b82d..eec6ad083e 100644 --- a/views/form/editor.blade.php +++ b/views/form/editor.blade.php @@ -7,5 +7,8 @@ @include('admin::form.error') + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/email.blade.php b/views/form/email.blade.php index 0875018e7c..3afc8bd88c 100644 --- a/views/form/email.blade.php +++ b/views/form/email.blade.php @@ -10,5 +10,8 @@ + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/file.blade.php b/views/form/file.blade.php index 2ea638af70..dda0a04b11 100644 --- a/views/form/file.blade.php +++ b/views/form/file.blade.php @@ -7,5 +7,8 @@ @include('admin::form.error') + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/help-block.blade.php b/views/form/help-block.blade.php new file mode 100644 index 0000000000..2a32e72546 --- /dev/null +++ b/views/form/help-block.blade.php @@ -0,0 +1,5 @@ +@if($help) + +  {!! array_get($help, 'text') !!} + +@endif \ No newline at end of file diff --git a/views/form/id.blade.php b/views/form/id.blade.php index e9ab1fbfad..7462a009a6 100644 --- a/views/form/id.blade.php +++ b/views/form/id.blade.php @@ -3,5 +3,8 @@
    + + @include('admin::form.help-block') +
    \ No newline at end of file diff --git a/views/form/image.blade.php b/views/form/image.blade.php index 2ea638af70..dda0a04b11 100644 --- a/views/form/image.blade.php +++ b/views/form/image.blade.php @@ -7,5 +7,8 @@ @include('admin::form.error') + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/ip.blade.php b/views/form/ip.blade.php index dbda44e2a4..4d9760d2a3 100644 --- a/views/form/ip.blade.php +++ b/views/form/ip.blade.php @@ -12,5 +12,8 @@ + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/json.blade.php b/views/form/json.blade.php index 017767e08c..90f3592b5b 100644 --- a/views/form/json.blade.php +++ b/views/form/json.blade.php @@ -7,5 +7,8 @@ @include('admin::form.error') + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/map.blade.php b/views/form/map.blade.php index 1bcc1d36ee..083a130f7a 100644 --- a/views/form/map.blade.php +++ b/views/form/map.blade.php @@ -9,5 +9,8 @@
    + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/mobile.blade.php b/views/form/mobile.blade.php index 90ed3ba762..43a29147f4 100644 --- a/views/form/mobile.blade.php +++ b/views/form/mobile.blade.php @@ -12,5 +12,8 @@ + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/multipleselect.blade.php b/views/form/multipleselect.blade.php index 42270409cc..0579246601 100644 --- a/views/form/multipleselect.blade.php +++ b/views/form/multipleselect.blade.php @@ -12,5 +12,8 @@ @endforeach + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/number.blade.php b/views/form/number.blade.php index 493aacaa41..f929adb4bc 100644 --- a/views/form/number.blade.php +++ b/views/form/number.blade.php @@ -9,5 +9,8 @@
    + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/password.blade.php b/views/form/password.blade.php index 336fc198ff..848379a26e 100644 --- a/views/form/password.blade.php +++ b/views/form/password.blade.php @@ -12,5 +12,8 @@ + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/radio.blade.php b/views/form/radio.blade.php index 60699a53eb..e33271e6d2 100644 --- a/views/form/radio.blade.php +++ b/views/form/radio.blade.php @@ -9,5 +9,8 @@ @foreach($values as $option => $label)  {{$label}}   @endforeach + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/rate.blade.php b/views/form/rate.blade.php index 4b0c4ad0cb..8792463b87 100644 --- a/views/form/rate.blade.php +++ b/views/form/rate.blade.php @@ -10,5 +10,8 @@ % + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/select.blade.php b/views/form/select.blade.php index feab53a38d..7533747ff0 100644 --- a/views/form/select.blade.php +++ b/views/form/select.blade.php @@ -11,5 +11,8 @@ @endforeach + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/slider.blade.php b/views/form/slider.blade.php index 889f1c637e..90f58462b8 100644 --- a/views/form/slider.blade.php +++ b/views/form/slider.blade.php @@ -7,5 +7,8 @@ @include('admin::form.error') + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/switchfield.blade.php b/views/form/switchfield.blade.php index 0fe2498b6b..d1fb33c26e 100644 --- a/views/form/switchfield.blade.php +++ b/views/form/switchfield.blade.php @@ -9,5 +9,7 @@ + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/text.blade.php b/views/form/text.blade.php index 6603406a30..61c4242645 100644 --- a/views/form/text.blade.php +++ b/views/form/text.blade.php @@ -10,5 +10,8 @@ + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/textarea.blade.php b/views/form/textarea.blade.php index dde6c47eec..9a254a2371 100644 --- a/views/form/textarea.blade.php +++ b/views/form/textarea.blade.php @@ -7,5 +7,8 @@ @include('admin::form.error') + + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/time.blade.php b/views/form/time.blade.php index 09f9725111..d823456926 100644 --- a/views/form/time.blade.php +++ b/views/form/time.blade.php @@ -10,5 +10,8 @@ + + @include('admin::form.help-block') + diff --git a/views/form/timerange.blade.php b/views/form/timerange.blade.php index 6c3bc3212f..015f2d3caf 100644 --- a/views/form/timerange.blade.php +++ b/views/form/timerange.blade.php @@ -22,5 +22,7 @@ + @include('admin::form.help-block') + \ No newline at end of file diff --git a/views/form/url.blade.php b/views/form/url.blade.php index c6361aa4d3..050eefe04e 100644 --- a/views/form/url.blade.php +++ b/views/form/url.blade.php @@ -10,5 +10,8 @@ + + @include('admin::form.help-block') + \ No newline at end of file From db1db28273de8b8f6f93e600d666c1c9ae63b0ef Mon Sep 17 00:00:00 2001 From: z-song Date: Sat, 3 Dec 2016 00:10:09 +0800 Subject: [PATCH 0212/2161] add connection for migrations. --- .../2016_01_04_173148_create_admin_tables.php | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/migrations/2016_01_04_173148_create_admin_tables.php b/migrations/2016_01_04_173148_create_admin_tables.php index abaa5de4f2..dc67ce3904 100644 --- a/migrations/2016_01_04_173148_create_admin_tables.php +++ b/migrations/2016_01_04_173148_create_admin_tables.php @@ -12,7 +12,9 @@ class CreateAdminTables extends Migration */ public function up() { - Schema::create(config('admin.database.users_table'), function (Blueprint $table) { + $connection = config('admin.database.connection') ?: config('database.default'); + + Schema::connection($connection)->create(config('admin.database.users_table'), function (Blueprint $table) { $table->increments('id'); $table->string('username', 190)->unique(); $table->string('password', 60); @@ -21,21 +23,21 @@ public function up() $table->timestamps(); }); - Schema::create(config('admin.database.roles_table'), function (Blueprint $table) { + Schema::connection($connection)->create(config('admin.database.roles_table'), function (Blueprint $table) { $table->increments('id'); $table->string('name', 50)->unique(); $table->string('slug', 50); $table->timestamps(); }); - Schema::create(config('admin.database.permissions_table'), function (Blueprint $table) { + Schema::connection($connection)->create(config('admin.database.permissions_table'), function (Blueprint $table) { $table->increments('id'); $table->string('name', 50)->unique(); $table->string('slug', 50); $table->timestamps(); }); - Schema::create(config('admin.database.menu_table'), function (Blueprint $table) { + Schema::connection($connection)->create(config('admin.database.menu_table'), function (Blueprint $table) { $table->increments('id'); $table->integer('parent_id')->default(0); $table->integer('order')->default(0); @@ -46,35 +48,35 @@ public function up() $table->timestamps(); }); - Schema::create(config('admin.database.role_users_table'), function (Blueprint $table) { + Schema::connection($connection)->create(config('admin.database.role_users_table'), function (Blueprint $table) { $table->integer('role_id'); $table->integer('user_id'); $table->index(['role_id', 'user_id']); $table->timestamps(); }); - Schema::create(config('admin.database.role_permissions_table'), function (Blueprint $table) { + Schema::connection($connection)->create(config('admin.database.role_permissions_table'), function (Blueprint $table) { $table->integer('role_id'); $table->integer('permission_id'); $table->index(['role_id', 'permission_id']); $table->timestamps(); }); - Schema::create(config('admin.database.user_permissions_table'), function (Blueprint $table) { + Schema::connection($connection)->create(config('admin.database.user_permissions_table'), function (Blueprint $table) { $table->integer('user_id'); $table->integer('permission_id'); $table->index(['user_id', 'permission_id']); $table->timestamps(); }); - Schema::create(config('admin.database.role_menu_table'), function (Blueprint $table) { + Schema::connection($connection)->create(config('admin.database.role_menu_table'), function (Blueprint $table) { $table->integer('role_id'); $table->integer('menu_id'); $table->index(['role_id', 'menu_id']); $table->timestamps(); }); - Schema::create(config('admin.database.operation_log_table'), function (Blueprint $table) { + Schema::connection($connection)->create(config('admin.database.operation_log_table'), function (Blueprint $table) { $table->increments('id'); $table->integer('user_id'); $table->string('path'); @@ -93,14 +95,16 @@ public function up() */ public function down() { - Schema::drop(config('admin.database.users_table')); - Schema::drop(config('admin.database.roles_table')); - Schema::drop(config('admin.database.permissions_table')); - Schema::drop(config('admin.database.menu_table')); - Schema::drop(config('admin.database.user_permissions_table')); - Schema::drop(config('admin.database.role_users_table')); - Schema::drop(config('admin.database.role_permissions_table')); - Schema::drop(config('admin.database.role_menu_table')); - Schema::drop(config('admin.database.operation_log_table')); + $connection = config('admin.database.connection') ?: config('database.default'); + + Schema::connection($connection)->drop(config('admin.database.users_table')); + Schema::connection($connection)->drop(config('admin.database.roles_table')); + Schema::connection($connection)->drop(config('admin.database.permissions_table')); + Schema::connection($connection)->drop(config('admin.database.menu_table')); + Schema::connection($connection)->drop(config('admin.database.user_permissions_table')); + Schema::connection($connection)->drop(config('admin.database.role_users_table')); + Schema::connection($connection)->drop(config('admin.database.role_permissions_table')); + Schema::connection($connection)->drop(config('admin.database.role_menu_table')); + Schema::connection($connection)->drop(config('admin.database.operation_log_table')); } } From db235628123d936632db6196e39ee778d5374560 Mon Sep 17 00:00:00 2001 From: Song Date: Fri, 2 Dec 2016 16:16:48 +0000 Subject: [PATCH 0213/2161] Apply fixes from StyleCI --- src/Grid/Model.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Grid/Model.php b/src/Grid/Model.php index 047b5faf17..1f304eb5ef 100644 --- a/src/Grid/Model.php +++ b/src/Grid/Model.php @@ -109,7 +109,8 @@ public function getPerPageName() /** * Set the query string variable used to store the per-page. * - * @param string $name + * @param string $name + * * @return $this */ public function setPerPageName($name) @@ -132,7 +133,8 @@ public function getSortName() /** * Set the query string variable used to store the sort. * - * @param string $name + * @param string $name + * * @return $this */ public function setSortName($name) From f7a0b1adf1b969ade048e01c034d65bc04115d22 Mon Sep 17 00:00:00 2001 From: chenlixin93 Date: Sat, 3 Dec 2016 11:28:23 +0800 Subject: [PATCH 0214/2161] Update menu.md --- docs/zh/menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/menu.md b/docs/zh/menu.md index 03beac6e38..df79e5391d 100644 --- a/docs/zh/menu.md +++ b/docs/zh/menu.md @@ -2,4 +2,4 @@ 打开`http://localhost:8000/admin/auth/menu`设置左侧边栏菜单。 -树状菜单基于[Nestable](https://github.com/dbushell/Nestable)开发,通过拖动来调整菜单的结构和顺序,点击下面的保存按钮来保存设置,然后刷新就能看到左侧边栏的变化了,`路径`天填写访问路径,角色选择可以看见这个菜单项的角色身份。 +树状菜单基于[Nestable](https://github.com/dbushell/Nestable)开发,通过拖动来调整菜单的结构和顺序,点击下面的保存按钮来保存设置,然后刷新就能看到左侧边栏的变化了,`路径`填写访问路径,角色选择可以看见这个菜单项的角色身份。 From b03d3cf5f7cb57fa31a52d410e594c481adcee22 Mon Sep 17 00:00:00 2001 From: z-song Date: Sun, 4 Dec 2016 23:42:39 +0800 Subject: [PATCH 0215/2161] multiple image upload support. --- src/Form.php | 56 ++----- src/Form/Field.php | 53 +++++- src/Form/Field/File.php | 158 ++++++++++++++++-- src/Form/Field/Image.php | 58 +++++-- tests/ImageUploadTest.php | 107 +++++++++++- tests/UserFormTest.php | 10 +- tests/controllers/MultipleImageController.php | 96 +++++++++++ tests/controllers/UserController.php | 4 +- .../2016_11_22_093148_create_test_tables.php | 7 + tests/models/MultipleImage.php | 10 ++ tests/routes.php | 1 + views/form/file.blade.php | 2 +- views/form/image.blade.php | 2 +- 13 files changed, 491 insertions(+), 73 deletions(-) create mode 100644 tests/controllers/MultipleImageController.php create mode 100644 tests/models/MultipleImage.php diff --git a/src/Form.php b/src/Form.php index 1f358bdad0..1392c5b68d 100644 --- a/src/Form.php +++ b/src/Form.php @@ -12,7 +12,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Input; -use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Validator; use Spatie\EloquentSortable\Sortable; /** @@ -263,8 +263,9 @@ public function store() { $data = Input::all(); - if (!$this->validate($data)) { - return back()->withInput()->withErrors($this->validator->messages()); + if ($validator = $this->validationFails($data)) { + dump($validator->messages()); + return back()->withInput()->withErrors($validator->messages()); } $this->prepare($data, $this->saving); @@ -402,8 +403,8 @@ public function update($id) return response(['status' => true, 'message' => trans('admin::lang.succeeded')]); } - if (!$this->validate($data)) { - return back()->withInput()->withErrors($this->validator->messages()); + if ($validator = $this->validationFails($data)) { + return back()->withInput()->withErrors($validator->messages()); } $this->model = $this->model->with($this->getRelations())->findOrFail($id); @@ -700,53 +701,26 @@ protected function setFieldValue($id) } /** - * Validate input data. + * Validation fails. * - * @param $input + * @param array $input * * @return bool */ - protected function validate($input) + protected function validationFails($input) { - $data = $rules = []; - foreach ($this->builder->fields() as $field) { - if (!method_exists($field, 'rules') || !$rule = $field->rules()) { - continue; - } - - $columns = $field->column(); - - if (is_string($columns)) { - if (!array_key_exists($columns, $input)) { - continue; - } - $value = array_get($input, $columns); - - // remove empty options from multiple select. - if ($field instanceof Field\MultipleSelect) { - $value = array_filter($value); - } + if (!$validator = $field->validate($input)) { + continue; + }; - $data[$field->label()] = $value; - $rules[$field->label()] = $rule; - } - - if (is_array($columns)) { - foreach ($columns as $key => $column) { - if (!array_key_exists($column, $input)) { - continue; - } - $data[$field->label().$key] = array_get($input, $column); - $rules[$field->label().$key] = $rule; - } + if (($validator instanceof Validator) && !$validator->passes()) { + return $validator; } } - $this->validator = Validator::make($data, $rules); - - return $this->validator->passes(); + return false; } /** diff --git a/src/Form/Field.php b/src/Form/Field.php index 20883b4f66..8fbc64bdb3 100644 --- a/src/Form/Field.php +++ b/src/Form/Field.php @@ -5,6 +5,7 @@ use Encore\Admin\Admin; use Encore\Admin\Form; use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Support\Facades\Validator; /** * Class Field. @@ -290,7 +291,7 @@ public function options($options = []) * * @param null $rules * - * @return $this + * @return $this|array */ public function rules($rules = null) { @@ -305,6 +306,12 @@ public function rules($rules = null) return $this; } + + protected function getRules() + { + return $this->rules; + } + /** * Set or get value of the field. * @@ -368,6 +375,50 @@ public function original() return $this->original; } + /** + * Validate input field data. + * + * @param array $input + * + * @return bool|Validator + */ + public function validate(array $input) + { + $data = $rules = []; + + if (!$fieldRules = $this->getRules()) { + return false; + } + + if (is_string($this->column)) { + if (!array_has($input, $this->column)) { + return false; + } + + $value = array_get($input, $this->column); + + // remove empty options from multiple select. + if ($this instanceof Field\MultipleSelect) { + $value = array_filter($value); + } + + $data[$this->label] = $value; + $rules[$this->label] = $fieldRules; + } + + if (is_array($this->column)) { + foreach ($this->column as $key => $column) { + if (!array_key_exists($column, $input)) { + continue; + } + $data[$this->label.$key] = array_get($input, $column); + $rules[$this->label.$key] = $fieldRules; + } + } + + return Validator::make($data, $rules); + } + /** * Add html attributes to elements. * diff --git a/src/Form/Field/File.php b/src/Form/Field/File.php index 0221f82123..8dc0a3ba8c 100644 --- a/src/Form/Field/File.php +++ b/src/Form/Field/File.php @@ -5,6 +5,7 @@ use Encore\Admin\Form\Field; use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -67,6 +68,13 @@ class File extends Field */ protected $useUniqueName = false; + /** + * Use multiple upload. + * + * @var bool + */ + protected $multiple = false; + /** * Create a new File instance. * @@ -119,6 +127,20 @@ public function options($options = []) return $this; } + /** + * Set field as mulitple upload. + * + * @return $this + */ + public function multiple() + { + $this->attribute('multiple', true); + + $this->multiple = true; + + return $this; + } + /** * Default store path for file upload. * @@ -146,6 +168,49 @@ public function move($directory, $name = null) return $this; } + /** + * {@inheritdoc} + */ + public function validate(array $input) + { + if (!$fieldRules = $this->getRules()) { + return false; + } + + if (!array_has($input, $this->column)) { + return false; + } + + $value = array_get($input, $this->column); + + if ($this->multiple) { + list($rules, $data) = $this->hydrateFiles($value); + } else { + $data = [$this->column => $value]; + $rules = [$this->column => $this->getRules()]; + } + + return Validator::make($data, $rules); + } + + /** + * Hydrate the files array. + * + * @param array $value + * @return array + */ + protected function hydrateFiles(array $value) + { + $data = $rules = []; + + foreach ($value as $key => $file) { + $rules[$this->column.$key] = $this->getRules(); + $data[$this->column.$key] = $file; + } + + return [$rules, $data]; + } + /** * Set name of store name. * @@ -175,13 +240,13 @@ public function uniqueName() /** * Prepare for saving. * - * @param UploadedFile $file + * @param UploadedFile|array $files * * @return mixed|string */ - public function prepare(UploadedFile $file = null) + public function prepare($files) { - if (is_null($file)) { + if (is_null($files)) { if ($this->isDeleteRequest()) { return ''; } @@ -189,6 +254,24 @@ public function prepare(UploadedFile $file = null) return $this->original; } + if ($this->multiple || is_array($files)) { + $targets = array_map([$this, 'prepareForSingle'], $files); + + return json_encode($targets); + } + + return $this->prepareForSingle($files); + } + + /** + * Prepare for single file. + * + * @param UploadedFile $file + * + * @return mixed|string + */ + protected function prepareForSingle(UploadedFile $file = null) + { $this->directory = $this->directory ?: $this->defaultStorePath(); $this->name = $this->getStoreName($file); @@ -245,11 +328,27 @@ protected function uploadAndDeleteOriginal(UploadedFile $file) /** * Preview html for file-upload plugin. * - * @return string + * @return array */ protected function preview() { - $fileName = basename($this->value); + $files = json_decode($this->value, true); + + if (!is_array($files)) { + $files = [$this->value]; + } + + return array_map([$this, 'buildPreviewItem'], $files); + } + + /** + * Preview html for file-upload plugin. + * + * @return string + */ + protected function buildPreviewItem($file) + { + $fileName = basename($file); return << @@ -279,6 +378,29 @@ public function objectUrl($path) return rtrim(config('admin.upload.host'), '/').'/'.trim($path, '/'); } + /** + * Initialize the caption. + * + * @param string $caption + * @return string + */ + protected function initialCaption($caption) + { + if (empty($caption)) { + return ''; + } + + if ($this->multiple) { + $caption = json_decode($caption, true); + } else { + $caption = [$caption]; + } + + $caption = array_map('basename', $caption); + + return implode(',', $caption); + } + /** * Render file upload field. * @@ -286,7 +408,7 @@ public function objectUrl($path) */ public function render() { - $this->options['initialCaption'] = basename($this->value); + $this->options['initialCaption'] = $this->initialCaption($this->value); if (!empty($this->value)) { $this->options['initialPreview'] = $this->preview(); @@ -304,7 +426,7 @@ public function render() EOT; - return parent::render(); + return parent::render()->with(['multiple' => $this->multiple]); } /** @@ -352,14 +474,30 @@ public function renameIfExists(UploadedFile $file) } /** - * Destroy original file. + * Destroy original files. * * @return void. */ public function destroy() { - if ($this->storage->exists($this->original)) { - $this->storage->delete($this->original); + $files = json_decode($this->original, true); + + if (!is_array($files)) { + $files = [$this->original]; + } + + array_map([$this, 'destroyItem'], $files); + } + + /** + * Destroy single original file. + * + * @param string $item + */ + protected function destroyItem($item) + { + if ($this->storage->exists($item)) { + $this->storage->delete($item); } } } diff --git a/src/Form/Field/Image.php b/src/Form/Field/Image.php index 14668dfa77..8f51bab70e 100644 --- a/src/Form/Field/Image.php +++ b/src/Form/Field/Image.php @@ -7,25 +7,39 @@ class Image extends File { + /** + * Validation rules. + * + * @var string + */ protected $rules = 'image'; + /** + * Intervention calls. + * + * @var array + */ protected $calls = []; + /** + * Get default storage path. + * + * @return mixed + */ public function defaultStorePath() { return config('admin.upload.directory.image'); } - public function prepare(UploadedFile $image = null) + /** + * Prepare for single upload file. + * + * @param UploadedFile|null $image + * + * @return string + */ + protected function prepareForSingle(UploadedFile $image = null) { - if (is_null($image)) { - if ($this->isDeleteRequest()) { - return ''; - } - - return $this->original; - } - $this->directory = $this->directory ?: $this->defaultStorePath(); $this->name = $this->getStoreName($image); @@ -38,7 +52,9 @@ public function prepare(UploadedFile $image = null) } /** - * @param $target + * Execute Intervention calls. + * + * @param string $target * * @return mixed */ @@ -55,11 +71,22 @@ public function executeCalls($target) return $target; } - protected function preview() + /** + * Build a preview item. + * + * @param string $image + * @return string + */ + protected function buildPreviewItem($image) { - return ''; + return ''; } + /** + * Render a image form field. + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ public function render() { $this->options(['allowedFileTypes' => ['image']]); @@ -67,6 +94,13 @@ public function render() return parent::render(); } + /** + * Call intervention methods. + * + * @param string $method + * @param array $arguments + * @return $this + */ public function __call($method, $arguments) { $this->calls[] = [ diff --git a/tests/ImageUploadTest.php b/tests/ImageUploadTest.php index 2bb4c4adeb..4f821dbecc 100644 --- a/tests/ImageUploadTest.php +++ b/tests/ImageUploadTest.php @@ -3,6 +3,7 @@ use Encore\Admin\Auth\Database\Administrator; use Illuminate\Support\Facades\File; use Tests\Models\Image; +use Tests\Models\MultipleImage; class ImageUploadTest extends TestCase { @@ -73,6 +74,25 @@ public function testUploadImage() File::cleanDirectory(public_path('upload/image')); } + public function testRemoveImage() + { + File::cleanDirectory(public_path('upload/image')); + + $this->uploadImages(); + + $this->assertEquals($this->fileCountInImageDir(), 6); + + $this->call( + 'PUT', # $method + '/admin/images/1', # $action + ['image2_action' => 1, 'image5_action' => 1] # $parameters + ); + + $this->assertRedirectedTo('/admin/images'); + + $this->assertEquals($this->fileCountInImageDir(), 4); + } + public function testUpdateImage() { File::cleanDirectory(public_path('upload/image')); @@ -150,9 +170,7 @@ public function testBatchDelete() ->seeInElement('td', 2) ->seeInElement('td', 3); - $fi = new FilesystemIterator(public_path('upload/image'), FilesystemIterator::SKIP_DOTS); - - $this->assertEquals(iterator_count($fi), 18); + $this->assertEquals($this->fileCountInImageDir(), 18); $this->assertEquals(Image::count(), 3); @@ -165,6 +183,87 @@ public function testBatchDelete() ->dontSeeInElement('td', 2) ->dontSeeInElement('td', 3); - $this->assertEquals(iterator_count($fi), 0); + $this->assertEquals($this->fileCountInImageDir(), 0); + } + + public function testUploadMultipleImage() + { + File::cleanDirectory(public_path('upload/image')); + + $this->visit('admin/multiple-images/create') + ->seeElement('input[type=file][name="pictures[]"][multiple=1]'); + + $path = __DIR__.'/assets/test.jpg'; + + $file = new \Illuminate\Http\UploadedFile( + $path, 'test.jpg', 'image/jpeg', filesize($path), null, true + ); + + $size = rand(10, 20); + $files = ['pictures' => array_pad([], $size, $file)]; + + $this->call( + 'POST', # $method + '/admin/multiple-images', # $action + [], # $parameters + [], + $files + ); + + $this->assertResponseStatus(302); + $this->assertRedirectedTo('/admin/multiple-images'); + + $this->assertEquals($this->fileCountInImageDir(), $size); + + $pictures = MultipleImage::first()->pictures; + + $pictures = json_decode($pictures, true); + + $this->assertCount($size, $pictures); + + foreach ($pictures as $picture) { + $this->assertFileExists(public_path('upload/'.$picture)); + } + } + + public function testRemoveMultipleFiles() + { + File::cleanDirectory(public_path('upload/image')); + + // upload files + $path = __DIR__.'/assets/test.jpg'; + + $file = new \Illuminate\Http\UploadedFile( + $path, 'test.jpg', 'image/jpeg', filesize($path), null, true + ); + + $size = rand(10, 20); + $files = ['pictures' => array_pad([], $size, $file)]; + + $this->call( + 'POST', # $method + '/admin/multiple-images', # $action + [], # $parameters + [], + $files + ); + + $this->assertEquals($this->fileCountInImageDir(), $size); + + // remove files + $this->call( + 'PUT', # $method + '/admin/multiple-images/1', # $action + ['pictures_action' => 1] # $parameters + ); + + $this->assertEquals($this->fileCountInImageDir(), 0); + } + + protected function fileCountInImageDir($dir = 'upload/image') + { + $file = new FilesystemIterator(public_path($dir), FilesystemIterator::SKIP_DOTS); + + return iterator_count($file); } } diff --git a/tests/UserFormTest.php b/tests/UserFormTest.php index e527c72d09..42f123e095 100644 --- a/tests/UserFormTest.php +++ b/tests/UserFormTest.php @@ -32,7 +32,8 @@ public function testCreatePage() ->seeElement('span[class=help-block] i[class*=fa-info-circle]') ->seeInElement('span[class=help-block]', 'Please input your postcode') ->seeElement('span[class=help-block] i[class*=fa-image]') - ->seeInElement('span[class=help-block]', '上传头像'); + ->seeInElement('span[class=help-block]', '上传头像') + ->seeElement("select[name='tags[]'][multiple=multiple]"); } public function testSubmitForm() @@ -102,6 +103,7 @@ protected function seedsTable($count = 100) ->create() ->each(function ($u) { $u->profile()->save(factory(\Tests\Models\Profile::class)->make()); + $u->tags()->saveMany(factory(\Tests\Models\Tag::class, 5)->make()); }); } @@ -126,7 +128,11 @@ public function testEditForm() ->seeElement("input[type=hidden][name='profile[longitude]'][value='{$user->profile->longitude}']") ->seeElement("input[type=text][name='profile[color]'][value='{$user->profile->color}']") ->seeElement("input[type=text][name='profile[start_at]'][value='{$user->profile->start_at}']") - ->seeElement("input[type=text][name='profile[end_at]'][value='{$user->profile->end_at}']"); + ->seeElement("input[type=text][name='profile[end_at]'][value='{$user->profile->end_at}']") + ->seeElement("select[name='tags[]'][multiple=multiple]"); + + $this->assertCount(50, $this->crawler()->filter("select[name='tags[]'] option")); + $this->assertCount(5, $this->crawler()->filter("select[name='tags[]'] option[selected]")); } public function testUpdateForm() diff --git a/tests/controllers/MultipleImageController.php b/tests/controllers/MultipleImageController.php new file mode 100644 index 0000000000..70e6fa8607 --- /dev/null +++ b/tests/controllers/MultipleImageController.php @@ -0,0 +1,96 @@ +header('header'); + $content->description('description'); + + $content->body($this->grid()); + }); + } + + /** + * Edit interface. + * + * @param $id + * + * @return Content + */ + public function edit($id) + { + return Admin::content(function (Content $content) use ($id) { + $content->header('header'); + $content->description('description'); + + $content->body($this->form()->edit($id)); + }); + } + + /** + * Create interface. + * + * @return Content + */ + public function create() + { + return Admin::content(function (Content $content) { + $content->header('Upload image'); + + $content->body($this->form()); + }); + } + + /** + * Make a grid builder. + * + * @return Grid + */ + protected function grid() + { + return Admin::grid(Image::class, function (Grid $grid) { + $grid->id('ID')->sortable(); + + $grid->created_at(); + $grid->updated_at(); + + $grid->disableFilter(); + }); + } + + /** + * Make a form builder. + * + * @return Form + */ + protected function form() + { + return Admin::form(MultipleImage::class, function (Form $form) { + $form->display('id', 'ID'); + + $form->image('pictures')->multiple(); + + $form->display('created_at', 'Created At'); + $form->display('updated_at', 'Updated At'); + }); + } +} diff --git a/tests/controllers/UserController.php b/tests/controllers/UserController.php index 0394e277ee..37ce695f28 100644 --- a/tests/controllers/UserController.php +++ b/tests/controllers/UserController.php @@ -8,7 +8,7 @@ use Encore\Admin\Form; use Encore\Admin\Grid; use Encore\Admin\Layout\Content; -use Tests\Models\Profile; +use Tests\Models\Tag; use Tests\Models\User; class UserController extends Controller @@ -141,6 +141,8 @@ protected function form() $form->datetime('profile.start_at'); $form->datetime('profile.end_at'); + $form->multipleSelect('tags', 'Tags')->options(Tag::all()->pluck('name', 'id'));//->rules('max:10|min:3'); + $form->display('created_at', 'Created At'); $form->display('updated_at', 'Updated At'); }); diff --git a/tests/migrations/2016_11_22_093148_create_test_tables.php b/tests/migrations/2016_11_22_093148_create_test_tables.php index 753981ac3c..e83cbcde70 100644 --- a/tests/migrations/2016_11_22_093148_create_test_tables.php +++ b/tests/migrations/2016_11_22_093148_create_test_tables.php @@ -23,6 +23,12 @@ public function up() $table->timestamps(); }); + Schema::create('test_multiple_images', function (Blueprint $table) { + $table->increments('id'); + $table->text('pictures'); + $table->timestamps(); + }); + Schema::create('test_files', function (Blueprint $table) { $table->increments('id'); $table->string('file1'); @@ -82,6 +88,7 @@ public function up() public function down() { Schema::drop('test_images'); + Schema::drop('test_multiple_images'); Schema::drop('test_files'); Schema::drop('test_users'); Schema::drop('test_user_profiles'); diff --git a/tests/models/MultipleImage.php b/tests/models/MultipleImage.php new file mode 100644 index 0000000000..1116be530b --- /dev/null +++ b/tests/models/MultipleImage.php @@ -0,0 +1,10 @@ + ['web', 'admin'], ], function ($router) { $router->resource('images', ImageController::class); + $router->resource('multiple-images', MultipleImageController::class); $router->resource('files', FileController::class); $router->resource('users', UserController::class); }); diff --git a/views/form/file.blade.php b/views/form/file.blade.php index dda0a04b11..105f3a6eae 100644 --- a/views/form/file.blade.php +++ b/views/form/file.blade.php @@ -5,7 +5,7 @@
    @include('admin::form.error') - + @include('admin::form.help-block') diff --git a/views/form/image.blade.php b/views/form/image.blade.php index dda0a04b11..db383dcd4e 100644 --- a/views/form/image.blade.php +++ b/views/form/image.blade.php @@ -5,7 +5,7 @@
    @include('admin::form.error') - + @include('admin::form.help-block') From d59c4c57fa2c12abbd493095bf2cfe8ebeb8af74 Mon Sep 17 00:00:00 2001 From: Song Date: Sun, 4 Dec 2016 15:47:56 +0000 Subject: [PATCH 0216/2161] Apply fixes from StyleCI --- src/Form.php | 4 ++-- src/Form/Field.php | 1 - src/Form/Field/File.php | 2 ++ src/Form/Field/Image.php | 2 ++ src/Form/Field/Select.php | 1 - tests/ImageUploadTest.php | 24 ++++++++++++------------ tests/controllers/UserController.php | 2 +- 7 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/Form.php b/src/Form.php index 1392c5b68d..7f3bb9da48 100644 --- a/src/Form.php +++ b/src/Form.php @@ -265,6 +265,7 @@ public function store() if ($validator = $this->validationFails($data)) { dump($validator->messages()); + return back()->withInput()->withErrors($validator->messages()); } @@ -710,10 +711,9 @@ protected function setFieldValue($id) protected function validationFails($input) { foreach ($this->builder->fields() as $field) { - if (!$validator = $field->validate($input)) { continue; - }; + } if (($validator instanceof Validator) && !$validator->passes()) { return $validator; diff --git a/src/Form/Field.php b/src/Form/Field.php index 8fbc64bdb3..1fef45306a 100644 --- a/src/Form/Field.php +++ b/src/Form/Field.php @@ -306,7 +306,6 @@ public function rules($rules = null) return $this; } - protected function getRules() { return $this->rules; diff --git a/src/Form/Field/File.php b/src/Form/Field/File.php index 8dc0a3ba8c..510c3beb14 100644 --- a/src/Form/Field/File.php +++ b/src/Form/Field/File.php @@ -197,6 +197,7 @@ public function validate(array $input) * Hydrate the files array. * * @param array $value + * * @return array */ protected function hydrateFiles(array $value) @@ -382,6 +383,7 @@ public function objectUrl($path) * Initialize the caption. * * @param string $caption + * * @return string */ protected function initialCaption($caption) diff --git a/src/Form/Field/Image.php b/src/Form/Field/Image.php index 8f51bab70e..126042f2f5 100644 --- a/src/Form/Field/Image.php +++ b/src/Form/Field/Image.php @@ -75,6 +75,7 @@ public function executeCalls($target) * Build a preview item. * * @param string $image + * * @return string */ protected function buildPreviewItem($image) @@ -99,6 +100,7 @@ public function render() * * @param string $method * @param array $arguments + * * @return $this */ public function __call($method, $arguments) diff --git a/src/Form/Field/Select.php b/src/Form/Field/Select.php index e45e729e7c..2e00b50b8b 100644 --- a/src/Form/Field/Select.php +++ b/src/Form/Field/Select.php @@ -22,7 +22,6 @@ public function render() $this->script = "$(\"#{$this->id}\").select2({allowClear: true});"; } - if (is_callable($this->options)) { $options = call_user_func($this->options, $this->value); $this->options($options); diff --git a/tests/ImageUploadTest.php b/tests/ImageUploadTest.php index 4f821dbecc..8154a19051 100644 --- a/tests/ImageUploadTest.php +++ b/tests/ImageUploadTest.php @@ -83,9 +83,9 @@ public function testRemoveImage() $this->assertEquals($this->fileCountInImageDir(), 6); $this->call( - 'PUT', # $method - '/admin/images/1', # $action - ['image2_action' => 1, 'image5_action' => 1] # $parameters + 'PUT', // $method + '/admin/images/1', // $action + ['image2_action' => 1, 'image5_action' => 1] // $parameters ); $this->assertRedirectedTo('/admin/images'); @@ -203,9 +203,9 @@ public function testUploadMultipleImage() $files = ['pictures' => array_pad([], $size, $file)]; $this->call( - 'POST', # $method - '/admin/multiple-images', # $action - [], # $parameters + 'POST', // $method + '/admin/multiple-images', // $action + [], // $parameters [], $files ); @@ -241,9 +241,9 @@ public function testRemoveMultipleFiles() $files = ['pictures' => array_pad([], $size, $file)]; $this->call( - 'POST', # $method - '/admin/multiple-images', # $action - [], # $parameters + 'POST', // $method + '/admin/multiple-images', // $action + [], // $parameters [], $files ); @@ -252,9 +252,9 @@ public function testRemoveMultipleFiles() // remove files $this->call( - 'PUT', # $method - '/admin/multiple-images/1', # $action - ['pictures_action' => 1] # $parameters + 'PUT', // $method + '/admin/multiple-images/1', // $action + ['pictures_action' => 1] // $parameters ); $this->assertEquals($this->fileCountInImageDir(), 0); diff --git a/tests/controllers/UserController.php b/tests/controllers/UserController.php index 37ce695f28..99af59e1b2 100644 --- a/tests/controllers/UserController.php +++ b/tests/controllers/UserController.php @@ -141,7 +141,7 @@ protected function form() $form->datetime('profile.start_at'); $form->datetime('profile.end_at'); - $form->multipleSelect('tags', 'Tags')->options(Tag::all()->pluck('name', 'id'));//->rules('max:10|min:3'); + $form->multipleSelect('tags', 'Tags')->options(Tag::all()->pluck('name', 'id')); //->rules('max:10|min:3'); $form->display('created_at', 'Created At'); $form->display('updated_at', 'Updated At'); From 308b20f413f7aad8abccdcf449aa515cc66ad36e Mon Sep 17 00:00:00 2001 From: z-song Date: Mon, 5 Dec 2016 12:02:22 +0800 Subject: [PATCH 0217/2161] support disable deletion in form --- src/Form.php | 21 ++++++++++++++++++ src/Form/Builder.php | 33 ++++++++++++++++++++++++++++ tests/FileUploadTest.php | 2 +- tests/ImageUploadTest.php | 2 +- tests/UserFormTest.php | 7 ++++-- tests/controllers/UserController.php | 4 +++- views/form.blade.php | 3 +++ 7 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/Form.php b/src/Form.php index 7f3bb9da48..891a9f17b7 100644 --- a/src/Form.php +++ b/src/Form.php @@ -114,6 +114,13 @@ class Form */ protected $callable; + /** + * Allow delete item in form page. + * + * @var bool + */ + protected $allowDeletion = true; + /** * Available fields. * @@ -181,6 +188,20 @@ public function builder() return $this->builder; } + /** + * Disable deletion in form page. + * + * @return $this + */ + public function disableDeletion() + { + $this->builder->disableDeletion(); + + $this->allowDeletion = false; + + return $this; + } + /** * Generate a edit form. * diff --git a/src/Form/Builder.php b/src/Form/Builder.php index f5335b48a5..38b493e75d 100644 --- a/src/Form/Builder.php +++ b/src/Form/Builder.php @@ -45,6 +45,13 @@ class Builder */ protected $mode = 'create'; + /** + * Allow delete item in form page. + * + * @var bool + */ + protected $allowDeletion = true; + /** * Builder constructor. * @@ -139,6 +146,28 @@ public function title() return ''; } + /** + * Disable deletion in form page. + * + * @return $this + */ + public function disableDeletion() + { + $this->allowDeletion = false; + + return $this; + } + + /** + * If allow deletion in form page. + * + * @return bool + */ + public function allowDeletion() + { + return $this->allowDeletion; + } + /** * Determine if form fields has files. * @@ -257,6 +286,10 @@ public function render() 'resource' => $this->form->resource($slice), ]; + if ($this->mode == static::MODE_CREATE) { + $this->disableDeletion(); + } + return view('admin::form', $vars)->render(); } diff --git a/tests/FileUploadTest.php b/tests/FileUploadTest.php index a24100580b..89f9ac0356 100644 --- a/tests/FileUploadTest.php +++ b/tests/FileUploadTest.php @@ -28,7 +28,7 @@ public function testFileUploadPage() ->seeElement('input[name=file5]') ->seeElement('input[name=file6]') ->seeInElement('a[href="https://github.com/admin/files"]', 'List') - ->seeInElement('a[class*=item_delete]', 'Delete') + ->dontSeeElement('a[class*=item_delete]') ->seeElement('input[type=reset][value=Reset]') ->seeInElement('button[type=submit]', 'Submit'); } diff --git a/tests/ImageUploadTest.php b/tests/ImageUploadTest.php index 8154a19051..1aa71d8f66 100644 --- a/tests/ImageUploadTest.php +++ b/tests/ImageUploadTest.php @@ -35,7 +35,7 @@ public function testImageUploadPage() ->seeElement('input[name=image5]') ->seeElement('input[name=image6]') ->seeInElement('a[href="https://github.com/admin/images"]', 'List') - ->seeInElement('a[class*=item_delete]', 'Delete') + ->dontSeeElement('a[class*=item_delete]') ->seeElement('input[type=reset][value=Reset]') ->seeInElement('button[type=submit]', 'Submit'); } diff --git a/tests/UserFormTest.php b/tests/UserFormTest.php index 42f123e095..c5f44c18e4 100644 --- a/tests/UserFormTest.php +++ b/tests/UserFormTest.php @@ -33,7 +33,8 @@ public function testCreatePage() ->seeInElement('span[class=help-block]', 'Please input your postcode') ->seeElement('span[class=help-block] i[class*=fa-image]') ->seeInElement('span[class=help-block]', '上传头像') - ->seeElement("select[name='tags[]'][multiple=multiple]"); + ->seeElement("select[name='tags[]'][multiple=multiple]") + ->dontSeeElement('a[class*=item_delete]'); } public function testSubmitForm() @@ -129,7 +130,9 @@ public function testEditForm() ->seeElement("input[type=text][name='profile[color]'][value='{$user->profile->color}']") ->seeElement("input[type=text][name='profile[start_at]'][value='{$user->profile->start_at}']") ->seeElement("input[type=text][name='profile[end_at]'][value='{$user->profile->end_at}']") - ->seeElement("select[name='tags[]'][multiple=multiple]"); + ->seeElement("select[name='tags[]'][multiple=multiple]") + ->dontSeeElement('a[class*=item_delete]'); + $this->assertCount(50, $this->crawler()->filter("select[name='tags[]'] option")); $this->assertCount(5, $this->crawler()->filter("select[name='tags[]'] option[selected]")); diff --git a/tests/controllers/UserController.php b/tests/controllers/UserController.php index 99af59e1b2..253af91188 100644 --- a/tests/controllers/UserController.php +++ b/tests/controllers/UserController.php @@ -122,8 +122,10 @@ protected function grid() protected function form() { return Admin::form(User::class, function (Form $form) { - $form->display('id', 'ID'); + $form->disableDeletion(); + + $form->display('id', 'ID'); $form->text('username'); $form->email('email')->rules('required'); $form->mobile('mobile'); diff --git a/views/form.blade.php b/views/form.blade.php index 0fd6f198f6..1671f18e93 100644 --- a/views/form.blade.php +++ b/views/form.blade.php @@ -3,9 +3,12 @@

    {{ $form->title() }}

    + @if($form->allowDeletion()) + @endif + From 8e8f55108b2217df3f7563fe5a02a8ec2b6808c5 Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 23 Nov 2016 18:23:21 +0800 Subject: [PATCH 0218/2161] add comments to bootstrap.php --- src/Commands/stubs/bootstrap.stub | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Commands/stubs/bootstrap.stub b/src/Commands/stubs/bootstrap.stub index b3d9bbc7f3..400e17dc0c 100644 --- a/src/Commands/stubs/bootstrap.stub +++ b/src/Commands/stubs/bootstrap.stub @@ -1 +1,20 @@ + * + * Bootstraper for Admin. + * + * Here you can remove builtin form field: + * Encore\Admin\Form::forget(['map', 'editor']); + * + * Or extend custom form field: + * Encore\Admin\Form::extend('php', PHPEditor::class); + * + * Or require js and css assets: + * Admin::css('/packages/prettydocs/css/styles.css'); + * Admin::js('/packages/prettydocs/js/main.js'); + * + */ + From 491e6b4e836894a1a4afda035c2e45bbea2a28df Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 23 Nov 2016 18:23:57 +0800 Subject: [PATCH 0219/2161] add tests --- tests/UserGridTest.php | 55 ++++++++++++++++++- tests/controllers/UserController.php | 20 ++++++- .../2016_11_22_093148_create_test_tables.php | 15 +++++ tests/models/Tag.php | 15 +++++ tests/models/User.php | 5 ++ tests/seeds/UserTableSeeder.php | 1 + tests/seeds/factory.php | 6 ++ 7 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 tests/models/Tag.php diff --git a/tests/UserGridTest.php b/tests/UserGridTest.php index 8781aa58a4..ce7dbc6ed9 100644 --- a/tests/UserGridTest.php +++ b/tests/UserGridTest.php @@ -51,6 +51,7 @@ protected function seedsTable($count = 100) ->create() ->each(function ($u) { $u->profile()->save(factory(\Tests\Models\Profile::class)->make()); + $u->tags()->saveMany(factory(\Tests\Models\Tag::class, 5)->make()); }); } @@ -63,12 +64,29 @@ public function testGridWithData() $this->assertCount(100, UserModel::all()); $this->assertCount(100, ProfileModel::all()); + } + + public function testGridPagination() + { + $this->seedsTable(65); + + $this->visit('admin/users') + ->see('All users'); $this->click(2)->seePageIs('admin/users?page=2'); + $this->assertCount(20, $this->crawler()->filter('td a i[class*=fa-edit]')); + $this->click(3)->seePageIs('admin/users?page=3'); + $this->assertCount(20, $this->crawler()->filter('td a i[class*=fa-edit]')); + $this->click(4)->seePageIs('admin/users?page=4'); - $this->click(5)->seePageIs('admin/users?page=5'); + $this->assertCount(5, $this->crawler()->filter('td a i[class*=fa-edit]')); + + $this->visit('admin/users?page=5')->seePageIs('admin/users?page=5'); + $this->assertCount(0, $this->crawler()->filter('td a i[class*=fa-edit]')); + $this->click(1)->seePageIs('admin/users?page=1'); + $this->assertCount(20, $this->crawler()->filter('td a i[class*=fa-edit]')); } public function testIsFileter() @@ -139,4 +157,39 @@ public function testFilterRelation() ->seeInElement('td', $user->start_at) ->seeInElement('td', $user->end_at); } + + public function testHasManyRelation() + { + factory(\Tests\Models\User::class, 10) + ->create() + ->each(function ($u) { + $u->profile()->save(factory(\Tests\Models\Profile::class)->make()); + $u->tags()->saveMany(factory(\Tests\Models\Tag::class, 5)->make()); + }); + + $this->visit('admin/users') + ->seeElement('td code'); + + $this->assertCount(50, $this->crawler()->filter('td code')); + } + + public function testGridActions() + { + $this->seedsTable(15); + + $this->visit('admin/users'); + + $this->assertCount(15, $this->crawler()->filter('td a i[class*=fa-edit]')); + $this->assertCount(15, $this->crawler()->filter('td a i[class*=fa-trash]')); + } + + public function testGridRows() + { + $this->seedsTable(10); + + $this->visit('admin/users') + ->seeInElement('td a[class*=btn]', 'detail'); + + $this->assertCount(5, $this->crawler()->filter('td a[class*=btn]')); + } } diff --git a/tests/controllers/UserController.php b/tests/controllers/UserController.php index eaa6577dfe..d24eea0094 100644 --- a/tests/controllers/UserController.php +++ b/tests/controllers/UserController.php @@ -79,12 +79,22 @@ protected function grid() return ""; }); $grid->profile()->postcode('Post code'); - $grid->profile()->address(); + $grid->profile('Address')->value(function ($profile) { + return $profile['address']; + }); $grid->position('Position'); $grid->profile()->color(); $grid->profile()->start_at('开始时间'); $grid->profile()->end_at('结束时间'); + $grid->tags()->value(function ($tags) { + $tags = collect($tags)->map(function ($tag) { + return "{$tag['name']}"; + })->toArray(); + + return join('', $tags); + }); + $grid->created_at(); $grid->updated_at(); @@ -95,6 +105,14 @@ protected function grid() $filter->between('profile.start_at')->datetime(); $filter->between('profile.end_at')->datetime(); }); + + $grid->rows(function (Grid\Row $row) { + if ($row->id % 2 == 0) { + $row->actions()->add(function ($row) { + return "detail"; + }); + } + }); }); } diff --git a/tests/migrations/2016_11_22_093148_create_test_tables.php b/tests/migrations/2016_11_22_093148_create_test_tables.php index e3c79eec75..753981ac3c 100644 --- a/tests/migrations/2016_11_22_093148_create_test_tables.php +++ b/tests/migrations/2016_11_22_093148_create_test_tables.php @@ -59,6 +59,19 @@ public function up() $table->timestamps(); }); + + Schema::create('test_tags', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->timestamps(); + }); + + Schema::create('test_user_tags', function (Blueprint $table) { + $table->integer('user_id'); + $table->integer('tag_id'); + $table->index(['user_id', 'tag_id']); + $table->timestamps(); + }); } /** @@ -72,5 +85,7 @@ public function down() Schema::drop('test_files'); Schema::drop('test_users'); Schema::drop('test_user_profiles'); + Schema::drop('test_tags'); + Schema::drop('test_user_tags'); } } diff --git a/tests/models/Tag.php b/tests/models/Tag.php new file mode 100644 index 0000000000..6750cd2873 --- /dev/null +++ b/tests/models/Tag.php @@ -0,0 +1,15 @@ +belongsToMany(User::class, 'test_user_tags', 'tag_id', 'user_id'); + } +} diff --git a/tests/models/User.php b/tests/models/User.php index 8824aed53b..228a2d7ae4 100644 --- a/tests/models/User.php +++ b/tests/models/User.php @@ -24,4 +24,9 @@ public function getPositionAttribute() { return "{$this->profile->latitude} {$this->profile->longitude}"; } + + public function tags() + { + return $this->belongsToMany(Tag::class, 'test_user_tags', 'user_id', 'tag_id'); + } } diff --git a/tests/seeds/UserTableSeeder.php b/tests/seeds/UserTableSeeder.php index 945291bac3..97ffeb7b46 100644 --- a/tests/seeds/UserTableSeeder.php +++ b/tests/seeds/UserTableSeeder.php @@ -12,6 +12,7 @@ public function run() ->create() ->each(function ($u) { $u->profile()->save(factory(\Tests\Models\Profile::class)->make()); + $u->tags()->saveMany(factory(\Tests\Models\Tag::class, 5)->make()); }); } } diff --git a/tests/seeds/factory.php b/tests/seeds/factory.php index fcfa461d2a..72e080e1a7 100644 --- a/tests/seeds/factory.php +++ b/tests/seeds/factory.php @@ -28,3 +28,9 @@ 'end_at' => $faker->dateTime, ]; }); + +$factory->define(Tests\Models\Tag::class, function (Faker $faker) { + return [ + 'name' => $faker->word, + ]; +}); From 74e056f537d0284f8f3c74bf891e0fe4122dd926 Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 24 Nov 2016 18:36:21 +0800 Subject: [PATCH 0220/2161] fix pjax timeout issue. --- views/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/index.blade.php b/views/index.blade.php index 4d2cabfe12..f065f26aad 100644 --- a/views/index.blade.php +++ b/views/index.blade.php @@ -72,8 +72,8 @@ $.noty.defaults.layout = 'topRight'; $.noty.defaults.theme = 'relax'; + $.pjax.defaults.timeout = 5000; $(document).pjax('a:not(a[target="_blank"])', { - timeout: 5000, container: '#pjax-container' }); From da40ca53a8472e0d8b431160dd04baaad14c5972 Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 25 Nov 2016 11:22:34 +0800 Subject: [PATCH 0221/2161] refactoring --- src/Grid.php | 144 ++++++++++++----- src/Grid/Column.php | 226 +++++++++++++-------------- tests/controllers/UserController.php | 4 +- 3 files changed, 217 insertions(+), 157 deletions(-) diff --git a/src/Grid.php b/src/Grid.php index 528523423d..7adfa716cf 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -315,7 +315,7 @@ public function build() $data = $this->processFilter(); $this->columns->map(function (Column $column) use (&$data) { - $data = $column->map($data); + $data = $column->fill($data); $this->columnNames[] = $column->getName(); }); @@ -549,6 +549,108 @@ public function resource($path = null) return app('router')->current()->getPath(); } + /** + * Handle table column for grid. + * + * @param string $method + * @param string $label + * + * @return bool|Column + */ + protected function handleTableColumn($method, $label) + { + $connection = $this->model()->eloquent()->getConnectionName(); + + if (Schema::connection($connection)->hasColumn($this->model()->getTable(), $method)) { + return $this->addColumn($method, $label); + } + + return false; + } + + /** + * Handle get mutator column for grid. + * + * @param string $method + * @param string $label + * + * @return bool|Column + */ + protected function handleGetMutatorColumn($method, $label) + { + if ($this->model()->eloquent()->hasGetMutator($method)) { + return $this->addColumn($method, $label); + } + + return false; + } + + /** + * Handle relation column for grid. + * + * @param string $method + * @param string $label + * + * @return bool|Column + */ + protected function handleRelationColumn($method, $label) + { + $model = $this->model()->eloquent(); + + if (! method_exists($model, $method)) { + return false; + } + + if (! ($relation = $model->$method()) instanceof Relation) { + return false; + } + + if ($relation instanceof HasOne || $relation instanceof BelongsTo) { + $this->model()->with($method); + + return $this->addColumn($method, $label)->setRelation($method); + } + + if ($relation instanceof HasMany || $relation instanceof BelongsToMany || $relation instanceof MorphToMany) { + $this->model()->with($method); + + return $this->addColumn($method, $label); + } + + return false; + } + + /** + * Dynamically add columns to the grid view. + * + * @param $method + * @param $arguments + * + * @return $this|Column + */ + public function __call($method, $arguments) + { + $label = isset($arguments[0]) ? $arguments[0] : ucfirst($method); + + if ($this->model()->eloquent() instanceof MongodbModel) { + return $this->addColumn($method, $label); + } + + if ($column = $this->handleTableColumn($method, $label)) { + return $column; + } + + if ($column = $this->handleGetMutatorColumn($method, $label)) { + return $column; + } + + if ($column = $this->handleRelationColumn($method, $label)) { + return $column; + } + + return $this->addColumn($method, $label); + } + /** * Add variables to grid view. * @@ -606,46 +708,6 @@ public function render() return view($this->view, $this->variables())->render(); } - /** - * Dynamically add columns to the grid view. - * - * @param $method - * @param $arguments - * - * @return $this|Column - */ - public function __call($method, $arguments) - { - $label = isset($arguments[0]) ? $arguments[0] : ucfirst($method); - - if ($this->model()->eloquent() instanceof MongodbModel) { - return $this->addColumn($method, $label); - } - - $connection = $this->model()->eloquent()->getConnectionName(); - if (Schema::connection($connection)->hasColumn($this->model()->getTable(), $method)) { - return $this->addColumn($method, $label); - } - - if ($this->model()->eloquent()->hasGetMutator($method)) { - return $this->addColumn($method, $label); - } - - $relation = $this->model()->eloquent()->$method(); - - if ($relation instanceof HasOne || $relation instanceof BelongsTo) { - $this->model()->with($method); - - return $this->addColumn($method, $label)->setRelation($method); - } - - if ($relation instanceof HasMany || $relation instanceof BelongsToMany || $relation instanceof MorphToMany) { - $this->model()->with($method); - - return $this->addColumn($method, $label); - } - } - /** * Js code for grid. * diff --git a/src/Grid/Column.php b/src/Grid/Column.php index 52bf77e9c9..8232e55b10 100644 --- a/src/Grid/Column.php +++ b/src/Grid/Column.php @@ -56,18 +56,18 @@ class Column protected $attributes = []; /** - * Value wrapper. + * Value callback. * * @var \Closure */ - protected $valueWrapper; + protected $valueCallback; /** - * Html wrapper. + * Html callback. * * @var array */ - protected $htmlWrappers = []; + protected $htmlCallbacks = []; /** * Relation name. @@ -136,42 +136,6 @@ public function getLabel() return $this->label; } - /** - * Add a value wrapper. - * - * @param callable $callable - * - * @return $this - */ - public function value(Closure $callable) - { - $this->valueWrapper = $callable; - - return $this; - } - - /** - * Alias for value method. - * - * @param callable $callable - * - * @return $this - */ - public function display(Closure $callable) - { - return $this->value($callable); - } - - /** - * If has a value wrapper. - * - * @return bool - */ - protected function hasValueWrapper() - { - return (bool) $this->valueWrapper; - } - /** * Set relation. * @@ -196,56 +160,6 @@ protected function isRelation() return (bool) $this->relation; } - /** - * Map all data to every column. - * - * @param array $data - * - * @return mixed - */ - public function map($data) - { - foreach ($data as &$item) { - $this->original = $value = array_get($item, $this->name); - - $value = $this->htmlEntityEncode($value); - - array_set($item, $this->name, $value); - - if ($this->hasValueWrapper()) { - $value = call_user_func($this->valueWrapper, $this->original); - array_set($item, $this->name, $value); - } - - if ($this->hasHtmlWrapper()) { - $value = $this->htmlWrap($value, $item); - array_set($item, $this->name, $value); - } - } - - return $data; - } - - /** - * Convert characters to HTML entities recursively. - * - * @param array|string $item - * - * @return mixed - */ - protected function htmlEntityEncode($item) - { - if (is_array($item)) { - array_walk_recursive($item, function (&$value) { - $value = htmlentities($value); - }); - } else { - $item = htmlentities($item); - } - - return $item; - } - /** * Mark this column as sortable. * @@ -267,9 +181,9 @@ public function sortable() */ public function badge($style = 'red') { - $wrapper = "{value}"; + $callback = "{value}"; - $this->htmlWrapper($wrapper); + $this->htmlCallback($callback); return $this; } @@ -283,9 +197,9 @@ public function badge($style = 'red') */ public function label($style = 'success') { - $wrapper = "{value}"; + $callback = "{value}"; - $this->htmlWrapper($wrapper); + $this->htmlCallback($callback); return $this; } @@ -304,9 +218,9 @@ public function link($href = '', $target = '_blank') $href = '{$value}'; } - $wrapper = "{value}"; + $callback = "{value}"; - $this->htmlWrapper($wrapper); + $this->htmlCallback($callback); return $this; } @@ -330,9 +244,9 @@ public function button($style = 'default') $style = 'btn-'.$style; } - $wrapper = "{value}"; + $callback = "{value}"; - $this->htmlWrapper($wrapper); + $this->htmlCallback($callback); return $this; } @@ -358,7 +272,7 @@ public function progressBar($style = 'primary', $size = 'sm', $max = 100) $style = 'progress-bar-'.$style; } - $wrapper = <<
    @@ -368,7 +282,7 @@ public function progressBar($style = 'primary', $size = 'sm', $max = 100) EOT; - $this->htmlWrapper($wrapper); + $this->htmlCallback($callback); return $this; } @@ -386,9 +300,9 @@ public function image($server = '', $width = 200, $height = 200) { $server = $server ?: config('admin.upload.host'); - $wrapper = ""; + $callback = ""; - $this->htmlWrapper($wrapper); + $this->htmlCallback($callback); return $this; } @@ -403,33 +317,69 @@ public function editable() $editable = new Editable($this->name, func_get_args()); $editable->setResource($this->grid->resource()); - $this->htmlWrapper($editable->html()); + $this->htmlCallback($editable->html()); return $this; } /** - * Set html wrapper. + * Add a value callback. * - * @param $wrapper + * @param callable $callable + * + * @return $this + */ + public function value(Closure $callable) + { + $this->valueCallback = $callable->bindTo($this); + + return $this; + } + + /** + * Alias for value method. + * + * @param callable $callable + * + * @return $this + */ + public function display(Closure $callable) + { + return $this->value($callable); + } + + /** + * If has a value callback. + * + * @return bool */ - protected function htmlWrapper($wrapper) + protected function hasValueCallback() { - $this->htmlWrappers[] = $wrapper; + return (bool) $this->valueCallback; } /** - * If column has html wrapper. + * Set html callback. + * + * @param $callback + */ + protected function htmlCallback($callback) + { + $this->htmlCallbacks[] = $callback; + } + + /** + * If column has html callback. * * @return bool */ - protected function hasHtmlWrapper() + protected function hasHtmlCallback() { - return !empty($this->htmlWrappers); + return !empty($this->htmlCallbacks); } /** - * Wrap value with wrapper. + * Wrap value with callback. * * @param $value * @@ -437,8 +387,8 @@ protected function hasHtmlWrapper() */ protected function htmlWrap($value, $row = []) { - foreach ($this->htmlWrappers as $wrapper) { - $value = str_replace('{value}', $value, $wrapper); + foreach ($this->htmlCallbacks as $callback) { + $value = str_replace('{value}', $value, $callback); } $value = str_replace( @@ -451,6 +401,56 @@ protected function htmlWrap($value, $row = []) return $value; } + /** + * Fill all data to every column. + * + * @param array $data + * + * @return mixed + */ + public function fill(array $data) + { + foreach ($data as &$item) { + $this->original = $value = array_get($item, $this->name); + + $value = $this->htmlEntityEncode($value); + + array_set($item, $this->name, $value); + + if ($this->hasValueCallback()) { + $value = call_user_func($this->valueCallback, $this->original); + array_set($item, $this->name, $value); + } + + if ($this->hasHtmlCallback()) { + $value = $this->htmlWrap($value, $item); + array_set($item, $this->name, $value); + } + } + + return $data; + } + + /** + * Convert characters to HTML entities recursively. + * + * @param array|string $item + * + * @return mixed + */ + protected function htmlEntityEncode($item) + { + if (is_array($item)) { + array_walk_recursive($item, function (&$value) { + $value = htmlentities($value); + }); + } else { + $item = htmlentities($item); + } + + return $item; + } + /** * Create the column sorter. * diff --git a/tests/controllers/UserController.php b/tests/controllers/UserController.php index d24eea0094..d5a0b6a60b 100644 --- a/tests/controllers/UserController.php +++ b/tests/controllers/UserController.php @@ -79,9 +79,7 @@ protected function grid() return ""; }); $grid->profile()->postcode('Post code'); - $grid->profile('Address')->value(function ($profile) { - return $profile['address']; - }); + $grid->profile()->address(); $grid->position('Position'); $grid->profile()->color(); $grid->profile()->start_at('开始时间'); From a15e3fa018a60ea49f106cb83acbb7f628f5dd0a Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 25 Nov 2016 12:36:08 +0800 Subject: [PATCH 0222/2161] fix menu order save issue. --- src/Controllers/MenuController.php | 9 ++++ src/Menu/Menu.php | 78 ++++++++++++++---------------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/Controllers/MenuController.php b/src/Controllers/MenuController.php index c6a306c3bb..8dd62dc8e8 100644 --- a/src/Controllers/MenuController.php +++ b/src/Controllers/MenuController.php @@ -12,6 +12,7 @@ use Encore\Admin\Menu\Menu; use Encore\Admin\Widgets\Box; use Encore\Admin\Widgets\Callout; +use Illuminate\Http\Request; use Illuminate\Routing\Controller; class MenuController extends Controller @@ -106,6 +107,14 @@ public function destroy($id) */ public function store() { + if (Request::capture()->has('_order')) { + $menu = new Menu(new MenuModel()); + + return response()->json([ + 'status' => $menu->saveTree(Request::capture()->get('_order')), + ]); + } + return $this->form()->store(); } diff --git a/src/Menu/Menu.php b/src/Menu/Menu.php index f66cb1c02e..1e5cc99eb2 100644 --- a/src/Menu/Menu.php +++ b/src/Menu/Menu.php @@ -14,11 +14,6 @@ class Menu implements Renderable */ protected $items = []; - /** - * @var string - */ - protected $script; - /** * @var string */ @@ -43,29 +38,36 @@ public function __construct(Model $model = null) } /** - * Variables in tree template. + * Build menu tree presented by array. * - * @return array + * @param string $serialize + * + * @return bool */ - public function variables() + public function saveTree($serialize) { - return [ - 'items' => $this->model->toTree(), - 'id' => $this->elementId, - ]; + $tree = json_decode($serialize, true); + + if (json_last_error() != JSON_ERROR_NONE) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $this->model->saveTree($tree); + + return true; } /** * Build tree grid scripts. * - * @return void + * @return string */ protected function buildupScript() { $confirm = trans('admin::lang.delete_confirm'); $token = csrf_token(); - $this->script = << - + {!! Admin::js() !!} @@ -69,8 +70,12 @@ return params; }; - $.noty.defaults.layout = 'topRight'; - $.noty.defaults.theme = 'relax'; + toastr.options = { + closeButton: true, + progressBar: true, + showMethod: 'slideDown', + timeOut: 4000 + }; $.pjax.defaults.timeout = 5000; $.pjax.defaults.maxCacheLength = 0; From 7ff3f148c4551faac9b9f76696ad3c10d3d1e8d6 Mon Sep 17 00:00:00 2001 From: z-song Date: Sun, 25 Dec 2016 10:03:16 +0800 Subject: [PATCH 0323/2161] fix issues of radio and checkbox. --- src/Form/Field/Checkbox.php | 8 ++++++++ src/Form/Field/Radio.php | 39 +++++++++++++++++++++++++++++-------- views/form/radio.blade.php | 8 ++++++-- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/Form/Field/Checkbox.php b/src/Form/Field/Checkbox.php index 2e8a8e70a3..5320b985a2 100644 --- a/src/Form/Field/Checkbox.php +++ b/src/Form/Field/Checkbox.php @@ -14,6 +14,14 @@ class Checkbox extends MultipleSelect 'packages/admin/AdminLTE/plugins/iCheck/icheck.min.js', ]; + /** + * {@inheritdoc} + */ + public function prepare(array $value) + { + return $value; + } + /** * Set options. * diff --git a/src/Form/Field/Radio.php b/src/Form/Field/Radio.php index 7afda23642..a038006cb6 100644 --- a/src/Form/Field/Radio.php +++ b/src/Form/Field/Radio.php @@ -3,6 +3,7 @@ namespace Encore\Admin\Form\Field; use Encore\Admin\Form\Field; +use Illuminate\Contracts\Support\Arrayable; class Radio extends Field { @@ -14,21 +15,43 @@ class Radio extends Field 'packages/admin/AdminLTE/plugins/iCheck/icheck.min.js', ]; - protected $values; - - public function render() + /** + * Set options. + * + * @param array|callable|string $options + * + * @return $this + */ + public function options($options = []) { - $this->options['radioClass'] = 'iradio_minimal-blue'; + if ($options instanceof Arrayable) { + $options = $options->toArray(); + } - $this->script = "$('.{$this->id}').iCheck(".json_encode($this->options).');'; + $this->options = (array) $options; - return parent::render()->with(['values' => $this->values]); + return $this; } + /** + * Set options. + * + * @param array|callable|string $values + * + * @return $this + */ public function values($values) { - $this->values = $values; + return $this->options($values); + } + + /** + * {@inheritdoc} + */ + public function render() + { + $this->script = "$('.{$this->getElementClass()}').iCheck({radioClass:'iradio_minimal-blue'});"; - return $this; + return parent::render()->with(['options' => $this->options]); } } diff --git a/views/form/radio.blade.php b/views/form/radio.blade.php index e71304fba4..e507292f08 100644 --- a/views/form/radio.blade.php +++ b/views/form/radio.blade.php @@ -6,8 +6,12 @@ @include('admin::form.error') - @foreach($values as $option => $label) -  {{$label}}   + @foreach($options as $option => $label) +
    + +
    @endforeach @include('admin::form.help-block') From 1d86f0c23381f5c1371e6632e1138b8207935f11 Mon Sep 17 00:00:00 2001 From: z-song Date: Sun, 25 Dec 2016 10:03:47 +0800 Subject: [PATCH 0324/2161] add checkbox and radio displayer to grid. --- src/Grid/Displayers/AbstractDisplayer.php | 29 ++++++++- src/Grid/Displayers/Checkbox.php | 75 +++++++++++++++++++++++ src/Grid/Displayers/Radio.php | 69 +++++++++++++++++++++ 3 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 src/Grid/Displayers/Checkbox.php create mode 100644 src/Grid/Displayers/Radio.php diff --git a/src/Grid/Displayers/AbstractDisplayer.php b/src/Grid/Displayers/AbstractDisplayer.php index d85b7710d8..1464efa653 100644 --- a/src/Grid/Displayers/AbstractDisplayer.php +++ b/src/Grid/Displayers/AbstractDisplayer.php @@ -31,7 +31,7 @@ abstract class AbstractDisplayer * Create a new displayer instance. * * @param mixed $value - * @param Grid grid + * @param Grid $grid * @param Column $column * @param \stdClass $row */ @@ -43,11 +43,38 @@ public function __construct($value, Grid $grid, Column $column, \stdClass $row) $this->row = $row; } + /** + * Get key of current row. + * + * @return mixed + */ protected function getKey() { return $this->row->{$this->grid->getKeyName()}; } + /** + * Get url path of current resource. + * + * @return string + */ + public function getResource() + { + return $this->grid->resource(); + } + + /** + * Get translation. + * + * @param string $text + * + * @return string|\Symfony\Component\Translation\TranslatorInterface + */ + protected function trans($text) + { + return trans("admin::lang.$text"); + } + /** * Display method. * diff --git a/src/Grid/Displayers/Checkbox.php b/src/Grid/Displayers/Checkbox.php new file mode 100644 index 0000000000..bac5d2ef74 --- /dev/null +++ b/src/Grid/Displayers/Checkbox.php @@ -0,0 +1,75 @@ +column->getName(); + + if (is_string($this->value)) { + $this->value = explode(',', $this->value); + } + + foreach ($options as $value => $label) { + $checked = in_array($value, $this->value) ? 'checked' : ''; + $radios .= << + +
    +EOT; + } + + Admin::script($this->script()); + + return << + $radios + + + +EOT; + } + + protected function script() + { + $name = $this->column->getName(); + $token = csrf_token(); + + return <<getResource()}/" + $(this).data('key'), + type: "POST", + data: { + $name: values, + _token: '$token', + _method: 'PUT' + }, + success: function (data) { + toastr.success(data.message); + } + }); + + return false; +}); + +EOT; + + } +} \ No newline at end of file diff --git a/src/Grid/Displayers/Radio.php b/src/Grid/Displayers/Radio.php new file mode 100644 index 0000000000..5d3b1cb08f --- /dev/null +++ b/src/Grid/Displayers/Radio.php @@ -0,0 +1,69 @@ +column->getName(); + + foreach ($options as $value => $label) { + $checked = ($value == $this->value) ? 'checked' : ''; + $radios .= << + +
    +EOT; + } + + Admin::script($this->script()); + + return << + $radios + + + +EOT; + } + + protected function script() + { + $name = $this->column->getName(); + $token = csrf_token(); + + return <<getResource()}/" + $(this).data('key'), + type: "POST", + data: { + $name: value, + _token: '$token', + _method: 'PUT' + }, + success: function (data) { + toastr.success(data.message); + } + }); + + return false; +}); + +EOT; + + } +} \ No newline at end of file From ccd18d720231e3bbdb920f9d76390ac3540242d6 Mon Sep 17 00:00:00 2001 From: z-song Date: Sun, 25 Dec 2016 17:07:55 +0800 Subject: [PATCH 0325/2161] ui fix. --- src/Menu/Menu.php | 9 ++++++++- views/form/checkbox.blade.php | 6 ++++-- views/form/display.blade.php | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Menu/Menu.php b/src/Menu/Menu.php index 6544d94e79..d8c78ec52a 100644 --- a/src/Menu/Menu.php +++ b/src/Menu/Menu.php @@ -63,9 +63,13 @@ public function saveTree($serialize) */ protected function buildupScript() { - $confirm = trans('admin::lang.delete_confirm'); $token = csrf_token(); + $confirm = trans('admin::lang.delete_confirm'); + $saveSucceeded = trans('admin::lang.save_succeeded'); + $refreshSucceeded = trans('admin::lang.refresh_succeeded'); + $deleteSucceeded = trans('admin::lang.delete_succeeded'); + return << + + + +
    + + + + + + diff --git a/docs/zh/_sidebar.md b/docs/zh/_sidebar.md new file mode 100644 index 0000000000..2ced107b6e --- /dev/null +++ b/docs/zh/_sidebar.md @@ -0,0 +1,24 @@ +- [快速开始](/zh/quick-start.md) +- [路由配置](/zh/router.md) +- [菜单配置](/zh/menu.md) +- [显示布局](/zh/layout.md) +- [数据模型表格](/zh/model-grid.md) + - [列操作使用和扩展](/zh/model-grid-actions.md) + - [列的使用和扩展](/zh/model-grid-column.md) + - [自定义工具](/zh/grid-custom-tools.md) +- [数据模型表单](/zh/model-form.md) + - [图片/文件上传](/zh/form-upload.md) + - [form组件使用](/zh/model-form-fields.md) + - [form组件管理](/zh/field-management.md) + - [保存回调](/zh/model-form-callback.md) +- [数据模型树](/zh/model-tree.md) +- [组件](/zh/widgets/table.md) + - [表格](/zh/widgets/table.md) + - [表单](/zh/widgets/form.md) + - [盒子](/zh/widgets/box.md) + - [信息盒子](/zh/widgets/info-box.md) + - [选项卡](/zh/widgets/tab.md) + - [滑动相册](/zh/widgets/carousel.md) + - [折叠容器](/zh/widgets/collapse.md) +- [权限控制](/zh/permission.md) +- [常见问题](/zh/qa.md) \ No newline at end of file From 059642dea155188910d8e071b11fc008e90094cc Mon Sep 17 00:00:00 2001 From: z-song Date: Tue, 14 Feb 2017 15:49:55 +0800 Subject: [PATCH 0483/2161] update docs [skip ci] --- README.md | 3 ++- docs/en/model-form-fields.md | 2 +- docs/index.html | 3 +-- docs/zh/model-form-fields.md | 2 +- docs/zh/widgets/_sidebar.md | 24 ++++++++++++++++++++++++ 5 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 docs/zh/widgets/_sidebar.md diff --git a/README.md b/README.md index 419de45121..bef5c2f5a6 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ laravel-admin [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/z-song/laravel-admin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/z-song/laravel-admin/?branch=master) [![Packagist](https://img.shields.io/packagist/l/encore/laravel-admin.svg?maxAge=2592000)](https://packagist.org/packages/encore/laravel-admin) [![Total Downloads](https://img.shields.io/packagist/dt/encore/laravel-admin.svg?style=flat-square)](https://packagist.org/packages/encore/laravel-admin) +[![Awesome Laravel](https://img.shields.io/badge/Awesome-Laravel-brightgreen.svg)](https://github.com/z-song/laravel-admin) `laravel-admin` is administrative interface builder for laravel which can help you build CRUD backends just with few lines of code. @@ -13,7 +14,7 @@ laravel-admin Inspired by [SleepingOwlAdmin](https://github.com/sleeping-owl/admin) and [rapyd-laravel](https://github.com/zofe/rapyd-laravel). -[中文文档](/docs/zh/README.md) +[Documentation](http://z-song.github.io/laravel-admin/index.html#/) | [中文文档](http://z-song.github.io/laravel-admin/index.html#/zh/) Screenshots ------------ diff --git a/docs/en/model-form-fields.md b/docs/en/model-form-fields.md index 47c1ab6f8f..1dbc6ca324 100644 --- a/docs/en/model-form-fields.md +++ b/docs/en/model-form-fields.md @@ -218,7 +218,7 @@ The json returned from api `/admin/demo/options`: } ``` -#### textarea: +#### textarea ```php $form->textarea($column[, $label])->rows(10); ``` diff --git a/docs/index.html b/docs/index.html index d52eaa35bf..b59c8c5076 100644 --- a/docs/index.html +++ b/docs/index.html @@ -14,8 +14,7 @@ '/', '/en', '/zh' - ], - coverpage: false + ] } diff --git a/docs/zh/model-form-fields.md b/docs/zh/model-form-fields.md index 0536633843..347ef40c90 100644 --- a/docs/zh/model-form-fields.md +++ b/docs/zh/model-form-fields.md @@ -216,7 +216,7 @@ public function users(Request $request) } ``` -### textarea输入框: +### textarea输入框 ```php $form->textarea($column[, $label])->rows(10); ``` diff --git a/docs/zh/widgets/_sidebar.md b/docs/zh/widgets/_sidebar.md new file mode 100644 index 0000000000..2ced107b6e --- /dev/null +++ b/docs/zh/widgets/_sidebar.md @@ -0,0 +1,24 @@ +- [快速开始](/zh/quick-start.md) +- [路由配置](/zh/router.md) +- [菜单配置](/zh/menu.md) +- [显示布局](/zh/layout.md) +- [数据模型表格](/zh/model-grid.md) + - [列操作使用和扩展](/zh/model-grid-actions.md) + - [列的使用和扩展](/zh/model-grid-column.md) + - [自定义工具](/zh/grid-custom-tools.md) +- [数据模型表单](/zh/model-form.md) + - [图片/文件上传](/zh/form-upload.md) + - [form组件使用](/zh/model-form-fields.md) + - [form组件管理](/zh/field-management.md) + - [保存回调](/zh/model-form-callback.md) +- [数据模型树](/zh/model-tree.md) +- [组件](/zh/widgets/table.md) + - [表格](/zh/widgets/table.md) + - [表单](/zh/widgets/form.md) + - [盒子](/zh/widgets/box.md) + - [信息盒子](/zh/widgets/info-box.md) + - [选项卡](/zh/widgets/tab.md) + - [滑动相册](/zh/widgets/carousel.md) + - [折叠容器](/zh/widgets/collapse.md) +- [权限控制](/zh/permission.md) +- [常见问题](/zh/qa.md) \ No newline at end of file From 368267059ecdb9a1b662a405adabb21c4619bdfa Mon Sep 17 00:00:00 2001 From: z-song Date: Tue, 14 Feb 2017 16:32:55 +0800 Subject: [PATCH 0484/2161] update docs [skip ci] --- docs/zh/README.md | 172 +++++------------------------------------ docs/zh/quick-start.md | 63 ++++++++++++++- 2 files changed, 81 insertions(+), 154 deletions(-) diff --git a/docs/zh/README.md b/docs/zh/README.md index 19aeca01d6..0ca6b30e36 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -1,5 +1,4 @@ -laravel-admin -===== +# laravel-admin [![Build Status](https://travis-ci.org/z-song/laravel-admin.svg?branch=master)](https://travis-ci.org/z-song/laravel-admin) [![StyleCI](https://styleci.io/repos/48796179/shield)](https://styleci.io/repos/48796179) @@ -9,157 +8,27 @@ laravel-admin `laravel-admin` 是一个可以快速帮你构建后台管理的工具,它提供的页面组件和表单元素等功能,能帮助你使用很少的代码就实现功能完善的后台管理功能。 -[Demo](http://120.26.143.106/admin) 账号/密码:admin/admin +## 特性 -Inspired by [SleepingOwlAdmin](https://github.com/sleeping-owl/admin) and [rapyd-laravel](https://github.com/zofe/rapyd-laravel). ++ 内置用户和权限系统 ++ `model-grid`支持快速构建数据表格 ++ `model-form`支持快速构建数据表单 ++ `model-tree`支持快速构建树状数据 ++ 内置30+种form元素组件、以及支持扩展组件 ++ 支持`Laravel`的多种模型关系 ++ `mysql`、`mongodb`、`pgsql`等多数据库支持 ++ 支持引入第三方前端库 ++ 数据库和artisan命令行工具的web实现 ++ 支持自定义图表 ++ 多种常用web组件 ++ 支持本地和oss文件上传 -截图 ------------- +## Demo -![laravel-admin](https://cloud.githubusercontent.com/assets/1479100/19625297/3b3deb64-9947-11e6-807c-cffa999004be.jpg) +打开`http://120.26.143.106/admin`,用账号密码`admin/admin`登陆 -安装 ------------- +# 依赖 -首先确保安装好了`laravel`,并且数据库连接设置正确。 - -``` -Laravel 5.1 -composer require encore/laravel-admin "1.1.*" - -Laravel 5.2 -composer require encore/laravel-admin "1.2.*" - -Laravel 5.3 -composer require encore/laravel-admin "1.3.*" -``` - -在`config/app.php`加入`ServiceProvider`: - -``` -Encore\Admin\Providers\AdminServiceProvider::class -``` - -然后运行下面的命令来发布资源: - -``` -php artisan vendor:publish --tag=laravel-admin -``` - -在该命令会生成配置文件`config/admin.php`,可以在里面修改安装的地址、数据库连接、以及表名。 - -然后运行下面的命令完成安装: -``` -php artisan admin:install -``` - -启动服务后,在浏览器打开 `http://localhost/admin/` ,使用用户名 `admin` 和密码 `admin`登陆. - -默认配置 ------------- - -安装完成之后,`laravel-admin`所有的配置都在`config/admin.php`文件中。 - -使用文档 ------------- - -- [快速开始](/docs/zh/quick-start.md) -- [路由配置](/docs/zh/router.md) -- [菜单配置](/docs/zh/menu.md) -- [显示布局](/docs/zh/layout.md) -- [数据模型表格](/docs/zh/model-grid.md) - - [列操作使用和扩展](/docs/zh/model-grid-actions.md) - - [列的使用和扩展](/docs/zh/model-grid-column.md) - - [自定义工具](/docs/zh/grid-custom-tools.md) -- [数据模型表单](/docs/zh/model-form.md) - - [图片/文件上传](/docs/zh/form-upload.md) - - [form组件使用](/docs/zh/model-form-fields.md) - - [form组件管理](/docs/zh/field-management.md) - - [保存回调](/docs/zh/model-form-callback.md) -- [数据模型树](/docs/zh/model-tree.md) -- [组件](/docs/zh/widgets/table.md) - - [表格](/docs/zh/widgets/table.md) - - [表单](/docs/zh/widgets/form.md) - - [盒子](/docs/zh/widgets/box.md) - - [信息盒子](/docs/zh/widgets/info-box.md) - - [选项卡](/docs/zh/widgets/tab.md) - - [滑动相册](/docs/zh/widgets/carousel.md) - - [折叠容器](/docs/zh/widgets/collapse.md) - - 数据图表 TODO -- [权限控制](/docs/zh/permission.md) -- [常见问题](/docs/zh/qa.md) - -目录结构 ------------- -安装完成之后,后台的安装目录为`app/Admin`,之后大部分的后台开发编码工作都是在这个目录下进行。 - -``` -app/Admin -├── Controllers -│   ├── ExampleController.php -│   └── HomeController.php -├── bootstrap.php -└── routes.php -``` - -`app/Admin/routes.php`文件用来配置后台路由,详细使用请阅读[路由配置](/docs/zh/router.md)。 - -`app/Admin/bootstrap.php` 是`laravel-admin`的启动文件, 使用方法请参考文件里面的注释. - -`app/Admin/Controllers`目录用来存放后台控制器文件,该目录下的`HomeController.php`文件是后台首页的显示控制器,`ExampleController.php`为实例文件。 - -快速开始 ------------- - -用`Laravel`自带的`users`表举例,表结构为: -```sql -CREATE TABLE `users` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `password` varchar(60) COLLATE utf8_unicode_ci NOT NULL, - `remember_token` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, - `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', - `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', - PRIMARY KEY (`id`), - UNIQUE KEY `users_email_unique` (`email`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci -``` -对应的数据模型为文件 `App\User.php` - -`laravel-admin`可以通过使用以下几步来快速生成`users`表的`CURD`操作页面: - -### 1.添加路由器 - -使用下面的命令来创建一个对应`App\User`模型的路由器 -```php -php artisan admin:make UserController --model=App\\User - -// 在windows系统中 -php artisan admin:make UserController --model=App\User -``` - -上面的命令会创建路由器文件`app/Admin/Controllers/UserController.php`. - -### 2.添加路由配置 - -在`laravel-admin`的路由配置文件`app/Admin/routes.php`里添加一行: -``` -$router->resource('users', UserController::class); -``` - -### 3.添加左侧菜单栏连接 - -打开`http://localhost:8000/admin/auth/menu`,添加对应的menu - -然后就能在后台管理页面的左侧边栏看到用户管理页面的链接入口了。 - -### 4.创建表格表单 - -剩下的工作就是构建数据表格和表单了,打开 `app/Admin/Contollers/UserController.php`,找到`form()`和`grid()`方法,然添加构建代码,更多详细使用请查看[model-grid](/docs/zh/model-grid.md)和[model-form](/docs/zh/model-form.md)。 - -其它 ------------- `laravel-admin` 基于以下组件或者服务: + [Laravel](https://laravel.com/) @@ -177,11 +46,10 @@ $router->resource('users', UserController::class); + [bootstrap-number-input](https://github.com/wpic/bootstrap-number-input) + [fontawesome-iconpicker](https://github.com/itsjavi/fontawesome-iconpicker) -交流 ------------- +## 交流 + QQ群:278455482 +## License -License ------------- `laravel-admin` is licensed under [The MIT License (MIT)](LICENSE). diff --git a/docs/zh/quick-start.md b/docs/zh/quick-start.md index 9351c88bde..50f6f77ce5 100644 --- a/docs/zh/quick-start.md +++ b/docs/zh/quick-start.md @@ -1,5 +1,64 @@ -快速开始 ------------- +# 快速开始 + +## 安装 + +首先确保安装好了`laravel`,并且数据库连接设置正确。 + +``` +Laravel 5.1 +composer require encore/laravel-admin "1.1.*" + +Laravel 5.2 +composer require encore/laravel-admin "1.2.*" + +Laravel 5.3 +composer require encore/laravel-admin "1.3.*" +``` + +在`config/app.php`加入`ServiceProvider`: + +``` +Encore\Admin\Providers\AdminServiceProvider::class +``` + +然后运行下面的命令来发布资源: + +``` +php artisan vendor:publish --tag=laravel-admin +``` + +在该命令会生成配置文件`config/admin.php`,可以在里面修改安装的地址、数据库连接、以及表名。 + +然后运行下面的命令完成安装: +``` +php artisan admin:install +``` + +启动服务后,在浏览器打开 `http://localhost/admin/` ,使用用户名 `admin` 和密码 `admin`登陆. + +## 默认配置 + +安装完成之后,`laravel-admin`所有的配置都在`config/admin.php`文件中。 + +## 目录结构 +安装完成之后,后台的安装目录为`app/Admin`,之后大部分的后台开发编码工作都是在这个目录下进行。 + +``` +app/Admin +├── Controllers +│   ├── ExampleController.php +│   └── HomeController.php +├── bootstrap.php +└── routes.php +``` + +`app/Admin/routes.php`文件用来配置后台路由,详细使用请阅读[路由配置](/docs/zh/router.md)。 + +`app/Admin/bootstrap.php` 是`laravel-admin`的启动文件, 使用方法请参考文件里面的注释. + +`app/Admin/Controllers`目录用来存放后台控制器文件,该目录下的`HomeController.php`文件是后台首页的显示控制器,`ExampleController.php`为实例文件。 + +## 快速开始 用`Laravel`自带的`users`表举例,表结构为: ```sql From 798beaba248c1f046a552dddee644b65c74ec34e Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 16 Feb 2017 00:18:19 +0800 Subject: [PATCH 0485/2161] update docs --- docs/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/index.html b/docs/index.html index b59c8c5076..8eef1fd3dc 100644 --- a/docs/index.html +++ b/docs/index.html @@ -10,6 +10,8 @@ subMaxLevel: 3, homepage: 'en/README.md', name: 'Laravel-admin', + ga: 'UA-52301626-2', + repo: 'https://github.com/z-song/laravel-admin', search : [ '/', '/en', @@ -29,4 +31,5 @@ + From 35fe7d23c37caea4459c5f139a24773899041733 Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 17 Feb 2017 08:07:49 +0800 Subject: [PATCH 0486/2161] update docs [skip ci] --- docs/zh/_sidebar.md | 2 +- docs/zh/field-management.md | 62 ++++++++++++++++++++++++++++++++++++ docs/zh/form-upload.md | 2 +- docs/zh/grid-custom-tools.md | 54 +++++++++++++++++++++++++++++-- docs/zh/model-form-fields.md | 16 +++++----- docs/zh/model-grid.md | 11 ++++--- docs/zh/permission.md | 2 +- docs/zh/widgets/_sidebar.md | 2 +- 8 files changed, 133 insertions(+), 18 deletions(-) diff --git a/docs/zh/_sidebar.md b/docs/zh/_sidebar.md index 2ced107b6e..a0ceac27c7 100644 --- a/docs/zh/_sidebar.md +++ b/docs/zh/_sidebar.md @@ -3,7 +3,7 @@ - [菜单配置](/zh/menu.md) - [显示布局](/zh/layout.md) - [数据模型表格](/zh/model-grid.md) - - [列操作使用和扩展](/zh/model-grid-actions.md) + - [行的使用和扩展](/zh/model-grid-actions.md) - [列的使用和扩展](/zh/model-grid-column.md) - [自定义工具](/zh/grid-custom-tools.md) - [数据模型表单](/zh/model-form.md) diff --git a/docs/zh/field-management.md b/docs/zh/field-management.md index 682535dac1..b43a122440 100644 --- a/docs/zh/field-management.md +++ b/docs/zh/field-management.md @@ -187,3 +187,65 @@ $form->editor('content'); ``` > 组件类中指定了`admin::form.editor`作为视图文件,视图文件路径在`vendor/encore/laravel-admin/views/form/editor.blade.php`,如果需要修改视图文件,可以将上述视图文件拷贝到`resources/views`目录下自行修改,然后在组件类`app/Admin/Extensions/WangEditor.php`的`$view`属性指定刚才修改的view即可。 + +### 集成富文本编辑器ckeditor + +先下载[ckeditor](http://ckeditor.com/download) 并解压到/public目录,比如放在`/public/packages/`目录下。 + +然后新建扩展文件`app/Admin/Extensions/Form/CKEditor.php`: +```php +script = "$('textarea.{$this->getElementClass()}').ckeditor();"; + + return parent::render(); + } +} +``` + +新建view `resources/views/admin/ckeditor.blade.php`: +```php +
    + + + +
    + + @include('admin::form.error') + + + + @include('admin::form.help-block') + +
    +
    + +``` + +然后在`app/Admin/bootstrap.php`中引入扩展: +```php +use App\Admin\Extensions\Form\CKEditor; +use Encore\Admin\Form; + +Form::extend('ckeditor', CKEditor::class); +``` + +然后就能在form中使用了: +```php +$form->ckeditor('content'); +``` \ No newline at end of file diff --git a/docs/zh/form-upload.md b/docs/zh/form-upload.md index 9de62d3616..3c963d2f13 100644 --- a/docs/zh/form-upload.md +++ b/docs/zh/form-upload.md @@ -72,7 +72,7 @@ $form->image('picture')->name(function ($file) { ### 云盘上传 -如果需要上传到云存储,需要安装兼容`laravel storage`操作方式的driver,拿七牛云存储举例 +如果需要上传到云存储,需要安装对应`laravel storage`的适配器,拿七牛云存储举例 首先安装 [zgldh/qiniu-laravel-storage](https://github.com/zgldh/qiniu-laravel-storage) diff --git a/docs/zh/grid-custom-tools.md b/docs/zh/grid-custom-tools.md index a1bbb681b8..963fa39bcc 100644 --- a/docs/zh/grid-custom-tools.md +++ b/docs/zh/grid-custom-tools.md @@ -1,4 +1,4 @@ -# 自定义工具条 +# 自定义工具 在`model-grid`的头部默认有`批量删除`和`刷新`两个操作工具,如果有更多的操作需求,`model-grid`提供了自定义工具的功能,下面的示例添加一个性别分类选择的按钮组工具。 @@ -171,4 +171,54 @@ class PostController extends Controller $router->post('posts/release', 'PostController@release'); ``` -这样整个流程就完成了。 \ No newline at end of file +这样整个流程就完成了。 + +## 自定义导出 + +`model-grid`默认只有简单的csv文件的导出功能,如果想要控制导出格式或导出字段,可以用下面的方式自定义导出功能 + +新建导出类, 比如`app/Admin/Extensions/CustomExporter.php`: + +```php +getTable().'.csv'; + + // 这里获取数据 + dd($this->getData()); + + // 根据上面的数据拼接出导出数据, + $output = ''; + + // 在这里控制你想输出的格式,或者使用第三方库导出Excel文件 + $headers = [ + 'Content-Encoding' => 'UTF-8', + 'Content-Type' => 'text/csv;charset=UTF-8', + 'Content-Disposition' => "attachment; filename=\"$filename\"", + ]; + + // 导出文件, + response(rtrim($output, "\n"), 200, $headers)->send(); + + exit; + } +} +``` + +然后在grid中使用: +```php +use App\Admin\Extensions\CustomExporter; + +... + +$grid->exporter(new CustomExporter()); +``` +就好了,导出类的逻辑可参考 [CsvExporter.php](https://github.com/z-song/laravel-admin/blob/1.3/src/Grid/Exporters/CsvExporter.php) \ No newline at end of file diff --git a/docs/zh/model-form-fields.md b/docs/zh/model-form-fields.md index 347ef40c90..759ceed4e5 100644 --- a/docs/zh/model-form-fields.md +++ b/docs/zh/model-form-fields.md @@ -67,7 +67,7 @@ $form->tab('Basic info', function ($form) { ``` -## Basic Usage +## 基本使用 ### 文本输入框 @@ -95,7 +95,7 @@ $form->select('user_id')->options(function ($id) { })->ajax('/admin/api/users'); ``` -url `/admin/api/users`接口的代码: +API `/admin/api/users`接口的代码: ```php public function users(Request $request) @@ -173,7 +173,7 @@ $form->select('friends')->options(function ($ids) { })->ajax('/admin/api/users'); ``` -url `/admin/api/users`接口的代码: +API `/admin/api/users`接口的代码: ```php public function users(Request $request) @@ -257,7 +257,7 @@ $form->ip($column[, $label]); ### 电话号码输入框 ```php -$form->mobile($column[, $label])->format('999 9999 9999'); +$form->mobile($column[, $label])->options(['mask' => '999 9999 9999']); ``` ### 颜色选择框 @@ -328,7 +328,7 @@ $form->rate($column[, $label]); ### 图片上传 -使用图片上传功能之前需要先完成上传配置,请参考:[图片/文件上传](/docs/zh/form-upload.md). +使用图片上传功能之前需要先完成上传配置,请参考:[图片/文件上传](form-upload.md). 图片上传目录在文件`config/admin.php`中的`upload.image`中配置,如果目录不存在,需要创建该目录并开放写权限。 @@ -353,7 +353,7 @@ $form->image($column[, $label])->multiple(); ### 文件上传 -使用图片上传功能之前需要先完成上传配置,请参考:[图片/文件上传](/docs/zh/form-upload.md). +使用图片上传功能之前需要先完成上传配置,请参考:[图片/文件上传](form-upload.md). 文件上传目录在文件`config/admin.php`中的`upload.file`中配置,如果目录不存在,需要创建该目录并开放写权限。 ```php @@ -371,7 +371,7 @@ $form->file($column[, $label])->multiple(); ### 地图控件 -地图组件引用了网络资源,默认关闭,如果要开启这个组件参考[form组件管理](/docs/zh/field-management.md) +地图组件引用了网络资源,默认关闭,如果要开启这个组件参考[form组件管理](field-management.md) 地图控件,用来选择经纬度,`$latitude`, `$longitude`为经纬度字段,`Laravel`的`locale`设置为`zh_CN`的时候使用腾讯地图,否则使用Google地图: ```php @@ -386,7 +386,7 @@ $form->slider($column[, $label])->options(['max' => 100, 'min' => 1, 'step' => 1 ### 富文本编辑框 -编辑器组件引用了网络资源,默认关闭,如果要开启这个组件参考[form组件管理](/docs/zh/field-management.md). +编辑器组件引用了网络资源,默认关闭,如果要开启这个组件参考[form组件管理](field-management.md). ```php $form->editor($column[, $label]); diff --git a/docs/zh/model-grid.md b/docs/zh/model-grid.md index f6370e8f5a..6209350cac 100644 --- a/docs/zh/model-grid.md +++ b/docs/zh/model-grid.md @@ -68,7 +68,7 @@ echo $grid; ``` -## Basic Usage +## 基本使用方法 #### 添加列 ```php @@ -252,7 +252,10 @@ class User extends Model class Profile extends Model { - $this->belongsTo(User::class); + public function user() + { + $this->belongsTo(User::class); + } } ``` @@ -336,7 +339,7 @@ return Admin::grid(Post::class, function (Grid $grid) { $grid->title(); $grid->content(); - $grid->comments('评论数')->value(function ($comments) { + $grid->comments('评论数')->display(function ($comments) { $count = count($comments); return "{$count}"; }); @@ -424,7 +427,7 @@ return Admin::grid(User::class, function (Grid $grid) { $grid->username(); $grid->name(); - $grid->roles()->value(function ($roles) { + $grid->roles()->display(function ($roles) { $roles = array_map(function ($role) { return "{$role['name']}"; diff --git a/docs/zh/permission.md b/docs/zh/permission.md index 68f279be65..08b359bb84 100644 --- a/docs/zh/permission.md +++ b/docs/zh/permission.md @@ -37,7 +37,7 @@ Admin::user()->id; Admin::user()->roles; ``` -获取用户角色 +获取用户的权限 ```php Admin::user()->permissions; ``` diff --git a/docs/zh/widgets/_sidebar.md b/docs/zh/widgets/_sidebar.md index 2ced107b6e..a0ceac27c7 100644 --- a/docs/zh/widgets/_sidebar.md +++ b/docs/zh/widgets/_sidebar.md @@ -3,7 +3,7 @@ - [菜单配置](/zh/menu.md) - [显示布局](/zh/layout.md) - [数据模型表格](/zh/model-grid.md) - - [列操作使用和扩展](/zh/model-grid-actions.md) + - [行的使用和扩展](/zh/model-grid-actions.md) - [列的使用和扩展](/zh/model-grid-column.md) - [自定义工具](/zh/grid-custom-tools.md) - [数据模型表单](/zh/model-form.md) From 08226c43cdce4cb2fc5dac7d40cccdbc7ca327c5 Mon Sep 17 00:00:00 2001 From: z-song Date: Tue, 14 Feb 2017 00:07:35 +0800 Subject: [PATCH 0487/2161] Add helpers controller. --- src/Controllers/TerminalController.php | 268 +++++++++++++++++++++++++ views/index.blade.php | 3 + views/terminal/artisan.blade.php | 173 ++++++++++++++++ views/terminal/database.blade.php | 166 +++++++++++++++ 4 files changed, 610 insertions(+) create mode 100644 src/Controllers/TerminalController.php create mode 100644 views/terminal/artisan.blade.php create mode 100644 views/terminal/database.blade.php diff --git a/src/Controllers/TerminalController.php b/src/Controllers/TerminalController.php new file mode 100644 index 0000000000..469f70fafe --- /dev/null +++ b/src/Controllers/TerminalController.php @@ -0,0 +1,268 @@ +header('Artisan terminal'); + + $content->row(view('admin::terminal.artisan', ['commands' => $this->organizedCommands()])); + }); + } + + public function runArtisan() + { + $command = Request::get('c', 'list'); + + // If Exception raised. + if (1 === Artisan::handle( + new ArgvInput(explode(' ', 'artisan ' . trim($command))), + $output = new StringOutput() + )) { + return $this->renderException(new Exception($output->getContent())); + } + + return sprintf("
    %s
    ", $output->getContent()); + } + + public function database() + { + return Admin::content(function (Content $content) { + + $content->header('Database terminal'); + + $content->row(view('admin::terminal.database', ['connections' => $this->connections()])); + }); + } + + public function runDatabase() + { + $query = Request::get('q'); + + $connection = Request::get('c', config('database.default')); + + return $this->dispatchQuery($connection, $query); + } + + protected function getDumpedHtml($var) + { + ob_start(); + + dump($var); + + $content = ob_get_contents(); + + ob_get_clean(); + + return substr($content, strpos($content, '
     $_) {
    +            $dbs[] = [
    +                'option'   => $name,
    +                'value'    => "db:$name",
    +                'selected' => $name == config('database.default')
    +            ];
    +        }
    +
    +        $connections = array_filter(config('database.redis'), function ($config) {
    +            return is_array($config);
    +        });
    +
    +        foreach ($connections as $name => $_) {
    +            $redis[] = [
    +                'value'     => "redis:$name",
    +                'option'    => $name
    +            ];
    +        }
    +
    +        return compact('dbs', 'redis');
    +    }
    +
    +    protected function table(array $headers, $rows, $style = 'default')
    +    {
    +        $output = new StringOutput();
    +
    +        $table = new Table($output);
    +
    +        if ($rows instanceof Arrayable) {
    +            $rows = $rows->toArray();
    +        }
    +
    +        $table->setHeaders($headers)->setRows($rows)->setStyle($style)->render();
    +
    +        return $output->getContent();
    +    }
    +
    +    protected function dispatchQuery($connection, $query)
    +    {
    +        list($type, $connection) = explode(':', $connection);
    +
    +        if ($type == 'redis') {
    +            return $this->execRedisCommand($connection, $query);
    +        }
    +
    +        $config = config('database.connections.'.$connection);
    +
    +        if ($config['driver'] == 'mongodb') {
    +            return $this->execMongodbQuery($config, $query);
    +        }
    +
    +        /* @var \Illuminate\Database\Connection $connection */
    +        $connection = DB::connection($connection);
    +
    +        $connection->enableQueryLog();
    +
    +        try {
    +            $result = $connection->select(str_replace([';', "\G"], '', $query));
    +        } catch (Exception $exception) {
    +            return $this->renderException($exception);
    +        }
    +
    +        $log = current($connection->getQueryLog());
    +
    +        if (empty($result)) {
    +            return sprintf("
    Empty set (%s sec)
    \r\n",number_format($log['time'] / 1000, 2)); + } + + $result = json_decode(json_encode($result), true); + + if (Str::contains($query, "\G")) { + return $this->getDumpedHtml($result); + } + + return sprintf( + "
    %s \n%d %s in set (%s sec)
    \r\n", + $this->table(array_keys(current($result)), $result), + count($result), + count($result) == 1 ? 'row' : 'rows', + number_format($log['time'] / 1000, 2) + ); + } + + protected function execMongodbQuery($config, $query) + { + if(Str::contains($query, '.find(') && ! Str::contains($query, '.toArray(')) { + $query .= '.toArray()'; + } + + $manager = new Manager("mongodb://{$config['host']}:{$config['port']}"); + $command = new Command(['eval' => $query]); + + try { + $cursor = $manager->executeCommand($config['database'], $command); + } catch(Exception $exception) { + return $this->renderException($exception); + } + + $result = $cursor->toArray()[0]; + + $result = json_decode(json_encode($result), true); + + if(isset($result['errmsg'])) { + return $this->renderException(new Exception($result['errmsg'])); + } + + return $this->getDumpedHtml($result['retval']); + } + + protected function execRedisCommand($connection, $command) + { + $command = explode(' ', $command); + + try { + $result = Redis::connection($connection)->executeRaw($command); + } catch (Exception $exception) { + return $this->renderException($exception); + } + + if (is_string($result) && Str::startsWith($result, ['ERR ', 'WRONGTYPE '])) { + return $this->renderException(new Exception($result)); + } + + return $this->getDumpedHtml($result); + } + + protected function organizedCommands() + { + $commands = array_keys(Artisan::all()); + + $groups = $others = []; + + foreach ($commands as $command) { + $parts = explode(':', $command); + + if (isset($parts[1])) { + $groups[$parts[0]][] = $command; + } else { + $others[] = $command; + } + } + + foreach ($groups as $key => $group) { + if (count($group) === 1) { + $others[] = $group[0]; + + unset($groups[$key]); + } + } + + ksort($groups); + sort($others); + + return compact('groups', 'others'); + } + + protected function renderException(Exception $exception) + { + return sprintf( + "
       %s
    ", + str_replace("\n", "
    ", $exception->getMessage()) + ); + } +} + +class StringOutput extends Output +{ + public $output = ''; + + public function clear() + { + $this->output = ''; + } + + protected function doWrite($message, $newline) + { + $this->output .= $message.($newline ? "\n" : ''); + } + + public function getContent() + { + return trim($this->output); + } +} + diff --git a/views/index.blade.php b/views/index.blade.php index 4fcea59195..86bfc5f8dc 100644 --- a/views/index.blade.php +++ b/views/index.blade.php @@ -63,6 +63,9 @@ + +
    +
    + +
    +
    + + + +
    + + +
    + \ No newline at end of file diff --git a/views/terminal/database.blade.php b/views/terminal/database.blade.php new file mode 100644 index 0000000000..1de680b8e7 --- /dev/null +++ b/views/terminal/database.blade.php @@ -0,0 +1,166 @@ + + + +
    +
    + + +
    + +
    + + +
    + +
    +
    +
    + + + +
    + + +
    + \ No newline at end of file From 80e55cae3ed547e9b4a36fab97457695fa6924e0 Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 15 Feb 2017 18:41:52 +0800 Subject: [PATCH 0488/2161] helpers develop. --- src/Admin.php | 21 ++ src/Controllers/ScaffoldController.php | 95 +++++++ src/Controllers/TerminalController.php | 4 +- src/Scaffold/MigrationCreator.php | 77 +++++ src/Scaffold/ModelCreator.php | 53 ++++ src/Scaffold/stubs/create.stub | 30 ++ src/Scaffold/stubs/model.stub | 13 + views/{terminal => helpers}/artisan.blade.php | 0 .../{terminal => helpers}/database.blade.php | 0 views/helpers/scaffold.blade.php | 269 ++++++++++++++++++ 10 files changed, 560 insertions(+), 2 deletions(-) create mode 100644 src/Controllers/ScaffoldController.php create mode 100644 src/Scaffold/MigrationCreator.php create mode 100644 src/Scaffold/ModelCreator.php create mode 100755 src/Scaffold/stubs/create.stub create mode 100644 src/Scaffold/stubs/model.stub rename views/{terminal => helpers}/artisan.blade.php (100%) rename views/{terminal => helpers}/database.blade.php (100%) create mode 100644 views/helpers/scaffold.blade.php diff --git a/src/Admin.php b/src/Admin.php index 6925f66824..7a3100820b 100644 --- a/src/Admin.php +++ b/src/Admin.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Model as EloquentModel; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\Route; use InvalidArgumentException; /** @@ -288,6 +289,26 @@ public function getNavbar() } return $this->navbar; + } + + public function enableHelpersRoutes($attributes = []) + { + $attributes = array_merge([ + 'prefix' => 'admin/helpers', + 'middleware' => ['web', 'admin'] + ], $attributes); + + Route::group($attributes, function ($router) { + + + /* @var \Illuminate\Routing\Router $router */ + $router->get('terminal/database', 'Encore\Admin\Controllers\TerminalController@database'); + $router->post('terminal/database', 'Encore\Admin\Controllers\TerminalController@runDatabase'); + $router->get('terminal/artisan', 'Encore\Admin\Controllers\TerminalController@artisan'); + $router->post('terminal/artisan', 'Encore\Admin\Controllers\TerminalController@runArtisan'); + $router->get('scaffold', 'Encore\Admin\Controllers\ScaffoldController@index'); + $router->post('scaffold', 'Encore\Admin\Controllers\ScaffoldController@store'); + }); } } diff --git a/src/Controllers/ScaffoldController.php b/src/Controllers/ScaffoldController.php new file mode 100644 index 0000000000..bafd9697e4 --- /dev/null +++ b/src/Controllers/ScaffoldController.php @@ -0,0 +1,95 @@ +header('Scaffold'); + + $dbTypes = [ + 'string', + 'integer', + 'text', + 'float', + 'double', + 'decimal', + 'boolean', + 'date', + 'time', + 'dateTime', + 'timestamp', + 'char', + 'mediumText', + 'longText', + 'tinyInteger', + 'smallInteger', + 'mediumInteger', + 'bigInteger', + 'unsignedTinyInteger', + 'unsignedSmallInteger', + 'unsignedMediumInteger', + 'unsignedInteger', + 'unsignedBigInteger', + 'enum', + 'json', + 'jsonb', + 'dateTimeTz', + 'timeTz', + 'timestampTz', + 'nullableTimestamps', + 'binary', + 'ipAddress', + 'macAddress', + ]; + + $content->row(view('admin::helpers.scaffold', compact('dbTypes'))); + }); + } + + public function store(Request $request) + { + $paths = []; + + try { + + (new ModelCreator($request->get('table_name'), $request->get('model_name')))->create(); + + + $migrationName = 'create_'.$request->get('table_name').'_table'; + + $paths['migration'] = (new MigrationCreator(app('files')))->buildBluePrint( + $request->get('fields'), + $request->get('primary_key', 'id'), + $request->get('use_timestamps') == 'on', + $request->get('use_soft_deletes') == 'on' + )->create($migrationName, database_path('migrations'), $request->get('table_name')); + + } catch (\Exception $exception) { + return $this->backWithException($exception); + } + + return $paths; + } + + protected function backWithException(\Exception $exception) + { + $error = new MessageBag([ + 'title' => 'Error', + 'message' => $exception->getMessage(), + ]); + + return back()->withInput()->with(compact('error')); + } +} diff --git a/src/Controllers/TerminalController.php b/src/Controllers/TerminalController.php index 469f70fafe..75410fe87d 100644 --- a/src/Controllers/TerminalController.php +++ b/src/Controllers/TerminalController.php @@ -26,7 +26,7 @@ public function artisan() $content->header('Artisan terminal'); - $content->row(view('admin::terminal.artisan', ['commands' => $this->organizedCommands()])); + $content->row(view('admin::helpers.artisan', ['commands' => $this->organizedCommands()])); }); } @@ -51,7 +51,7 @@ public function database() $content->header('Database terminal'); - $content->row(view('admin::terminal.database', ['connections' => $this->connections()])); + $content->row(view('admin::helpers.database', ['connections' => $this->connections()])); }); } diff --git a/src/Scaffold/MigrationCreator.php b/src/Scaffold/MigrationCreator.php new file mode 100644 index 0000000000..5da69c15ca --- /dev/null +++ b/src/Scaffold/MigrationCreator.php @@ -0,0 +1,77 @@ +ensureMigrationDoesntAlreadyExist($name); + + $path = $this->getPath($name, $path); + + $stub = $this->files->get(__DIR__.'/stubs/create.stub'); + + $this->files->put($path, $this->populateStub($name, $stub, $table)); + + $this->firePostCreateHooks(); + + return $path; + } + + protected function populateStub($name, $stub, $table) + { + return str_replace( + ['DummyClass', 'DummyTable', 'DummyStructure'], + [$this->getClassName($name), $table, $this->bluePrint], + $stub + ); + } + + public function buildBluePrint($fields = [], $keyName = 'id', $useTimestamps = true, $softDeletes = false) + { + $fields = array_filter($fields, function ($field) { + return isset($field['name']) && !empty($field['name']); + }); + + if (empty($fields)) { + throw new \Exception('table fields can\'t be empty'); + } + + $rows[] = "\$table->increments('$keyName');\n"; + + foreach ($fields as $field) { + $column = "\$table->{$field['type']}('{$field['name']}')"; + + if ($field['key']) { + $column .= "->{$field['key']}()"; + } + + if (isset($field['default']) && $field['default']) { + $column .= "->default('{$field['default']}')"; + } + + if (array_get($field, 'nullable') == 'on') { + $column .= '->nullable()'; + } + + $rows[] = $column . ";\n"; + } + + if ($useTimestamps) { + $rows[] = "\$table->timestamps();\n"; + } + + if ($softDeletes) { + $rows[] = "\$table->softDeletes();\n"; + } + + $this->bluePrint = str_repeat(' ', 12) . join(str_repeat(' ', 12), $rows); + + return $this; + } +} diff --git a/src/Scaffold/ModelCreator.php b/src/Scaffold/ModelCreator.php new file mode 100644 index 0000000000..b6e9c96551 --- /dev/null +++ b/src/Scaffold/ModelCreator.php @@ -0,0 +1,53 @@ +tableName = $tableName; + + $this->name = $name; + + $this->files = $files ?: app('files'); + } + + protected function populateStub() + { + $stub = $this->files->get($this->getStub()); + } + + public function create($keyName = 'id', $useTimestamps = true, $useSoftDeletes = false) + { + if (Str::endsWith($this->name, '\\')) { + $this->name = trim($this->name, '\\'). '\\' . ucfirst(Str::singular($this->tableName)); + } + + $namespace = substr($this->name, 0, strrpos($this->name, '\\')); + + + + $this->populateStub(); + + dd($namespace, $this->name, $this->tableName, $stub); + } + + public function getStub() + { + return __DIR__.'/stubs/model.stub'; + } +} diff --git a/src/Scaffold/stubs/create.stub b/src/Scaffold/stubs/create.stub new file mode 100755 index 0000000000..ace8eb1a47 --- /dev/null +++ b/src/Scaffold/stubs/create.stub @@ -0,0 +1,30 @@ + +
    +

    Scaffold

    +
    + +
    + +
    + +
    + +
    + +
    + + + +
    + +
    + + +   Table name can't be empty! + + +
    +
    + + +
    + +
    +
    + +
    + + +
    + +
    +
    + +
    +
    +
    + + + +
    +
    +
    + +
    + +
    + +

    Fields

    + + + + + + + + + + + + + + + + + + + + +
    Field nameTypeNullableKeyDefault valueAction
    + + + + + + remove
    + +
    + +
    + +
    + + +
    + +
    +
    + + +
    +
    + +
    + +
    + +
    + + {{--
    --}} + {{----}} + {{--
    --}} + + {{--
    --}} + {{----}} + {{----}} + {{--
    --}} + + {{-- 新增--}} + + {{--
    --}} + + + {{--
    --}} + + {{--
    --}} + + {{--

    Relations

    --}} + + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{----}} + {{--
    Relation nameTypeWith PivotforignKeyOtherKeyAction
    --}} + {{----}} + {{-- 删除
    --}} + {{----}} + {{-- 删除
    --}} + +
    + + + + {{ csrf_field() }} + + +
    + + +
    + +
    + + + + \ No newline at end of file From 0f472fcb74cf14f19a5dab02df07127740738b10 Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 16 Feb 2017 00:17:49 +0800 Subject: [PATCH 0489/2161] develop scaffold --- src/Controllers/ScaffoldController.php | 14 +- src/Scaffold/ControllerCreator.php | 129 ++++++++++++++++ src/Scaffold/MigrationCreator.php | 34 +++++ src/Scaffold/ModelCreator.php | 198 ++++++++++++++++++++++++- src/Scaffold/stubs/model.stub | 10 +- views/helpers/scaffold.blade.php | 6 +- 6 files changed, 377 insertions(+), 14 deletions(-) create mode 100644 src/Scaffold/ControllerCreator.php diff --git a/src/Controllers/ScaffoldController.php b/src/Controllers/ScaffoldController.php index bafd9697e4..9a0e0a556b 100644 --- a/src/Controllers/ScaffoldController.php +++ b/src/Controllers/ScaffoldController.php @@ -4,6 +4,7 @@ use Encore\Admin\Facades\Admin; use Encore\Admin\Layout\Content; +use Encore\Admin\Scaffold\ControllerCreator; use Encore\Admin\Scaffold\MigrationCreator; use Encore\Admin\Scaffold\ModelCreator; use Illuminate\Http\Request; @@ -64,9 +65,20 @@ public function store(Request $request) try { - (new ModelCreator($request->get('table_name'), $request->get('model_name')))->create(); + // Create controller + $paths['controller'] = (new ControllerCreator($request->get('controller_name'))) + ->create($request->get('model_name')); + // Create model. + $modelCreator = new ModelCreator($request->get('table_name'), $request->get('model_name')); + $paths['model'] = $modelCreator->create( + $request->get('primary_key'), + $request->get('timestamps') == 'on', + $request->get('soft_deletes') == 'on' + ); + + // Create migration $migrationName = 'create_'.$request->get('table_name').'_table'; $paths['migration'] = (new MigrationCreator(app('files')))->buildBluePrint( diff --git a/src/Scaffold/ControllerCreator.php b/src/Scaffold/ControllerCreator.php new file mode 100644 index 0000000000..7ab5ab0dd1 --- /dev/null +++ b/src/Scaffold/ControllerCreator.php @@ -0,0 +1,129 @@ +name = $name; + + $this->files = $files ?: app('files'); + } + + /** + * Create a controller. + * + * @param string $model + * + * @return string + * + * @throws \Exception + */ + public function create($model) + { + $path = $this->getpath($this->name); + + if ($this->files->exists($path)) { + throw new \Exception("Controller [$this->name] already exists!"); + } + + $stub = $this->files->get($this->getStub()); + + $this->files->put($path, $this->replace($stub, $this->name, $model)); + + return $path; + } + + /** + * @param string $stub + * @param string $name + * @param string $model + * + * @return string + */ + protected function replace($stub, $name, $model) + { + $stub = $this->replaceClass($stub, $name); + + return str_replace( + ['DummyModelNamespace', 'DummyModel'], + [$model, class_basename($model)], + $stub + ); + } + + /** + * Get controller namespace from giving name. + * + * @param string $name + * + * @return string + */ + protected function getNamespace($name) + { + return trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + } + + /** + * Replace the class name for the given stub. + * + * @param string $stub + * @param string $name + * @return string + */ + protected function replaceClass($stub, $name) + { + $class = str_replace($this->getNamespace($name).'\\', '', $name); + + return str_replace(['DummyClass', 'DummyNamespace'], [$class, $this->getNamespace($name)], $stub); + } + + /** + * Get file path from giving controller name. + * + * @param $name + * @return string + */ + public function getPath($name) + { + $segments = explode('\\', $name); + + array_shift($segments); + + return app_path(join('/', $segments)).'.php'; + } + + /** + * Get stub file path. + * + * @return string + */ + public function getStub() + { + return __DIR__.'/../Commands/stubs/controller.stub'; + } +} diff --git a/src/Scaffold/MigrationCreator.php b/src/Scaffold/MigrationCreator.php index 5da69c15ca..85545f2b5f 100644 --- a/src/Scaffold/MigrationCreator.php +++ b/src/Scaffold/MigrationCreator.php @@ -6,8 +6,21 @@ class MigrationCreator extends BaseMigrationCreator { + /** + * @var string + */ protected $bluePrint = ''; + /** + * Create a new model. + * + * @param string $name + * @param string $path + * @param null $table + * @param bool|true $create + * + * @return string + */ public function create($name, $path, $table = null, $create = true) { $this->ensureMigrationDoesntAlreadyExist($name); @@ -23,6 +36,15 @@ public function create($name, $path, $table = null, $create = true) return $path; } + /** + * Populate stub. + * + * @param string $name + * @param string $stub + * @param string $table + * + * @return mixed + */ protected function populateStub($name, $stub, $table) { return str_replace( @@ -32,6 +54,18 @@ protected function populateStub($name, $stub, $table) ); } + /** + * Build the table blueprint. + * + * @param array $fields + * @param string $keyName + * @param bool|true $useTimestamps + * @param bool|false $softDeletes + * + * @return $this + * + * @throws \Exception + */ public function buildBluePrint($fields = [], $keyName = 'id', $useTimestamps = true, $softDeletes = false) { $fields = array_filter($fields, function ($field) { diff --git a/src/Scaffold/ModelCreator.php b/src/Scaffold/ModelCreator.php index b6e9c96551..da553c6bd9 100644 --- a/src/Scaffold/ModelCreator.php +++ b/src/Scaffold/ModelCreator.php @@ -6,8 +6,18 @@ class ModelCreator { + /** + * Table name. + * + * @var string + */ protected $tableName; + /** + * Model name. + * + * @var string + */ protected $name; /** @@ -17,6 +27,13 @@ class ModelCreator */ protected $files; + /** + * ModelCreator constructor. + * + * @param string $tableName + * @param string $name + * @param null $files + */ public function __construct($tableName, $name, $files = null) { $this->tableName = $tableName; @@ -26,26 +43,193 @@ public function __construct($tableName, $name, $files = null) $this->files = $files ?: app('files'); } - protected function populateStub() + /** + * Create a new migration file. + * + * @param string $keyName + * @param bool|true $timestamps + * @param bool|false $softDeletes + * + * @return string + * + * @throws \Exception + */ + public function create($keyName = 'id', $timestamps = true, $softDeletes = false) { + $path = $this->getpath($this->name); + + if ($this->files->exists($path)) { + throw new \Exception("Model [$this->name] already exists!"); + } + $stub = $this->files->get($this->getStub()); + + $stub = $this->replaceClass($stub, $this->name) + ->replaceNamespace($stub, $this->name) + ->replaceSoftDeletes($stub, $softDeletes) + ->replaceTable($stub, $this->name) + ->replaceTimestamp($stub, $timestamps) + ->replacePrimaryKey($stub, $keyName) + ->replaceSpace($stub); + + $this->files->put($path, $stub); + + return $path; + } + + /** + * Get path for migration file. + * + * @param string $name + * + * @return string + */ + public function getPath($name) + { + $segments = explode('\\', $name); + + array_shift($segments); + + return app_path(join('/', $segments)).'.php'; + } + + /** + * Get namespace of giving class full name. + * + * @param string $name + * + * @return string + */ + protected function getNamespace($name) + { + return trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + } + + /** + * Replace class dummy. + * + * @param string $stub + * @param string $name + * @return $this + */ + protected function replaceClass(&$stub, $name) + { + $class = str_replace($this->getNamespace($name).'\\', '', $name); + + $stub = str_replace('DummyClass', $class, $stub); + + return $this; + } + + /** + * Replace namespace dummy. + * + * @param string $stub + * @param string $name + * + * @return $this + */ + protected function replaceNamespace(&$stub, $name) + { + $stub = str_replace( + 'DummyNamespace', $this->getNamespace($name), $stub + ); + + return $this; } - public function create($keyName = 'id', $useTimestamps = true, $useSoftDeletes = false) + /** + * Replace soft-deletes dummy. + * + * @param string $stub + * @param bool $softDeletes + * + * @return $this + */ + protected function replaceSoftDeletes(&$stub, $softDeletes) { - if (Str::endsWith($this->name, '\\')) { - $this->name = trim($this->name, '\\'). '\\' . ucfirst(Str::singular($this->tableName)); + $import = $use = ''; + + if ($softDeletes) { + $import = "use Illuminate\\Database\\Eloquent\\SoftDeletes;\n"; + $use = "use SoftDeletes;\n"; } - $namespace = substr($this->name, 0, strrpos($this->name, '\\')); + $stub = str_replace(['DummyImportSoftDeletesTrait', 'DummyUseSoftDeletesTrait'], [$import, $use], $stub); + + return $this; + } + + /** + * Replace primarykey dummy. + * + * @param string $stub + * @param string $keyName + * + * @return $this + */ + protected function replacePrimaryKey(&$stub, $keyName) + { + $modelKey = $keyName == 'id' ? '' : "protected \$primaryKey = '$keyName';\n"; + + $stub = str_replace('DummyModelKey', $modelKey, $stub); + + return $this; + } + + /** + * Replace Table name dummy. + * + * @param string $stub + * @param string $name + * + * @return $this + */ + protected function replaceTable(&$stub, $name) + { + $class = str_replace($this->getNamespace($name).'\\', '', $name); + + $table = Str::plural(strtolower($class)) !== $this->tableName ? "protected \$table = '$this->tableName';\n" : ''; + $stub = str_replace('DummyModelTable', $table, $stub); + return $this; + } - $this->populateStub(); + /** + * Replace timestamps dummy. + * + * @param string $stub + * @param bool $timestamps + * + * @return $this + */ + protected function replaceTimestamp(&$stub, $timestamps) + { + $useTimestamps = $timestamps ? '' : "public \$timestamps = false;\n"; - dd($namespace, $this->name, $this->tableName, $stub); + $stub = str_replace('DummyTimestamp', $useTimestamps, $stub); + + return $this; } + /** + * Replace spaces. + * + * @param string $stub + * + * @return mixed + */ + public function replaceSpace($stub) + { + return str_replace(["\n\n\n", "\n \n"], ["\n\n", ""], $stub); + } + + /** + * Get stub path of model. + * + * @return string + */ public function getStub() { return __DIR__.'/stubs/model.stub'; diff --git a/src/Scaffold/stubs/model.stub b/src/Scaffold/stubs/model.stub index 8151072cd2..ddd47770ec 100644 --- a/src/Scaffold/stubs/model.stub +++ b/src/Scaffold/stubs/model.stub @@ -3,11 +3,15 @@ namespace DummyNamespace; use Illuminate\Database\Eloquent\Model; -ImportSoftDeletesTraitDummy +DummyImportSoftDeletesTrait class DummyClass extends Model { - UseSoftDeletesTraitDummy + DummyUseSoftDeletesTrait - ModelTableDummy + DummyModelTable + + DummyModelKey + + DummyTimestamp } diff --git a/views/helpers/scaffold.blade.php b/views/helpers/scaffold.blade.php index 9d94d4923d..97826bc584 100644 --- a/views/helpers/scaffold.blade.php +++ b/views/helpers/scaffold.blade.php @@ -36,7 +36,7 @@
    - +
    @@ -110,10 +110,10 @@
    From d8d4579a52ec2cd744ad5df304afa64ec6f6720e Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 16 Feb 2017 11:14:39 +0800 Subject: [PATCH 0490/2161] fix issue #416 #410 --- src/Form.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Form.php b/src/Form.php index 9cf259fbc9..e04affdc17 100644 --- a/src/Form.php +++ b/src/Form.php @@ -630,7 +630,9 @@ protected function updateRelation($relationsData) switch (get_class($relation)) { case \Illuminate\Database\Eloquent\Relations\BelongsToMany::class: case \Illuminate\Database\Eloquent\Relations\MorphToMany::class: - $relation->sync($prepared[$name]); + if (isset($prepared[$name])) { + $relation->sync($prepared[$name]); + } break; case \Illuminate\Database\Eloquent\Relations\HasOne::class: From 4ddacc0fba848ab5d35f3a3f8178071e036112be Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 16 Feb 2017 11:22:49 +0800 Subject: [PATCH 0491/2161] fix issue #408 #199 --- views/index.blade.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/views/index.blade.php b/views/index.blade.php index 86bfc5f8dc..541a935c5a 100644 --- a/views/index.blade.php +++ b/views/index.blade.php @@ -99,6 +99,16 @@ function LA() {} }); }); + $(function() { + $('.sidebar-menu li:not(.treeview) a').each(function() { + $(this).on('click', function () { + $(this).parent().siblings().removeClass('active') + $(this).parent().siblings('.treeview').children('ul.treeview-menu.menu-open').css("display", "none"); + $(this).parent().addClass('active'); + }); + }); + }); + From 8a01f8f12e9035f4bbfadb1ebad8f778c66cf1ad Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 16 Feb 2017 16:18:03 +0800 Subject: [PATCH 0492/2161] scaffold develop. --- src/Admin.php | 3 +- src/Controllers/ScaffoldController.php | 118 +++++++++--------- src/Scaffold/ControllerCreator.php | 2 - src/Scaffold/MigrationCreator.php | 5 +- views/helpers/scaffold.blade.php | 164 ++++++++++++++----------- views/partials/error.blade.php | 2 +- views/partials/exception.blade.php | 2 +- views/partials/success.blade.php | 2 +- 8 files changed, 164 insertions(+), 134 deletions(-) diff --git a/src/Admin.php b/src/Admin.php index 7a3100820b..aad1a6f504 100644 --- a/src/Admin.php +++ b/src/Admin.php @@ -294,13 +294,12 @@ public function getNavbar() public function enableHelpersRoutes($attributes = []) { $attributes = array_merge([ - 'prefix' => 'admin/helpers', + 'prefix' => trim(config('admin.prefix'), '/') . '/helpers', 'middleware' => ['web', 'admin'] ], $attributes); Route::group($attributes, function ($router) { - /* @var \Illuminate\Routing\Router $router */ $router->get('terminal/database', 'Encore\Admin\Controllers\TerminalController@database'); $router->post('terminal/database', 'Encore\Admin\Controllers\TerminalController@runDatabase'); diff --git a/src/Controllers/ScaffoldController.php b/src/Controllers/ScaffoldController.php index 9a0e0a556b..1807c8ae11 100644 --- a/src/Controllers/ScaffoldController.php +++ b/src/Controllers/ScaffoldController.php @@ -9,6 +9,7 @@ use Encore\Admin\Scaffold\ModelCreator; use Illuminate\Http\Request; use Illuminate\Routing\Controller; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\MessageBag; class ScaffoldController extends Controller @@ -20,39 +21,11 @@ public function index() $content->header('Scaffold'); $dbTypes = [ - 'string', - 'integer', - 'text', - 'float', - 'double', - 'decimal', - 'boolean', - 'date', - 'time', - 'dateTime', - 'timestamp', - 'char', - 'mediumText', - 'longText', - 'tinyInteger', - 'smallInteger', - 'mediumInteger', - 'bigInteger', - 'unsignedTinyInteger', - 'unsignedSmallInteger', - 'unsignedMediumInteger', - 'unsignedInteger', - 'unsignedBigInteger', - 'enum', - 'json', - 'jsonb', - 'dateTimeTz', - 'timeTz', - 'timestampTz', - 'nullableTimestamps', - 'binary', - 'ipAddress', - 'macAddress', + 'string', 'integer', 'text', 'float', 'double', 'decimal', 'boolean', 'date', 'time', + 'dateTime', 'timestamp', 'char', 'mediumText', 'longText', 'tinyInteger', 'smallInteger', + 'mediumInteger', 'bigInteger', 'unsignedTinyInteger', 'unsignedSmallInteger', 'unsignedMediumInteger', + 'unsignedInteger', 'unsignedBigInteger', 'enum', 'json', 'jsonb', 'dateTimeTz', 'timeTz', + 'timestampTz', 'nullableTimestamps', 'binary', 'ipAddress', 'macAddress', ]; $content->row(view('admin::helpers.scaffold', compact('dbTypes'))); @@ -62,37 +35,54 @@ public function index() public function store(Request $request) { $paths = []; + $message = ''; try { - // Create controller - $paths['controller'] = (new ControllerCreator($request->get('controller_name'))) - ->create($request->get('model_name')); + // 1. Create model. + if (in_array('model', $request->get('create'))) { + $modelCreator = new ModelCreator($request->get('table_name'), $request->get('model_name')); + + $paths['model'] = $modelCreator->create( + $request->get('primary_key'), + $request->get('timestamps') == 'on', + $request->get('soft_deletes') == 'on' + ); + } + + // 2. Create controller. + if (in_array('controller', $request->get('create'))) { + $paths['controller'] = (new ControllerCreator($request->get('controller_name'))) + ->create($request->get('model_name')); + } + + // 3. Create migration. + if (in_array('migration', $request->get('create'))) { + $migrationName = 'create_' . $request->get('table_name') . '_table'; + + $paths['migration'] = (new MigrationCreator(app('files')))->buildBluePrint( + $request->get('fields'), + $request->get('primary_key', 'id'), + $request->get('timestamps') == 'on', + $request->get('soft_deletes') == 'on' + )->create($migrationName, database_path('migrations'), $request->get('table_name')); + } + + // 4. Run migrate. + if (in_array('migrate', $request->get('create'))) { + Artisan::call('migrate'); + $message = Artisan::output(); + } - // Create model. - $modelCreator = new ModelCreator($request->get('table_name'), $request->get('model_name')); - - $paths['model'] = $modelCreator->create( - $request->get('primary_key'), - $request->get('timestamps') == 'on', - $request->get('soft_deletes') == 'on' - ); - - // Create migration - $migrationName = 'create_'.$request->get('table_name').'_table'; + } catch (\Exception $exception) { - $paths['migration'] = (new MigrationCreator(app('files')))->buildBluePrint( - $request->get('fields'), - $request->get('primary_key', 'id'), - $request->get('use_timestamps') == 'on', - $request->get('use_soft_deletes') == 'on' - )->create($migrationName, database_path('migrations'), $request->get('table_name')); + // Delete generated files if exception thrown. + app('files')->delete($paths); - } catch (\Exception $exception) { return $this->backWithException($exception); } - return $paths; + return $this->backWithSuccess($paths, $message); } protected function backWithException(\Exception $exception) @@ -104,4 +94,22 @@ protected function backWithException(\Exception $exception) return back()->withInput()->with(compact('error')); } + + protected function backWithSuccess($paths, $message) + { + $messages = []; + + foreach ($paths as $name => $path) { + $messages[] = ucfirst($name) . ": $path"; + } + + $messages[] = "
    $message"; + + $success = new MessageBag([ + 'title' => 'Success', + 'message' => join('
    ', $messages), + ]); + + return back()->with(compact('success')); + } } diff --git a/src/Scaffold/ControllerCreator.php b/src/Scaffold/ControllerCreator.php index 7ab5ab0dd1..1eb915a538 100644 --- a/src/Scaffold/ControllerCreator.php +++ b/src/Scaffold/ControllerCreator.php @@ -2,8 +2,6 @@ namespace Encore\Admin\Scaffold; -use Illuminate\Support\Str; - class ControllerCreator { /** diff --git a/src/Scaffold/MigrationCreator.php b/src/Scaffold/MigrationCreator.php index 85545f2b5f..664fbe5dd1 100644 --- a/src/Scaffold/MigrationCreator.php +++ b/src/Scaffold/MigrationCreator.php @@ -73,11 +73,12 @@ public function buildBluePrint($fields = [], $keyName = 'id', $useTimestamps = t }); if (empty($fields)) { - throw new \Exception('table fields can\'t be empty'); + throw new \Exception('Table fields can\'t be empty'); } $rows[] = "\$table->increments('$keyName');\n"; + foreach ($fields as $field) { $column = "\$table->{$field['type']}('{$field['name']}')"; @@ -104,7 +105,7 @@ public function buildBluePrint($fields = [], $keyName = 'id', $useTimestamps = t $rows[] = "\$table->softDeletes();\n"; } - $this->bluePrint = str_repeat(' ', 12) . join(str_repeat(' ', 12), $rows); + $this->bluePrint = trim(join(str_repeat(' ', 12), $rows), "\n"); return $this; } diff --git a/views/helpers/scaffold.blade.php b/views/helpers/scaffold.blade.php index 97826bc584..bd92ce0198 100644 --- a/views/helpers/scaffold.blade.php +++ b/views/helpers/scaffold.blade.php @@ -5,7 +5,7 @@
    -
    +
    @@ -41,16 +41,19 @@
    -
    +
    +
    @@ -72,6 +75,34 @@ Default value Action + + @if(old('fields')) + @foreach(old('fields') as $index => $field) + + + + + + + + + + + + + remove + + @endforeach + @else @@ -95,6 +126,7 @@ remove + @endif @@ -102,93 +134,59 @@
    -
    - - + +
    +
    -
    +
    +    +
    -
    - +
    + +
    - {{--
    --}} - {{----}} - {{--
    --}} - - {{--
    --}} - {{----}} - {{----}} - {{--
    --}} - - {{-- 新增--}} - - {{--
    --}} - - - {{--
    --}} - {{--
    --}} {{--

    Relations

    --}} - {{----}} - {{----}} + {{--
    --}} + {{----}} + {{----}} {{----}} {{----}} - {{----}} + {{----}} {{----}} {{----}} + {{----}} {{----}} {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} - {{----}} {{----}} {{--
    Relation nameTypeWith PivotRelated modelforignKeyOtherKeyWith PivotAction
    --}} - {{----}} - {{-- 删除
    --}} - {{----}} - {{-- 删除
    --}} + {{--
    --}} + + {{--
    --}} + + {{--
    --}} + {{----}} + {{--
    --}} + + {{--
    --}} +
    @@ -220,7 +217,7 @@ @@ -230,6 +227,25 @@ + + @endif \ No newline at end of file From a616df4ba2ba7db555cf9ac4501e1f4eec4aca9d Mon Sep 17 00:00:00 2001 From: z-song Date: Tue, 21 Feb 2017 19:12:09 +0800 Subject: [PATCH 0528/2161] refactoring of file upload. --- .../bootstrap-fileinput/css/fileinput.min.css | 17 +- .../bootstrap-fileinput/js/fileinput.min.js | 29 +- .../js/fileinput_locale_LANG.js | 60 - .../js/fileinput_locale_ar.js | 61 - .../js/fileinput_locale_bg.js | 60 - .../js/fileinput_locale_ca.js | 60 - .../js/fileinput_locale_cr.js | 61 - .../js/fileinput_locale_cz.js | 60 - .../js/fileinput_locale_da.js | 60 - .../js/fileinput_locale_de.js | 58 - .../js/fileinput_locale_el.js | 60 - .../js/fileinput_locale_es.js | 60 - .../js/fileinput_locale_fa.js | 61 - .../js/fileinput_locale_fi.js | 43 - .../js/fileinput_locale_fr.js | 60 - .../js/fileinput_locale_hu.js | 60 - .../js/fileinput_locale_id.js | 61 - .../js/fileinput_locale_it.js | 62 - .../js/fileinput_locale_ja.js | 70 - .../js/fileinput_locale_nl.js | 60 - .../js/fileinput_locale_pl.js | 60 - .../js/fileinput_locale_pt-BR.js | 60 - .../js/fileinput_locale_pt.js | 60 - .../js/fileinput_locale_ro.js | 61 - .../js/fileinput_locale_ru.js | 61 - .../js/fileinput_locale_sk.js | 60 - .../js/fileinput_locale_th.js | 60 - .../js/fileinput_locale_tr.js | 60 - .../js/fileinput_locale_uk.js | 61 - .../js/fileinput_locale_zh-TW.js | 61 - .../js/fileinput_locale_zh_CN.js | 61 - .../js/plugins/canvas-to-blob.js | 95 ++ .../bootstrap-fileinput/js/plugins/purify.js | 812 ++++++++++ .../js/plugins/purify.min.js | 2 + .../js/plugins/sortable.js | 1333 +++++++++++++++++ .../js/plugins/sortable.min.js | 2 + src/Form.php | 55 +- src/Form/Field.php | 2 + src/Form/Field/File.php | 463 +----- src/Form/Field/Image.php | 107 +- src/Form/Field/ImageField.php | 84 ++ src/Form/Field/MultipleFile.php | 219 +++ src/Form/Field/MultipleImage.php | 39 + src/Form/Field/UploadField.php | 288 ++++ views/form/file.blade.php | 4 +- ...image.blade.php => multiplefile.blade.php} | 4 +- 46 files changed, 2965 insertions(+), 2332 deletions(-) delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_LANG.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_ar.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_bg.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_ca.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_cr.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_cz.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_da.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_de.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_el.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_es.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_fa.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_fi.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_fr.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_hu.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_id.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_it.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_ja.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_nl.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_pl.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_pt-BR.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_pt.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_ro.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_ru.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_sk.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_th.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_tr.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_uk.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_zh-TW.js delete mode 100755 assets/bootstrap-fileinput/js/fileinput_locale_zh_CN.js create mode 100755 assets/bootstrap-fileinput/js/plugins/canvas-to-blob.js create mode 100755 assets/bootstrap-fileinput/js/plugins/purify.js create mode 100755 assets/bootstrap-fileinput/js/plugins/purify.min.js create mode 100755 assets/bootstrap-fileinput/js/plugins/sortable.js create mode 100755 assets/bootstrap-fileinput/js/plugins/sortable.min.js create mode 100644 src/Form/Field/ImageField.php create mode 100644 src/Form/Field/MultipleFile.php create mode 100644 src/Form/Field/MultipleImage.php create mode 100644 src/Form/Field/UploadField.php rename views/form/{image.blade.php => multiplefile.blade.php} (61%) diff --git a/assets/bootstrap-fileinput/css/fileinput.min.css b/assets/bootstrap-fileinput/css/fileinput.min.css index af63e4d582..911984578e 100755 --- a/assets/bootstrap-fileinput/css/fileinput.min.css +++ b/assets/bootstrap-fileinput/css/fileinput.min.css @@ -1,11 +1,12 @@ /*! - * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2015 - * @package bootstrap-fileinput - * @version 4.3.2 + * bootstrap-fileinput v4.3.7 + * http://plugins.krajee.com/file-input + * + * Krajee default styling for bootstrap-fileinput. * - * File input styling for Bootstrap 3.0 - * Built for Yii Framework 2.0 * Author: Kartik Visweswaran - * Year: 2015 - * For more Yii related demos visit http://demos.krajee.com - */.file-preview-frame,.file-preview-image,.file-preview-other{height:160px;vertical-align:middle}.file-loading{top:0;right:0;width:25px;height:25px;font-size:999px;text-align:right;color:#fff;background:url(https://github.com/img/loading.gif)top left no-repeat;border:none}.file-object{margin:0 0 -5px;padding:0}.btn-file{position:relative;overflow:hidden}.btn-file input[type=file]{position:absolute;top:0;right:0;min-width:100%;min-height:100%;text-align:right;opacity:0;background:0 0;cursor:inherit;display:block}.file-caption-name{display:inline-block;overflow:hidden;height:20px;word-break:break-all}.input-group-lg .file-caption-name{height:25px}.file-preview-detail-modal{text-align:left}.file-error-message{color:#a94442;background-color:#f2dede;margin:5px;border:1px solid #ebccd1;border-radius:4px;padding:15px}.file-error-message pre,.file-error-message ul{margin:0;text-align:left}.file-error-message pre{margin:5px 0}.file-caption-disabled{background-color:#EEE;cursor:not-allowed;opacity:1}.file-preview{border-radius:5px;border:1px solid #ddd;padding:5px;width:100%;margin-bottom:5px}.file-preview-frame{display:table;margin:8px;border:1px solid #ddd;box-shadow:1px 1px 5px 0 #a2958a;padding:6px;float:left;text-align:center}.file-preview-frame:not(.file-preview-error):hover{box-shadow:3px 3px 5px 0 #333}.file-preview-text{text-align:left;width:160px;margin-bottom:2px;color:#428bca;background:#fff;overflow-x:hidden}.file-preview-other{display:table-cell;text-align:center;width:160px;border:2px solid #999;border-radius:30px}.file-preview-other:hover{opacity:.8}.file-actions,.file-other-error{text-align:left}.file-icon-lg{font-size:1.2em}.file-icon-2x{font-size:2.4em}.file-icon-4x{font-size:4.8em}.file-input-ajax-new .fileinput-remove-button,.file-input-ajax-new .fileinput-upload-button,.file-input-new .close,.file-input-new .file-preview,.file-input-new .fileinput-remove-button,.file-input-new .fileinput-upload-button,.file-input-new .glyphicon-file{display:none}.file-thumb-loading{background:url(https://github.com/img/loading.gif)center center no-repeat content-box!important}.file-actions{margin-top:15px}.file-footer-buttons{float:right}.file-upload-indicator{padding-top:2px;cursor:default;opacity:.8;width:60%}.file-upload-indicator:hover{font-weight:700;opacity:1}.file-footer-caption{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:160px;text-align:center;padding-top:4px;font-size:11px;color:#777;margin:5px auto 10px}.file-preview-error{opacity:.65;box-shadow:none}.file-preview-frame:not(.file-preview-error) .file-footer-caption:hover{color:#000}.file-drop-zone{border:1px dashed #aaa;border-radius:4px;height:100%;text-align:center;vertical-align:middle;margin:12px 15px 12px 12px;padding:5px}.file-drop-zone-title{color:#aaa;font-size:40px;padding:85px 10px}.file-highlighted{border:2px dashed #999!important;background-color:#f0f0f0}.file-uploading{background:url(https://github.com/img/loading-sm.gif)center bottom 10px no-repeat;opacity:.65}.file-thumb-progress .progress,.file-thumb-progress .progress-bar{height:10px;font-size:9px;line-height:10px}.file-thumbnail-footer{position:relative}.file-thumb-progress{position:absolute;top:22px;left:0;right:0}.btn-file ::-ms-browse{width:100%;height:100%} \ No newline at end of file + * Copyright: 2014 - 2017, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */.file-loading{top:0;right:0;width:25px;height:25px;font-size:999px;text-align:right;color:#fff;background:url(https://github.com/img/loading.gif) top left no-repeat;border:none}.file-object{margin:0 0 -5px;padding:0}.btn-file{position:relative;overflow:hidden}.btn-file input[type=file]{position:absolute;top:0;right:0;min-width:100%;min-height:100%;text-align:right;opacity:0;background:none;cursor:inherit;display:block}.file-caption-name{display:inline-block;overflow:hidden;height:20px;word-break:break-all}.input-group-lg .file-caption-name{height:25px}.file-zoom-dialog{text-align:left}.file-error-message{color:#a94442;background-color:#f2dede;margin:5px;border:1px solid #ebccd1;border-radius:4px;padding:15px}.file-error-message pre,.file-error-message ul{margin:0;text-align:left}.file-error-message pre{margin:5px 0}.file-caption-disabled{background-color:#EEE;cursor:not-allowed;opacity:1}.file-preview{border-radius:5px;border:1px solid #ddd;padding:5px;width:100%;margin-bottom:5px}.krajee-default.file-preview-frame{position:relative;display:table;margin:8px;height:160px;border:1px solid #ddd;box-shadow:1px 1px 5px 0 #a2958a;padding:6px;float:left;text-align:center;vertical-align:middle}.krajee-default.file-preview-frame:not(.file-preview-error):hover{box-shadow:3px 3px 5px 0 #333}.krajee-default .file-preview-image{vertical-align:middle;image-orientation:from-image}.krajee-default .file-preview-text{display:block;color:#428bca;border:1px solid #ddd;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;outline:0;padding:8px;resize:none}.krajee-default .file-preview-html{border:1px solid #ddd;padding:8px;overflow:auto}.krajee-default .file-zoom-dialog .file-preview-text{font-size:1.2em}.krajee-default .file-preview-other{left:0;top:0;right:0;bottom:0;margin:auto;text-align:center;vertical-align:middle;padding:10px}.krajee-default .file-preview-other:hover{opacity:.8}.krajee-default .file-actions,.krajee-default .file-other-error{text-align:left}.krajee-default .file-other-icon{font-size:4.8em}.krajee-default .file-actions{margin-top:15px}.krajee-default .file-footer-buttons{float:right}.krajee-default .file-footer-caption{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:160px;text-align:center;padding-top:4px;font-size:11px;color:#777;margin:5px auto}.file-input-ajax-new .fileinput-remove-button,.file-input-ajax-new .fileinput-upload-button,.file-input-ajax-new .no-browse .input-group-btn,.file-input-new .close,.file-input-new .file-preview,.file-input-new .fileinput-remove-button,.file-input-new .fileinput-upload-button,.file-input-new .glyphicon-file,.file-input-new .no-browse .input-group-btn{display:none}.krajee-default .file-preview-error{opacity:.65;box-shadow:none}.krajee-default .file-preview-frame:not(.file-preview-error) .file-footer-caption:hover{color:#000}.krajee-default .file-drag-handle,.krajee-default .file-upload-indicator{position:absolute;text-align:center;bottom:-6px;left:-6px;padding:8px 8px 1px 3px;border-left:none;border-bottom:none;border-right:1px solid #8a6d3b;border-top:1px solid #8a6d3b;border-top-right-radius:24px;font-size:12px}.krajee-default .file-drag-handle{background-color:#d9edf7;border-color:#bce8f1}.krajee-default .file-upload-indicator{font-size:13px;background-color:#fcf8e3;border-color:#faebcc;padding-bottom:0}.krajee-default.file-preview-error .file-upload-indicator{background-color:#f2dede;border-color:#ebccd1}.krajee-default.file-preview-success .file-upload-indicator{background-color:#dff0d8;border-color:#d6e9c6}.krajee-default.file-preview-loading .file-upload-indicator{background-color:#e5e5e5;border-color:#777}.krajee-default .file-thumb-progress .progress,.krajee-default .file-thumb-progress .progress-bar{height:10px;font-size:9px;line-height:10px}.krajee-default .file-thumbnail-footer{position:relative}.krajee-default .file-thumb-progress{height:10px;position:absolute;top:35px;left:0;right:0}.krajee-default.kvsortable-ghost{background:#e1edf7;border:2px solid #a1abff}.file-zoom-dialog .file-other-icon{font-size:8em;font-size:55vmin}.file-caption-main{width:100%}.file-input-ajax-new .no-browse .form-control,.file-input-new .no-browse .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.file-thumb-loading{background:url(https://github.com/img/loading.gif) center center no-repeat content-box!important}.file-sortable .file-drag-handle{cursor:move;cursor:-webkit-grabbing;opacity:1}.file-sortable .file-drag-handle:hover{opacity:.7}.file-drop-zone{border:1px dashed #aaa;border-radius:4px;height:100%;text-align:center;vertical-align:middle;margin:12px 15px 12px 12px;padding:5px}.file-drop-zone-title{color:#aaa;font-size:1.6em;padding:85px 10px;cursor:default}.clickable .file-drop-zone-title,.file-preview .clickable{cursor:pointer}.file-drop-zone.clickable:hover{border:2px dashed #999}.file-drop-zone.clickable:focus{border:2px solid #5acde2}.file-drop-zone .file-preview-thumbnails{cursor:default}.file-highlighted{border:2px dashed #999!important;background-color:#f0f0f0}.file-uploading{background:url(https://github.com/img/loading-sm.gif) center bottom 10px no-repeat;opacity:.65}.file-zoom-fullscreen.modal{position:fixed;top:0;right:0;bottom:0;left:0}.file-zoom-fullscreen .modal-dialog{position:fixed;margin:0;width:100%;height:100%;padding:0}.file-zoom-fullscreen .modal-content{border-radius:0;box-shadow:none}.file-zoom-fullscreen .modal-body{overflow-y:auto}.file-zoom-dialog .modal-body{position:relative!important}.file-zoom-dialog .btn-navigate{position:absolute;padding:0;margin:0;background:0 0;text-decoration:none;outline:0;opacity:.7;top:45%;font-size:4em;color:#1c94c4}.file-zoom-dialog .floating-buttons{position:absolute;top:5px;right:10px}.floating-buttons,.floating-buttons .btn{z-index:3000}.file-zoom-dialog .kv-zoom-actions .btn,.floating-buttons .btn{margin-left:3px}.file-zoom-dialog .btn-navigate:not([disabled]):focus,.file-zoom-dialog .btn-navigate:not([disabled]):hover{outline:0;box-shadow:none;opacity:.5}.file-zoom-dialog .btn-navigate[disabled]{opacity:.3}.file-zoom-dialog .btn-prev{left:1px}.file-zoom-dialog .btn-next{right:1px}.file-zoom-content{height:480px;text-align:center}.file-preview-initial.sortable-chosen{background-color:#d9edf7}.btn-file ::-ms-browse{width:100%;height:100%}owed}.file-sortable .file-drag-handle{cursor:move;cursor:-webkit-grabbing;opacity:1}.file-sortable .file-drag-handle:hover{opacity:.7}.file-zoom-content{height:480px;text-align:center}.file-preview-initial.sortable-chosen{background-color:#d9edf7}.file-preview-frame.sortable-ghost{background-color:#eee}.btn-file ::-ms-browse{width:100%;height:100%} \ No newline at end of file diff --git a/assets/bootstrap-fileinput/js/fileinput.min.js b/assets/bootstrap-fileinput/js/fileinput.min.js index a9de338084..4728b9a12e 100755 --- a/assets/bootstrap-fileinput/js/fileinput.min.js +++ b/assets/bootstrap-fileinput/js/fileinput.min.js @@ -1,17 +1,12 @@ -/*! - * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2015 - * @version 4.3.2 - * - * File input styled for Bootstrap 3.0 that utilizes HTML5 File Input's advanced features including the FileReader API. - * - * The plugin drastically enhances the HTML file input to preview multiple files on the client before upload. In - * addition it provides the ability to preview content of images, text, videos, audio, html, flash and other objects. - * It also offers the ability to upload and delete files using AJAX, and add files in batches (i.e. preview, append, - * or remove before upload). - * - * Author: Kartik Visweswaran - * Copyright: 2015, Kartik Visweswaran, Krajee.com - * For more JQuery plugins visit http://plugins.krajee.com - * For more Yii related demos visit http://demos.krajee.com - */!function(e){"use strict";"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof module&&module.exports?module.exports=e(require("jquery")):e(window.jQuery)}(function(e){"use strict";e.fn.fileinputLocales={};var i,t,a,r,n,l,o,s,d,c,p,u,f,v,g,m,h,w,_,b,C,x,y,T,F,E,I,$,k,P,S,D,U,A,j,L,z,O,R,N,B,M,Z,H,W,q,V,K,X,J,Q,Y,G,ee,ie;i=".fileinput",t=window.URL||window.webkitURL,a=function(e,i,t){return void 0!==e&&(t?e===i:e.match(i))},r=function(e){if("Microsoft Internet Explorer"!==navigator.appName)return!1;if(10===e)return new RegExp("msie\\s"+e,"i").test(navigator.userAgent);var i,t=document.createElement("div");return t.innerHTML="",i=t.getElementsByTagName("i").length,document.body.appendChild(t),t.parentNode.removeChild(t),i},n=function(){return new RegExp("Edge/[0-9]+","i").test(navigator.userAgent)},l=function(e,t,a,r){var n=r?t:t.split(" ").join(i+" ")+i;e.off(n).on(n,a)},o={data:{},init:function(e){var i=e.initialPreview,t=e.id;i.length>0&&!X(i)&&(i=i.split(e.initialPreviewDelimiter)),o.data[t]={content:i,config:e.initialPreviewConfig,tags:e.initialPreviewThumbTags,delimiter:e.initialPreviewDelimiter,template:e.previewGenericTemplate,msg:function(i){return e._getMsgSelected(i)},initId:e.previewInitId,footer:e._getLayoutTemplate("footer").replace(/\{progress}/g,e._renderThumbProgress()),isDelete:e.initialPreviewShowDelete,caption:e.initialCaption,actions:function(i,t,a,r,n){return e._renderFileActions(i,t,a,r,n)}}},fetch:function(e){return o.data[e].content.filter(function(e){return null!==e})},count:function(e,i){return o.data[e]&&o.data[e].content?i?o.data[e].content.length:o.fetch(e).length:0},get:function(i,t,a){var r,n,l="init_"+t,s=o.data[i],d=s.config[t],c=s.initId+"-"+l,p=" file-preview-initial";return a=void 0===a?!0:a,null===s.content[t]?"":(K(d)||K(d.frameClass)||(p+=" "+d.frameClass),r=s.template.replace(/\{previewId}/g,c).replace(/\{frameClass}/g,p).replace(/\{fileindex}/g,l).replace(/\{content}/g,s.content[t]).replace(/\{footer}/g,o.footer(i,t,a)),s.tags.length&&s.tags[t]&&(r=ee(r,s.tags[t])),K(d)||K(d.frameAttr)||(n=e(document.createElement("div")).html(r),n.find(".file-preview-initial").attr(d.frameAttr),r=n.html(),n.remove()),r)},add:function(i,t,a,r,n){var l,s=e.extend(!0,{},o.data[i]);return X(t)||(t=t.split(s.delimiter)),n?(l=s.content.push(t)-1,s.config[l]=a,s.tags[l]=r):(l=t.length,s.content=t,s.config=a,s.tags=r),o.data[i]=s,l},set:function(i,t,a,r,n){var l,s,d=e.extend(!0,{},o.data[i]);if(t&&t.length&&(X(t)||(t=t.split(d.delimiter)),s=t.filter(function(e){return null!==e}),s.length)){if(void 0===d.content&&(d.content=[]),void 0===d.config&&(d.config=[]),void 0===d.tags&&(d.tags=[]),n){for(l=0;ln;n++)t+=o.get(e,n);return i=a.msg(o.count(e)),{content:t,caption:i}},footer:function(e,i,t){var a=o.data[e];if(t=void 0===t?!0:t,0===a.config.length||K(a.config[i]))return"";var r=a.config[i],n=J("caption",r)?r.caption:"",l=J("width",r)?r.width:"auto",s=J("url",r)?r.url:!1,d=J("key",r)?r.key:null,c=s===!1&&t,p=a.isDelete?a.actions(!1,!0,c,s,d):"",u=a.footer.replace(/\{actions}/g,p);return u.replace(/\{caption}/g,n).replace(/\{width}/g,l).replace(/\{indicator}/g,"").replace(/\{indicatorTitle}/g,"")}},s=function(e,i){return i=i||0,"number"==typeof e?e:("string"==typeof e&&(e=parseFloat(e)),isNaN(e)?i:e)},d=function(){return!(!window.File||!window.FileReader)},c=function(){var e=document.createElement("div");return!r(9)&&!n()&&(void 0!==e.draggable||void 0!==e.ondragstart&&void 0!==e.ondrop)},p=function(){return d()&&window.FormData},u=function(e,i){e.removeClass(i).addClass(i)},f='style="width:{width};height:{height};"',v=' \n \n \n \n \n \n',g='
    \n {previewFileIcon}\n
    ',m={removeIcon:'',removeClass:"btn btn-xs btn-default",removeTitle:"Remove file",uploadIcon:'',uploadClass:"btn btn-xs btn-default",uploadTitle:"Upload file",indicatorNew:'',indicatorSuccess:'',indicatorError:'',indicatorLoading:'',indicatorNewTitle:"Not uploaded yet",indicatorSuccessTitle:"Uploaded",indicatorErrorTitle:"Upload Error",indicatorLoadingTitle:"Uploading ..."},h='{preview}\n
    \n
    \n {caption}\n
    \n {remove}\n {cancel}\n {upload}\n {browse}\n
    \n
    ',w='{preview}\n
    \n{remove}\n{cancel}\n{upload}\n{browse}\n',_='
    \n {close}
    \n
    \n
    \n
    \n
    \n
    \n
    ',C='
    ×
    \n',b='',x='
    \n
    \n
    \n',y='',T='{icon}{label}',F='
    {icon}{label}
    ',E='',I='
    \n
    \n {percent}%\n
    \n
    ',$='',k='
    \n \n
    {indicator}
    \n
    \n
    ',P='\n',S='\n',D='\n',U='
    \n {content}\n {footer}\n
    \n',A='
    \n \n '+g+"\n \n {footer}\n
    ",j='
    \n {caption}\n {footer}\n
    \n",L='
    \n
    {data}
    \n {zoom}\n {footer}\n
    ",z='
    \n \n {footer}\n
    \n",O='
    \n \n {footer}\n
    ",R='
    \n \n'+v+" "+g+"\n \n {footer}\n
    \n",N='
    \n \n \n'+v+" "+g+"\n \n {footer}\n
    ",B='
    \n
    \n '+g+'\n
    \n \n
    ',M={main1:h,main2:w,preview:_,close:C,zoom:D,icon:b,caption:x,modal:E,progress:I,footer:$,actions:k,actionDelete:P,actionUpload:S,btnDefault:y,btnLink:T,btnBrowse:F},Z={generic:U,html:A,image:j,text:L,video:z,audio:O,flash:R,object:N,other:B},H=["image","html","text","video","audio","flash","object"],W={image:{width:"auto",height:"160px"},html:{width:"213px",height:"160px"},text:{width:"160px",height:"136px"},video:{width:"213px",height:"160px"},audio:{width:"213px",height:"80px"},flash:{width:"213px",height:"160px"},object:{width:"160px",height:"160px"},other:{width:"160px",height:"160px"}},V={image:function(e,i){return a(e,"image.*")||a(i,/\.(gif|png|jpe?g)$/i)},html:function(e,i){return a(e,"text/html")||a(i,/\.(htm|html)$/i)},text:function(e,i){return a(e,"text.*")||a(e,/\.(xml|javascript)$/i)||a(i,/\.(txt|md|csv|nfo|ini|json|php|js|css)$/i)},video:function(e,i){return a(e,"video.*")&&(a(e,/(ogg|mp4|mp?g|webm|3gp)$/i)||a(i,/\.(og?|mp4|webm|mp?g|3gp)$/i))},audio:function(e,i){return a(e,"audio.*")&&(a(e,/(ogg|mp3|mp?g|wav)$/i)||a(i,/\.(og?|mp3|mp?g|wav)$/i))},flash:function(e,i){return a(e,"application/x-shockwave-flash",!0)||a(i,/\.(swf)$/i)},object:function(e,i){return a(e,"application/pdf",!0)||a(i,/\.(pdf)$/i)},other:function(){return!0}},K=function(i,t){return void 0===i||null===i||0===i.length||t&&""===e.trim(i)},X=function(e){return Array.isArray(e)||"[object Array]"===Object.prototype.toString.call(e)},J=function(e,i){return"object"==typeof i&&e in i},Q=function(i,t,a){return K(i)||K(i[t])?a:e(i[t])},Y=function(){return Math.round((new Date).getTime()+100*Math.random())},G=function(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")},ee=function(i,t){var a=i;return t?(e.each(t,function(e,i){"function"==typeof i&&(i=i()),a=a.split(e).join(i)}),a):a},ie=function(e){var i=e.is("img")?e.attr("src"):e.find("source").attr("src");t.revokeObjectURL(i)},q=function(i,t){var a=this;a.$element=e(i),a._validate()&&(a.isPreviewable=d(),a.isIE9=r(9),a.isIE10=r(10),a.isPreviewable||a.isIE9?(a._init(t),a._listen()):a.$element.removeClass("file-loading"))},q.prototype={constructor:q,_init:function(i){var t,a=this,r=a.$element;e.each(i,function(e,i){switch(e){case"minFileCount":case"maxFileCount":case"maxFileSize":a[e]=s(i);break;default:a[e]=i}}),K(a.allowedPreviewTypes)&&(a.allowedPreviewTypes=H),a.fileInputCleared=!1,a.fileBatchCompleted=!0,a.isPreviewable||(a.showPreview=!1),a.uploadFileAttr=K(r.attr("name"))?"file_data":r.attr("name"),a.reader=null,a.formdata={},a.clearStack(),a.uploadCount=0,a.uploadStatus={},a.uploadLog=[],a.uploadAsyncCount=0,a.loadedImages=[],a.totalImagesCount=0,a.ajaxRequests=[],a.isError=!1,a.ajaxAborted=!1,a.cancelling=!1,t=a._getLayoutTemplate("progress"),a.progressTemplate=t.replace("{class}",a.progressClass),a.progressCompleteTemplate=t.replace("{class}",a.progressCompleteClass),a.progressErrorTemplate=t.replace("{class}",a.progressErrorClass),a.dropZoneEnabled=c()&&a.dropZoneEnabled,a.isDisabled=a.$element.attr("disabled")||a.$element.attr("readonly"),a.isUploadable=p()&&!K(a.uploadUrl),a.slug="function"==typeof i.slugCallback?i.slugCallback:a._slugDefault,a.mainTemplate=a.showCaption?a._getLayoutTemplate("main1"):a._getLayoutTemplate("main2"),a.captionTemplate=a._getLayoutTemplate("caption"),a.previewGenericTemplate=a._getPreviewTemplate("generic"),a.resizeImage&&(a.maxImageWidth||a.maxImageHeight)&&(a.imageCanvas=document.createElement("canvas"),a.imageCanvasContext=a.imageCanvas.getContext("2d")),K(a.$element.attr("id"))&&a.$element.attr("id",Y()),void 0===a.$container?a.$container=a._createContainer():a._refreshContainer(),a.$dropZone=a.$container.find(".file-drop-zone"),a.$progress=a.$container.find(".kv-upload-progress"),a.$btnUpload=a.$container.find(".fileinput-upload"),a.$captionContainer=Q(i,"elCaptionContainer",a.$container.find(".file-caption")),a.$caption=Q(i,"elCaptionText",a.$container.find(".file-caption-name")),a.$previewContainer=Q(i,"elPreviewContainer",a.$container.find(".file-preview")),a.$preview=Q(i,"elPreviewImage",a.$container.find(".file-preview-thumbnails")),a.$previewStatus=Q(i,"elPreviewStatus",a.$container.find(".file-preview-status")),a.$errorContainer=Q(i,"elErrorContainer",a.$previewContainer.find(".kv-fileinput-error")),K(a.msgErrorClass)||u(a.$errorContainer,a.msgErrorClass),a.$errorContainer.hide(),a.fileActionSettings=e.extend(!0,m,i.fileActionSettings),a.previewInitId="preview-"+Y(),a.id=a.$element.attr("id"),o.init(a),a._initPreview(!0),a._initPreviewDeletes(),a.options=i,a._setFileDropZoneTitle(),a.$element.removeClass("file-loading"),a.$element.attr("disabled")&&a.disable()},_validate:function(){var e,i=this;return"file"===i.$element.attr("type")?!0:(e='

    Invalid Input Type

    You must set an input type = file for bootstrap-fileinput plugin to initialize.
    ',i.$element.after(e),!1)},_errorsExist:function(){var i,t=this;return t.$errorContainer.find("li").length?!0:(i=e(document.createElement("div")).html(t.$errorContainer.html()),i.find("span.kv-error-close").remove(),i.find("ul").remove(),e.trim(i.text()).length?!0:!1)},_errorHandler:function(e,i){var t=this,a=e.target.error;a.code===a.NOT_FOUND_ERR?t._showError(t.msgFileNotFound.replace("{name}",i)):a.code===a.SECURITY_ERR?t._showError(t.msgFileSecured.replace("{name}",i)):a.code===a.NOT_READABLE_ERR?t._showError(t.msgFileNotReadable.replace("{name}",i)):a.code===a.ABORT_ERR?t._showError(t.msgFilePreviewAborted.replace("{name}",i)):t._showError(t.msgFilePreviewError.replace("{name}",i))},_addError:function(e){var i=this,t=i.$errorContainer;e&&t.length&&(t.html(i.errorCloseButton+e),l(t.find(".kv-error-close"),"click",function(){t.fadeOut("slow")}))},_resetErrors:function(e){var i=this,t=i.$errorContainer;i.isError=!1,i.$container.removeClass("has-error"),t.html(""),e?t.fadeOut("slow"):t.hide()},_showFolderError:function(e){var i,t=this,a=t.$errorContainer;e&&(i=t.msgFoldersNotAllowed.replace(/\{n}/g,e),t._addError(i),u(t.$container,"has-error"),a.fadeIn(800),t._raise("filefoldererror",[e,i]))},_showUploadError:function(e,i,t){var a=this,r=a.$errorContainer,n=t||"fileuploaderror",l=i&&i.id?'
  • '+e+"
  • ":"
  • "+e+"
  • ";return 0===r.find("ul").length?a._addError("
      "+l+"
    "):r.find("ul").append(l),r.fadeIn(800),a._raise(n,[i,e]),a.$container.removeClass("file-input-new"),u(a.$container,"has-error"),!0},_showError:function(e,i,t){var a=this,r=a.$errorContainer,n=t||"fileerror";return i=i||{},i.reader=a.reader,a._addError(e),r.fadeIn(800),a._raise(n,[i,e]),a.isUploadable||a._clearFileInput(),a.$container.removeClass("file-input-new"),u(a.$container,"has-error"),a.$btnUpload.attr("disabled",!0),!0},_noFilesError:function(e){var i=this,t=i.minFileCount>1?i.filePlural:i.fileSingle,a=i.msgFilesTooLess.replace("{n}",i.minFileCount).replace("{files}",t),r=i.$errorContainer;i._addError(a),i.isError=!0,i._updateFileDetails(0),r.fadeIn(800),i._raise("fileerror",[e,a]),i._clearFileInput(),u(i.$container,"has-error")},_parseError:function(i,t,a){var r=this,n=e.trim(t+""),l="."===n.slice(-1)?"":".",o=void 0!==i.responseJSON&&void 0!==i.responseJSON.error?i.responseJSON.error:i.responseText;return r.cancelling&&r.msgUploadAborted&&(n=r.msgUploadAborted),r.showAjaxErrorDetails&&o?(o=e.trim(o.replace(/\n\s*\n/g,"\n")),o=o.length>0?"
    "+o+"
    ":"",n+=l+o):n+=l,r.cancelling=!1,a?""+a+": "+n:n},_parseFileType:function(e){var i,t,a,r,n=this;for(r=0;r-1&&(a=t.split(".").pop(),r.previewFileIconSettings&&r.previewFileIconSettings[a]&&(n=r.previewFileIconSettings[a]),r.previewFileExtSettings&&e.each(r.previewFileExtSettings,function(e,i){r.previewFileIconSettings[e]&&i(a)&&(n=r.previewFileIconSettings[e])})),i.indexOf("{previewFileIcon}")>-1?i.replace(/\{previewFileIconClass}/g,r.previewFileIconClass).replace(/\{previewFileIcon}/g,n):i},_raise:function(i,t){var a=this,r=e.Event(i);if(void 0!==t?a.$element.trigger(r,t):a.$element.trigger(r),r.isDefaultPrevented())return!1;if(!r.result)return r.result;switch(i){case"filebatchuploadcomplete":case"filebatchuploadsuccess":case"fileuploaded":case"fileclear":case"filecleared":case"filereset":case"fileerror":case"filefoldererror":case"fileuploaderror":case"filebatchuploaderror":case"filedeleteerror":case"filecustomerror":case"filesuccessremove":break;default:a.ajaxAborted=r.result}return!0},_listen:function(){var i=this,t=i.$element,a=t.closest("form"),r=i.$container;l(t,"change",e.proxy(i._change,i)),l(i.$btnFile,"click",e.proxy(i._browse,i)),l(a,"reset",e.proxy(i.reset,i)),l(r.find(".fileinput-remove:not([disabled])"),"click",e.proxy(i.clear,i)),l(r.find(".fileinput-cancel"),"click",e.proxy(i.cancel,i)),i._initDragDrop(),i.isUploadable||l(a,"submit",e.proxy(i._submitForm,i)),l(i.$container.find(".fileinput-upload"),"click",e.proxy(i._uploadClick,i))},_initDragDrop:function(){var i=this,t=i.$dropZone;i.isUploadable&&i.dropZoneEnabled&&i.showPreview&&(l(t,"dragenter dragover",e.proxy(i._zoneDragEnter,i)),l(t,"dragleave",e.proxy(i._zoneDragLeave,i)),l(t,"drop",e.proxy(i._zoneDrop,i)),l(e(document),"dragenter dragover drop",i._zoneDragDropInit))},_zoneDragDropInit:function(e){e.stopPropagation(),e.preventDefault()},_zoneDragEnter:function(i){var t=this,a=e.inArray("Files",i.originalEvent.dataTransfer.types)>-1;return t._zoneDragDropInit(i),t.isDisabled||!a?(i.originalEvent.dataTransfer.effectAllowed="none",void(i.originalEvent.dataTransfer.dropEffect="none")):void u(t.$dropZone,"file-highlighted")},_zoneDragLeave:function(e){var i=this;i._zoneDragDropInit(e),i.isDisabled||i.$dropZone.removeClass("file-highlighted")},_zoneDrop:function(e){var i=this;e.preventDefault(),i.isDisabled||K(e.originalEvent.dataTransfer.files)||(i._change(e,"dragdrop"),i.$dropZone.removeClass("file-highlighted"))},_uploadClick:function(e){var i,t=this,a=t.$container.find(".fileinput-upload"),r=!a.hasClass("disabled")&&K(a.attr("disabled"));if(!e||!e.isDefaultPrevented()){if(!t.isUploadable)return void(r&&"submit"!==a.attr("type")&&(i=a.closest("form"),i.length&&i.trigger("submit"),e.preventDefault()));e.preventDefault(),r&&t.upload()}},_submitForm:function(){var e=this,i=e.$element,t=i.get(0).files;return t&&e.minFileCount>0&&e._getFileCount(t.length)0?i._getMsgSelected(l):"",i._raise("filedeleted",[s,n,f]),i._setCaption(d),v.removeClass("file-uploading").addClass("file-deleted"),void v.fadeOut("slow",function(){i._clearObjects(v),v.remove(),a(),l||0!==i.getFileStack().length||(i._setCaption(""),i.reset())})):(c.jqXHR=n,c.response=e,i._showError(e.error,c,"filedeleteerror"),v.removeClass("file-uploading"),r.removeClass("disabled"),void a())},error:function(e,t,r){var n=i._parseError(e,r);c.jqXHR=e,c.response={},i._showError(n,c,"filedeleteerror"),v.removeClass("file-uploading"),a()}},i.ajaxDeleteSettings),l(r,"click",function(){return i._validateMinCount()?void e.ajax(d):!1})}})},_clearObjects:function(i){i.find("video audio").each(function(){this.pause(),e(this).remove()}),i.find("img object div").each(function(){e(this).remove()})},_clearFileInput:function(){var i,t,a,r=this,n=r.$element;K(n.val())||(r.isIE9||r.isIE10?(i=n.closest("form"),t=e(document.createElement("form")),a=e(document.createElement("div")),n.before(a),i.length?i.after(t):a.after(t),t.append(n).trigger("reset"),a.before(n).remove(),t.remove()):n.val(""),r.fileInputCleared=!0)},_resetUpload:function(){var e=this;e.uploadCache={content:[],config:[],tags:[],append:!0},e.uploadCount=0,e.uploadStatus={},e.uploadLog=[],e.uploadAsyncCount=0,e.loadedImages=[],e.totalImagesCount=0,e.$btnUpload.removeAttr("disabled"),e._setProgress(0),u(e.$progress,"hide"),e._resetErrors(!1),e.ajaxAborted=!1,e.ajaxRequests=[],e._resetCanvas()},_resetCanvas:function(){var e=this;e.canvas&&e.imageCanvasContext&&e.imageCanvasContext.clearRect(0,0,e.canvas.width,e.canvas.height)},_hasInitialPreview:function(){var e=this;return!e.overwriteInitial&&o.count(e.id)},_resetPreview:function(){var e,i,t=this;o.count(t.id)?(e=o.out(t.id),t.$preview.html(e.content),i=t.initialCaption?t.initialCaption:e.caption,t._setCaption(i)):(t._clearPreview(),t._initCaption())},_clearDefaultPreview:function(){var e=this;e.$preview.find(".file-default-preview").remove()},_validateDefaultPreview:function(){var e=this;e.showPreview&&!K(e.defaultPreviewContent)&&(e.$preview.html('
    '+e.defaultPreviewContent+"
    "),e.$container.removeClass("file-input-new"))},_resetPreviewThumbs:function(e){var i,t=this;return e?(t._clearPreview(),void t.clearStack()):void(t._hasInitialPreview()?(i=o.out(t.id),t.$preview.html(i.content),t._setCaption(i.caption),t._initPreviewDeletes()):t._clearPreview())},_getLayoutTemplate:function(e){var i=this,t=J(e,i.layoutTemplates)?i.layoutTemplates[e]:M[e];return K(i.customLayoutTags)?t:ee(t,i.customLayoutTags)},_getPreviewTemplate:function(e){var i=this,t=J(e,i.previewTemplates)?i.previewTemplates[e]:Z[e];return K(i.customPreviewTags)?t:ee(t,i.customPreviewTags)},_getOutData:function(e,i,t){var a=this;return e=e||{},i=i||{},t=t||a.filestack.slice(0)||{},{form:a.formdata,files:t,filenames:a.filenames,extra:a._getExtraData(),response:i,reader:a.reader,jqXHR:e}},_getMsgSelected:function(e){var i=this,t=1===e?i.fileSingle:i.filePlural;return i.msgSelected.replace("{n}",e).replace("{files}",t)},_getThumbs:function(e){return e=e||"",this.$preview.find(".file-preview-frame:not(.file-preview-initial)"+e)},_getExtraData:function(e,i){var t=this,a=t.uploadExtraData;return"function"==typeof t.uploadExtraData&&(a=t.uploadExtraData(e,i)),a},_initXhr:function(e,i,t){var a=this;return e.upload&&e.upload.addEventListener("progress",function(e){var r=0,n=e.loaded||e.position,l=e.total;e.lengthComputable&&(r=Math.ceil(n/l*100)),i?a._setAsyncUploadStatus(i,r,t):a._setProgress(Math.ceil(r))},!1),e},_ajaxSubmit:function(i,t,a,r,n,l){var o,s=this;s._raise("filepreajax",[n,l]),s._uploadExtra(n,l),o=e.extend(!0,{},{xhr:function(){var i=e.ajaxSettings.xhr();return s._initXhr(i,n,s.getFileStack().length)},url:s.uploadUrl,type:"POST",dataType:"json",data:s.formdata,cache:!1,processData:!1,contentType:!1,beforeSend:i,success:t,complete:a,error:r},s.ajaxSettings),s.ajaxRequests.push(e.ajax(o))},_initUploadSuccess:function(i,t,a){var r,n,l,s,d,c,p,u,f=this;f.showPreview&&"object"==typeof i&&!e.isEmptyObject(i)&&void 0!==i.initialPreview&&i.initialPreview.length>0&&(f.hasInitData=!0,d=i.initialPreview||[],c=i.initialPreviewConfig||[],p=i.initialPreviewThumbTags||[],r=void 0===i.append||i.append?!0:!1,f.overwriteInitial=!1,void 0!==t?a?(u=t.attr("data-fileindex"),f.uploadCache.content[u]=d[0],f.uploadCache.config[u]=c[0],f.uploadCache.tags[u]=p[0],f.uploadCache.append=r):(l=o.add(f.id,d,c[0],p[0],r),n=o.get(f.id,l,!1),s=e(n).hide(),t.after(s).fadeOut("slow",function(){s.fadeIn("slow").css("display:inline-block"),f._initPreviewDeletes(),f._clearFileInput(),t.remove()})):(o.set(f.id,d,c,p,r),f._initPreview(),f._initPreviewDeletes()))},_initSuccessThumbs:function(){var i=this;i.showPreview&&i._getThumbs(".file-preview-success").each(function(){var t=e(this),a=t.find(".kv-file-remove");a.removeAttr("disabled"),l(a,"click",function(){var e=i._raise("filesuccessremove",[t.attr("id"),t.data("fileindex")]);ie(t),e!==!1&&t.fadeOut("slow",function(){t.remove(),i.$preview.find(".file-preview-frame").length||i.reset()})})})},_checkAsyncComplete:function(){var i,t,a=this;for(t=0;t0||!e.isEmptyObject(m.uploadExtraData),C={id:_,index:i};m.formdata=w,m.showPreview&&(n=e("#"+_+":not(.file-preview-initial)"),s=n.find(".kv-file-upload"),d=n.find(".kv-file-remove"),e("#"+_).find(".file-thumb-progress").removeClass("hide")),0===h||!b||s&&s.hasClass("disabled")||m._abort(C)||(g=function(e,i){m.updateStack(e,void 0),m.uploadLog.push(i),m._checkAsyncComplete()&&(m.fileBatchCompleted=!0)},l=function(){m.fileBatchCompleted&&setTimeout(function(){m.showPreview&&(o.set(m.id,m.uploadCache.content,m.uploadCache.config,m.uploadCache.tags,m.uploadCache.append),m.hasInitData&&(m._initPreview(),m._initPreviewDeletes())),m.unlock(),m._clearFileInput(),m._raise("filebatchuploadcomplete",[m.filestack,m._getExtraData()]),m.uploadCount=0,m.uploadStatus={},m.uploadLog=[],m._setProgress(100)},100)},c=function(t){r=m._getOutData(t),m.fileBatchCompleted=!1,m.showPreview&&(n.hasClass("file-preview-success")||(m._setThumbStatus(n,"Loading"),u(n,"file-uploading")),s.attr("disabled",!0),d.attr("disabled",!0)),a||m.lock(),m._raise("filepreupload",[r,_,i]),e.extend(!0,C,r),m._abort(C)&&(t.abort(),m._setProgressCancelled())},p=function(t,l,o){r=m._getOutData(o,t),e.extend(!0,C,r),setTimeout(function(){K(t)||K(t.error)?(m.showPreview&&(m._setThumbStatus(n,"Success"),s.hide(),m._initUploadSuccess(t,n,a)),m._raise("fileuploaded",[r,_,i]),a?g(i,_):m.updateStack(i,void 0)):(m._showUploadError(t.error,C),m._setPreviewError(n,i),a&&g(i,_))},100)},f=function(){setTimeout(function(){m.showPreview&&(s.removeAttr("disabled"),d.removeAttr("disabled"),n.removeClass("file-uploading")),a?l():(m.unlock(!1),m._clearFileInput()),m._initSuccessThumbs()},100)},v=function(r,l,o){var s=m._parseError(r,o,a?t[i].name:null);setTimeout(function(){a&&g(i,_),m.uploadStatus[_]=100,m._setPreviewError(n,i),e.extend(!0,C,m._getOutData(r)),m._showUploadError(s,C)},100)},w.append(m.uploadFileAttr,t[i],m.filenames[i]),w.append("file_id",i),m._ajaxSubmit(c,p,f,v,_,i))},_uploadBatch:function(){var i,t,a,r,n,l=this,o=l.filestack,s=o.length,d={},c=l.filestack.length>0||!e.isEmptyObject(l.uploadExtraData);l.formdata=new FormData,0!==s&&c&&!l._abort(d)&&(n=function(){e.each(o,function(e){l.updateStack(e,void 0)}),l._clearFileInput()},i=function(i){l.lock();var t=l._getOutData(i);l.showPreview&&l._getThumbs().each(function(){var i=e(this),t=i.find(".kv-file-upload"),a=i.find(".kv-file-remove");i.hasClass("file-preview-success")||(l._setThumbStatus(i,"Loading"),u(i,"file-uploading")),t.attr("disabled",!0),a.attr("disabled",!0)}),l._raise("filebatchpreupload",[t]),l._abort(t)&&(i.abort(),l._setProgressCancelled())},t=function(i,t,a){var r=l._getOutData(a,i),o=l._getThumbs(),s=0,d=K(i)||K(i.errorkeys)?[]:i.errorkeys;K(i)||K(i.error)?(l._raise("filebatchuploadsuccess",[r]),n(),l.showPreview?(o.each(function(){var i=e(this),t=i.find(".kv-file-upload");i.find(".kv-file-upload").hide(),l._setThumbStatus(i,"Success"),i.removeClass("file-uploading"),t.removeAttr("disabled")}),l._initUploadSuccess(i)):l.reset()):(l.showPreview&&(o.each(function(){var i=e(this),t=i.find(".kv-file-remove"),a=i.find(".kv-file-upload");return i.removeClass("file-uploading"),a.removeAttr("disabled"),t.removeAttr("disabled"),0===d.length?void l._setPreviewError(i):(-1!==e.inArray(s,d)?l._setPreviewError(i):(i.find(".kv-file-upload").hide(),l._setThumbStatus(i,"Success"),l.updateStack(s,void 0)),void s++)}),l._initUploadSuccess(i)),l._showUploadError(i.error,r,"filebatchuploaderror"))},r=function(){l._setProgress(100),l.unlock(),l._initSuccessThumbs(),l._clearFileInput(),l._raise("filebatchuploadcomplete",[l.filestack,l._getExtraData()])},a=function(i,t,a){var r=l._getOutData(i),n=l._parseError(i,a);l._showUploadError(n,r,"filebatchuploaderror"),l.uploadFileCount=s-1,l.showPreview&&(l._getThumbs().each(function(){var i=e(this),t=i.attr("data-fileindex");i.removeClass("file-uploading"),void 0!==l.filestack[t]&&l._setPreviewError(i)}),l._getThumbs().removeClass("file-uploading"),l._getThumbs(" .kv-file-upload").removeAttr("disabled"),l._getThumbs(" .kv-file-delete").removeAttr("disabled"))},e.each(o,function(e,i){K(o[e])||l.formdata.append(l.uploadFileAttr,i,l.filenames[e])}),l._ajaxSubmit(i,t,r,a))},_uploadExtraOnly:function(){var e,i,t,a,r=this,n={};r.formdata=new FormData,r._abort(n)||(e=function(e){r.lock(); -var i=r._getOutData(e);r._raise("filebatchpreupload",[i]),r._setProgress(50),n.data=i,n.xhr=e,r._abort(n)&&(e.abort(),r._setProgressCancelled())},i=function(e,i,t){var a=r._getOutData(t,e);K(e)||K(e.error)?(r._raise("filebatchuploadsuccess",[a]),r._clearFileInput(),r._initUploadSuccess(e)):r._showUploadError(e.error,a,"filebatchuploaderror")},t=function(){r._setProgress(100),r.unlock(),r._clearFileInput(),r._raise("filebatchuploadcomplete",[r.filestack,r._getExtraData()])},a=function(e,i,t){var a=r._getOutData(e),l=r._parseError(e,t);n.data=a,r._showUploadError(l,a,"filebatchuploaderror")},r._ajaxSubmit(e,i,t,a))},_initFileActions:function(){var i=this;i.showPreview&&(i.$preview.find(".kv-file-remove").each(function(){var t,a,r,n,s=e(this),d=s.closest(".file-preview-frame"),c=d.attr("id"),p=d.attr("data-fileindex");l(s,"click",function(){return n=i._raise("filepreremove",[c,p]),n!==!1&&i._validateMinCount()?(t=d.hasClass("file-preview-error"),ie(d),void d.fadeOut("slow",function(){i.updateStack(p,void 0),i._clearObjects(d),d.remove(),c&&t&&i.$errorContainer.find('li[data-file-id="'+c+'"]').fadeOut("fast",function(){e(this).remove(),i._errorsExist()||i._resetErrors()});var n=i.getFileStack(!0),l=n.length,s=o.count(i.id),u=i.showPreview&&i.$preview.find(".file-preview-frame").length;i._clearFileInput(),0!==l||0!==s||u?(a=s+l,r=a>1?i._getMsgSelected(a):n[0]?i._getFileNames()[0]:"",i._setCaption(r)):i.reset(),i._raise("fileremoved",[c,p])})):!1})}),i.$preview.find(".kv-file-upload").each(function(){var t=e(this);l(t,"click",function(){var e=t.closest(".file-preview-frame"),a=e.attr("data-fileindex");e.hasClass("file-preview-error")||i._uploadSingle(a,i.filestack,!1)})}))},_hideFileIcon:function(){this.overwriteInitial&&this.$captionContainer.find(".kv-caption-icon").hide()},_showFileIcon:function(){this.$captionContainer.find(".kv-caption-icon").show()},_previewDefault:function(i,a,r){if(this.showPreview){var n=this,l="",o=i?i.name:"",s=t.createObjectURL(i),d=a.slice(a.lastIndexOf("-")+1),c=n.previewSettings.other||W.other,p=n._renderFileFooter(i.name,c.width),u=n._parseFilePreviewIcon(n._getPreviewTemplate("other"),o);r===!0&&(n.isUploadable||(p+='
    '+n.fileActionSettings.indicatorError+"
    ")),n._clearDefaultPreview(),n.$preview.append("\n"+u.replace(/\{previewId}/g,a).replace(/\{frameClass}/g,l).replace(/\{fileindex}/g,d).replace(/\{caption}/g,n.slug(i.name)).replace(/\{width}/g,c.width).replace(/\{height}/g,c.height).replace(/\{type}/g,i.type).replace(/\{data}/g,s).replace(/\{footer}/g,p)),r===!0&&n.isUploadable&&n._setThumbStatus(e("#"+a),"Error")}},_previewFile:function(e,i,t,a,r){if(this.showPreview){var n,l,o,s=this,d=s._parseFileType(i),c=i?i.name:"",p=s.slug(c),u=s.allowedPreviewTypes,f=s.allowedPreviewMimeTypes,v=s._getPreviewTemplate(d),g=u&&u.indexOf(d)>=0,m=J(d,s.previewSettings)?s.previewSettings[d]:W[d],h=f&&-1!==f.indexOf(i.type),w=s._renderFileFooter(p,m.width),_="",b=a.slice(a.lastIndexOf("-")+1);g||h?(v=s._parseFilePreviewIcon(v,c.split(".").pop()),"text"===d?(l=G(t.target.result),o="text-"+Y(),n=v.replace(/\{zoom}/g,s._getLayoutTemplate("zoom")),_=s._getLayoutTemplate("modal").replace("{id}",o).replace(/\{title}/g,p).replace(/\{body}/g,l).replace(/\{heading}/g,s.msgZoomModalHeading),n=n.replace(/\{previewId}/g,a).replace(/\{caption}/g,p).replace(/\{width}/g,m.width).replace(/\{height}/g,m.height).replace(/\{frameClass}/g,"").replace(/\{zoomInd}/g,s.zoomIndicator).replace(/\{footer}/g,w).replace(/\{fileindex}/g,b).replace(/\{type}/g,i.type).replace(/\{zoomTitle}/g,s.msgZoomTitle).replace(/\{dialog}/g,"$('#"+o+"').modal('show')").replace(/\{data}/g,l)+_):n=v.replace(/\{previewId}/g,a).replace(/\{caption}/g,p).replace(/\{frameClass}/g,"").replace(/\{type}/g,i.type).replace(/\{fileindex}/g,b).replace(/\{width}/g,m.width).replace(/\{height}/g,m.height).replace(/\{footer}/g,w).replace(/\{data}/g,r),s._clearDefaultPreview(),s.$preview.append("\n"+n),s._validateImage(e,a,p,i.type)):s._previewDefault(i,a)}},_slugDefault:function(e){return K(e)?"":String(e).replace(/[\-\[\]\/\{}:;#%=\(\)\*\+\?\\\^\$\|<>&"']/g,"_")},_readFiles:function(i){this.reader=new FileReader;var r,n=this,l=n.$element,o=n.$preview,s=n.reader,d=n.$previewContainer,c=n.$previewStatus,p=n.msgLoading,u=n.msgProgress,f=n.previewInitId,v=i.length,g=n.fileTypeSettings,m=n.filestack.length,h=function(t,a,l,o){var s=e.extend(!0,{},n._getOutData({},{},i),{id:l,index:o}),d={id:l,index:o,file:a,files:i};return n._previewDefault(a,l,!0),n.isUploadable&&n.addToStack(void 0),setTimeout(r(o+1),100),n._initFileActions(),n.removeFromPreviewOnError&&e("#"+l).remove(),n.isUploadable?n._showUploadError(t,s):n._showError(t,d)};n.loadedImages=[],n.totalImagesCount=0,e.each(i,function(e,i){var t=n.fileTypeSettings.image||V.image;t&&t(i.type)&&n.totalImagesCount++}),r=function(e){if(K(l.attr("multiple"))&&(v=1),e>=v)return n.isUploadable&&n.filestack.length>0?n._raise("filebatchselected",[n.getFileStack()]):n._raise("filebatchselected",[i]),d.removeClass("file-thumb-loading"),void c.html("");var w,_,b,C,x,y,T=m+e,F=f+"-"+T,E=i[e],I=n.slug(E.name),$=(E.size||0)/1e3,k="",P=t.createObjectURL(E),S=0,D=n.allowedFileTypes,U=K(D)?"":D.join(", "),A=n.allowedFileExtensions,j=K(A)?"":A.join(", ");if(K(A)||(k=new RegExp("\\.("+A.join("|")+")$","i")),$=$.toFixed(2),n.maxFileSize>0&&$>n.maxFileSize)return C=n.msgSizeTooLarge.replace("{name}",I).replace("{size}",$).replace("{maxSize}",n.maxFileSize),void(n.isError=h(C,E,F,e));if(!K(D)&&X(D)){for(b=0;b0&&void 0!==FileReader?(c.html(p.replace("{index}",e+1).replace("{files}",v)),d.addClass("file-thumb-loading"),s.onerror=function(e){n._errorHandler(e,I)},s.onload=function(i){n._previewFile(e,E,i,F,P),n._initFileActions()},s.onloadend=function(){C=u.replace("{index}",e+1).replace("{files}",v).replace("{percent}",50).replace("{name}",I),setTimeout(function(){c.html(C),n._updateFileDetails(v),r(e+1)},100),n._raise("fileloaded",[E,F,e,s])},s.onprogress=function(i){if(i.lengthComputable){var t=i.loaded/i.total*100,a=Math.ceil(t);C=u.replace("{index}",e+1).replace("{files}",v).replace("{percent}",a).replace("{name}",I),setTimeout(function(){c.html(C)},100)}},w=J("text",g)?g.text:V.text,w(E.type,I)?s.readAsText(E,n.textEncoding):s.readAsArrayBuffer(E)):(n._previewDefault(E,F),setTimeout(function(){r(e+1),n._updateFileDetails(v)},100),n._raise("fileloaded",[E,F,e,s])),void n.addToStack(E)):(n.addToStack(E),setTimeout(r(e+1),100),void n._raise("fileloaded",[E,F,e,s])):(C=n.msgInvalidFileExtension.replace("{name}",I).replace("{extensions}",j),void(n.isError=h(C,E,F,e)))},r(0),n._updateFileDetails(v,!1)},_updateFileDetails:function(e){var i=this,t=i.$element,a=i.getFileStack(),r=t[0].files[0]&&t[0].files[0].name||a.length&&a[0].name||"",n=i.slug(r),l=i.isUploadable?a.length:e,s=o.count(i.id)+l,d=l>1?i._getMsgSelected(s):n;i.isError?(i.$previewContainer.removeClass("file-thumb-loading"),i.$previewStatus.html(""),i.$captionContainer.find(".kv-caption-icon").hide()):i._showFileIcon(),i._setCaption(d,i.isError),i.$container.removeClass("file-input-new file-input-ajax-new"),1===arguments.length&&i._raise("fileselect",[e,n]),o.count(i.id)&&i._initPreviewDeletes()},_setThumbStatus:function(e,i){var t=this;if(t.showPreview){var a="indicator"+i,r=a+"Title",n="file-preview-"+i.toLowerCase(),l=e.find(".file-upload-indicator"),o=t.fileActionSettings;e.removeClass("file-preview-success file-preview-error file-preview-loading"),"Error"===i&&e.find(".kv-file-upload").attr("disabled",!0),l.html(o[a]),l.attr("title",o[r]),e.addClass(n)}},_setProgressCancelled:function(){var e=this;e._setProgress(100,e.$progress,e.msgCancelled)},_setProgress:function(e,i,t){var a=this,r=Math.min(e,100),n=100>r?a.progressTemplate:t?a.progressErrorTemplate:a.progressCompleteTemplate;i=i||a.$progress,K(n)||(i.html(n.replace(/\{percent}/g,r)),t&&i.find('[role="progressbar"]').html(t))},_setFileDropZoneTitle:function(){var e=this,i=e.$container.find(".file-drop-zone");i.find("."+e.dropZoneTitleClass).remove(),e.isUploadable&&e.showPreview&&0!==i.length&&!(e.getFileStack().length>0)&&e.dropZoneEnabled&&(0===i.find(".file-preview-frame").length&&K(e.defaultPreviewContent)&&i.prepend('
    '+e.dropZoneTitle+"
    "),e.$container.removeClass("file-input-new"),u(e.$container,"file-input-ajax-new"))},_setAsyncUploadStatus:function(i,t,a){var r=this,n=0;r._setProgress(t,e("#"+i).find(".file-thumb-progress")),r.uploadStatus[i]=t,e.each(r.uploadStatus,function(e,i){n+=i}),r._setProgress(Math.ceil(n/a))},_validateMinCount:function(){var e=this,i=e.isUploadable?e.getFileStack().length:e.$element.get(0).files.length;return e.validateInitialCount&&e.minFileCount>0&&e._getFileCount(i-1)=f:f>=s,c||(o=p["msgImage"+n+i].replace("{name}",r).replace("{size}",f),p._showUploadError(o,l),p._setPreviewError(a,e,null)))},_validateImage:function(e,i,a,r){var n,o,s,d=this,c=d.$preview,p=c.find("#"+i),u=p.find("img");a=a||"Untitled",u.length&&l(u,"load",function(){o=p.width(),s=c.width(),o>s&&(u.css("width","100%"),p.css("width","97%")),n={ind:e,id:i},d._checkDimensions(e,"Small",u,p,a,"Width",n),d._checkDimensions(e,"Small",u,p,a,"Height",n),d.resizeImage||(d._checkDimensions(e,"Large",u,p,a,"Width",n),d._checkDimensions(e,"Large",u,p,a,"Height",n)),d._raise("fileimageloaded",[i]),d.loadedImages.push({ind:e,img:u,thumb:p,pid:i,typ:r}),d._validateAllImages(),t.revokeObjectURL(u.attr("src"))})},_validateAllImages:function(){var e,i,t,a,r,n,l,o=this,s={};if(o.loadedImages.length===o.totalImagesCount&&(o._raise("fileimagesloaded"),o.resizeImage)){for(l=o.isUploadable?o._showUploadError:o._showError,e=0;ec,n=s>p,d="width"===l.resizePreference?r?c/o:n?p/s:1:n?p/s:r?c/o:1,l._resetCanvas(),o*=d,s*=d,f.width=o,f.height=s;try{return v.drawImage(e,0,0,o,s),f.toBlob(function(e){l._raise("fileimageresized",[t,a]),l.filestack[a]=e},i,l.resizeQuality),!0}catch(g){return!1}},_initBrowse:function(e){var i=this;i.$btnFile=e.find(".btn-file"),i.$btnFile.append(i.$element)},_initCaption:function(){var e=this,i=e.initialCaption||"";return e.overwriteInitial||K(i)?(e.$caption.html(""),!1):(e._setCaption(i),!0)},_setCaption:function(i,t){var a,r,n,l,o=this,s=o.getFileStack();if(o.$caption.length){if(t)a=e("
    "+o.msgValidationError+"
    ").text(),n=s.length,l=n?1===n&&s[0]?o._getFileNames()[0]:o._getMsgSelected(n):o._getMsgSelected(o.msgNo),r=''+o.msgValidationErrorIcon+(K(i)?l:i)+"";else{if(K(i))return;a=e("
    "+i+"
    ").text(),r=o._getLayoutTemplate("icon")+a}o.$caption.html(r),o.$caption.attr("title",a),o.$captionContainer.find(".file-caption-ellipsis").attr("title",a)}},_createContainer:function(){var i=this,t=e(document.createElement("div")).attr({"class":"file-input file-input-new"}).html(i._renderMain());return i.$element.before(t),i._initBrowse(t),t},_refreshContainer:function(){var e=this,i=e.$container;i.before(e.$element),i.html(e._renderMain()),e._initBrowse(i)},_renderMain:function(){var e=this,i=e.isUploadable&&e.dropZoneEnabled?" file-drop-zone":"file-drop-disabled",t=e.showClose?e._getLayoutTemplate("close"):"",a=e.showPreview?e._getLayoutTemplate("preview").replace(/\{class}/g,e.previewClass).replace(/\{dropClass}/g,i):"",r=e.isDisabled?e.captionClass+" file-caption-disabled":e.captionClass,n=e.captionTemplate.replace(/\{class}/g,r+" kv-fileinput-caption");return e.mainTemplate.replace(/\{class}/g,e.mainClass).replace(/\{preview}/g,a).replace(/\{close}/g,t).replace(/\{caption}/g,n).replace(/\{upload}/g,e._renderButton("upload")).replace(/\{remove}/g,e._renderButton("remove")).replace(/\{cancel}/g,e._renderButton("cancel")).replace(/\{browse}/g,e._renderButton("browse"))},_renderButton:function(e){var i=this,t=i._getLayoutTemplate("btnDefault"),a=i[e+"Class"],r=i[e+"Title"],n=i[e+"Icon"],l=i[e+"Label"],o=i.isDisabled?" disabled":"",s="button";switch(e){case"remove":if(!i.showRemove)return"";break;case"cancel":if(!i.showCancel)return"";a+=" hide";break;case"upload":if(!i.showUpload)return"";i.isUploadable&&!i.isDisabled?t=i._getLayoutTemplate("btnLink").replace("{href}",i.uploadUrl):s="submit";break;case"browse":t=i._getLayoutTemplate("btnBrowse");break;default:return""}return a+="browse"===e?" btn-file":" fileinput-"+e+" fileinput-"+e+"-button",K(l)||(l=' '+l+""),t.replace("{type}",s).replace("{css}",a).replace("{title}",r).replace("{status}",o).replace("{icon}",n).replace("{label}",l)},_renderThumbProgress:function(){return'
    '+this.progressTemplate.replace(/\{percent}/g,"0")+"
    "},_renderFileFooter:function(e,i){var t,a,r=this,n=r.fileActionSettings,l=r._getLayoutTemplate("footer");return r.isUploadable?(t=l.replace(/\{actions}/g,r._renderFileActions(!0,!0,!1,!1,!1)),a=t.replace(/\{caption}/g,e).replace(/\{width}/g,i).replace(/\{progress}/g,r._renderThumbProgress()).replace(/\{indicator}/g,n.indicatorNew).replace(/\{indicatorTitle}/g,n.indicatorNewTitle)):a=l.replace(/\{actions}/g,"").replace(/\{caption}/g,e).replace(/\{progress}/g,"").replace(/\{width}/g,i).replace(/\{indicator}/g,"").replace(/\{indicatorTitle}/g,""),a=ee(a,r.previewThumbTags)},_renderFileActions:function(e,i,t,a,r){if(!e&&!i)return"";var n=this,l=a===!1?"":' data-url="'+a+'"',o=r===!1?"":' data-key="'+r+'"',s=n._getLayoutTemplate("actionDelete"),d="",c=n._getLayoutTemplate("actions"),p=n.otherActionButtons.replace(/\{dataKey}/g,o),u=n.fileActionSettings,f=t?u.removeClass+" disabled":u.removeClass;return s=s.replace(/\{removeClass}/g,f).replace(/\{removeIcon}/g,u.removeIcon).replace(/\{removeTitle}/g,u.removeTitle).replace(/\{dataUrl}/g,l).replace(/\{dataKey}/g,o),e&&(d=n._getLayoutTemplate("actionUpload").replace(/\{uploadClass}/g,u.uploadClass).replace(/\{uploadIcon}/g,u.uploadIcon).replace(/\{uploadTitle}/g,u.uploadTitle)),c.replace(/\{delete}/g,s).replace(/\{upload}/g,d).replace(/\{other}/g,p)},_browse:function(e){var i=this;i._raise("filebrowse"),e&&e.isDefaultPrevented()||(i.isError&&!i.isUploadable&&i.clear(),i.$captionContainer.focus())},_change:function(i){var t=this,a=t.$element;if(!t.isUploadable&&K(a.val())&&t.fileInputCleared)return void(t.fileInputCleared=!1);t.fileInputCleared=!1;var r,n,l,s,d,c,p=arguments.length>1,u=t.isUploadable,f=0,v=p?i.originalEvent.dataTransfer.files:a.get(0).files,g=t.filestack.length,m=K(a.attr("multiple")),h=m&&g>0,w=0,_=function(i,a,r,n){var l=e.extend(!0,{},t._getOutData({},{},v),{id:r,index:n}),o={id:r,index:n,file:a,files:v};return t.isUploadable?t._showUploadError(i,l):t._showError(i,o)};if(t.reader=null,t._resetUpload(),t._hideFileIcon(),t.isUploadable&&t.$container.find(".file-drop-zone ."+t.dropZoneTitleClass).remove(),p)for(r=[];v[f];)s=v[f],s.type||s.size%4096!==0?r.push(s):w++,f++;else r=void 0===i.target.files?i.target&&i.target.value?[{name:i.target.value.replace(/^.+\\/,"")}]:[]:i.target.files;if(K(r)||0===r.length)return u||t.clear(),t._showFolderError(w),void t._raise("fileselectnone");if(t._resetErrors(),c=r.length,l=t._getFileCount(t.isUploadable?t.getFileStack().length+c:c),t.maxFileCount>0&&l>t.maxFileCount){if(!t.autoReplace||c>t.maxFileCount)return d=t.autoReplace&&c>t.maxFileCount?c:l,n=t.msgFilesTooMany.replace("{m}",t.maxFileCount).replace("{n}",d),t.isError=_(n,null,null,null),t.$captionContainer.find(".kv-caption-icon").hide(),t._setCaption("",!0),void t.$container.removeClass("file-input-new file-input-ajax-new");l>t.maxFileCount&&t._resetPreviewThumbs(u)}else!u||h?(t._resetPreviewThumbs(!1),h&&t.clearStack()):!u||0!==g||o.count(t.id)&&!t.overwriteInitial||t._resetPreviewThumbs(!0);t.isPreviewable?t._readFiles(r):t._updateFileDetails(1),t._showFolderError(w)},_abort:function(i){var t,a=this;return a.ajaxAborted&&"object"==typeof a.ajaxAborted&&void 0!==a.ajaxAborted.message?(t=e.extend(!0,{},a._getOutData(),i),t.abortData=a.ajaxAborted.data||{},t.abortMessage=a.ajaxAborted.message,a.cancel(),a._setProgress(100,a.$progress,a.msgCancelled),a._showUploadError(a.ajaxAborted.message,t,"filecustomerror"),!0):!1},_resetFileStack:function(){var i=this,t=0,a=[],r=[];i._getThumbs().each(function(){var n=e(this),l=n.attr("data-fileindex"),o=i.filestack[l];-1!==l&&(void 0!==o?(a[t]=o,r[t]=i._getFileName(o),n.attr({id:i.previewInitId+"-"+t,"data-fileindex":t}),t++):n.attr({id:"uploaded-"+Y(),"data-fileindex":"-1"}))}),i.filestack=a,i.filenames=r},clearStack:function(){var e=this;return e.filestack=[],e.filenames=[],e.$element},updateStack:function(e,i){var t=this;return t.filestack[e]=i,t.filenames[e]=t._getFileName(i),t.$element},addToStack:function(e){var i=this;return i.filestack.push(e),i.filenames.push(i._getFileName(e)),i.$element},getFileStack:function(e){var i=this;return i.filestack.filter(function(i){return e?void 0!==i:void 0!==i&&null!==i})},lock:function(){var e=this;return e._resetErrors(),e.disable(),e.showRemove&&u(e.$container.find(".fileinput-remove"),"hide"),e.showCancel&&e.$container.find(".fileinput-cancel").removeClass("hide"),e._raise("filelock",[e.filestack,e._getExtraData()]),e.$element},unlock:function(e){var i=this;return void 0===e&&(e=!0),i.enable(),i.showCancel&&u(i.$container.find(".fileinput-cancel"),"hide"),i.showRemove&&i.$container.find(".fileinput-remove").removeClass("hide"),e&&i._resetFileStack(),i._raise("fileunlock",[i.filestack,i._getExtraData()]),i.$element},cancel:function(){var i,t=this,a=t.ajaxRequests,r=a.length;if(r>0)for(i=0;r>i;i+=1)t.cancelling=!0,a[i].abort();return t._setProgressCancelled(),t._getThumbs().each(function(){var i=e(this),a=i.attr("data-fileindex");i.removeClass("file-uploading"),void 0!==t.filestack[a]&&(i.find(".kv-file-upload").removeClass("disabled").removeAttr("disabled"),i.find(".kv-file-remove").removeClass("disabled").removeAttr("disabled")),t.unlock()}),t.$element},clear:function(){var i,t=this;return t.$btnUpload.removeAttr("disabled"),t._getThumbs().find("video,audio,img").each(function(){ie(e(this))}),t._resetUpload(),t.clearStack(),t._clearFileInput(),t._resetErrors(!0),t._raise("fileclear"),t._hasInitialPreview()?(t._showFileIcon(),t._resetPreview(),t._initPreviewDeletes(),t.$container.removeClass("file-input-new")):(t._getThumbs().each(function(){t._clearObjects(e(this))}),t.isUploadable&&(o.data[t.id]={}),t.$preview.html(""),i=!t.overwriteInitial&&t.initialCaption.length>0?t.initialCaption:"",t._setCaption(i),t.$caption.attr("title",""),u(t.$container,"file-input-new"),t._validateDefaultPreview()),0===t.$container.find(".file-preview-frame").length&&(t._initCaption()||t.$captionContainer.find(".kv-caption-icon").hide()),t._hideFileIcon(),t._raise("filecleared"),t.$captionContainer.focus(),t._setFileDropZoneTitle(),t.$element},reset:function(){var e=this;return e._resetPreview(),e.$container.find(".fileinput-filename").text(""),e._raise("filereset"),u(e.$container,"file-input-new"),(e.$preview.find(".file-preview-frame").length||e.isUploadable&&e.dropZoneEnabled)&&e.$container.removeClass("file-input-new"),e._setFileDropZoneTitle(),e.clearStack(),e.formdata={},e.$element},disable:function(){var e=this;return e.isDisabled=!0,e._raise("filedisabled"),e.$element.attr("disabled","disabled"),e.$container.find(".kv-fileinput-caption").addClass("file-caption-disabled"),e.$container.find(".btn-file, .fileinput-remove, .fileinput-upload, .file-preview-frame button").attr("disabled",!0),e._initDragDrop(),e.$element},enable:function(){var e=this;return e.isDisabled=!1,e._raise("fileenabled"),e.$element.removeAttr("disabled"),e.$container.find(".kv-fileinput-caption").removeClass("file-caption-disabled"),e.$container.find(".btn-file, .fileinput-remove, .fileinput-upload, .file-preview-frame button").removeAttr("disabled"),e._initDragDrop(),e.$element},upload:function(){var i,t,a,r=this,n=r.getFileStack().length,l={},o=!e.isEmptyObject(r._getExtraData());if(r.minFileCount>0&&r._getFileCount(n)i;i++)r.uploadCache.content[i]=null,r.uploadCache.config[i]=null,r.uploadCache.tags[i]=null;for(i=0;a>i;i++)void 0!==r.filestack[i]&&r._uploadSingle(i,r.filestack,!0)}},destroy:function(){var e=this,t=e.$container;return t.find(".file-drop-zone").off(),e.$element.insertBefore(t).off(i).removeData(),t.off().remove(),e.$element},refresh:function(i){var t=this,a=t.$element;return i=i?e.extend(!0,{},t.options,i):t.options,t.destroy(),a.fileinput(i),a.val()&&a.trigger("change.fileinput"),a}},e.fn.fileinput=function(i){if(d()||r(9)){var t=Array.apply(null,arguments),a=[];switch(t.shift(),this.each(function(){var r,n=e(this),l=n.data("fileinput"),o="object"==typeof i&&i,s=o.language||n.data("language")||"en",d={};l||("en"===s||K(e.fn.fileinputLocales[s])||(d=e.fn.fileinputLocales[s]),r=e.extend(!0,{},e.fn.fileinput.defaults,e.fn.fileinputLocales.en,d,o,n.data()),l=new q(this,r),n.data("fileinput",l)),"string"==typeof i&&a.push(l[i].apply(l,t))}),a.length){case 0:return this;case 1:return a[0];default:return a}}},e.fn.fileinput.defaults={language:"en",showCaption:!0,showPreview:!0,showRemove:!0,showUpload:!0,showCancel:!0,showClose:!0,showUploadedThumbs:!0,autoReplace:!1,mainClass:"",previewClass:"",captionClass:"",mainTemplate:null,initialCaption:"",initialPreview:[],initialPreviewDelimiter:"*$$*",initialPreviewConfig:[],initialPreviewThumbTags:[],previewThumbTags:{},initialPreviewShowDelete:!0,removeFromPreviewOnError:!1,deleteUrl:"",deleteExtraData:{},overwriteInitial:!0,layoutTemplates:M,previewTemplates:Z,allowedPreviewTypes:null,allowedPreviewMimeTypes:null,allowedFileTypes:null,allowedFileExtensions:null,defaultPreviewContent:null,customLayoutTags:{},customPreviewTags:{},previewSettings:W,fileTypeSettings:V,previewFileIcon:'',previewFileIconClass:"file-icon-4x",previewFileIconSettings:{},previewFileExtSettings:{},buttonLabelClass:"hidden-xs",browseIcon:' ',browseClass:"btn btn-primary",removeIcon:'',removeClass:"btn btn-default",cancelIcon:'',cancelClass:"btn btn-default",uploadIcon:'',uploadClass:"btn btn-default",uploadUrl:null,uploadAsync:!0,uploadExtraData:{},minImageWidth:null,minImageHeight:null,maxImageWidth:null,maxImageHeight:null,resizeImage:!1,resizePreference:"width",resizeQuality:.92,resizeDefaultImageType:"image/jpeg",maxFileSize:0,minFileCount:0,maxFileCount:0,validateInitialCount:!1,msgValidationErrorClass:"text-danger",msgValidationErrorIcon:' ',msgErrorClass:"file-error-message",progressThumbClass:"progress-bar progress-bar-success progress-bar-striped active",progressClass:"progress-bar progress-bar-success progress-bar-striped active",progressCompleteClass:"progress-bar progress-bar-success",progressErrorClass:"progress-bar progress-bar-danger",previewFileType:"image",zoomIndicator:'',elCaptionContainer:null,elCaptionText:null,elPreviewContainer:null,elPreviewImage:null,elPreviewStatus:null,elErrorContainer:null,errorCloseButton:'×',slugCallback:null,dropZoneEnabled:!0,dropZoneTitleClass:"file-drop-zone-title",fileActionSettings:{},otherActionButtons:"",textEncoding:"UTF-8",ajaxSettings:{},ajaxDeleteSettings:{},showAjaxErrorDetails:!0},e.fn.fileinputLocales.en={fileSingle:"file",filePlural:"files",browseLabel:"Browse …",removeLabel:"Remove",removeTitle:"Clear selected files",cancelLabel:"Cancel",cancelTitle:"Abort ongoing upload",uploadLabel:"Upload",uploadTitle:"Upload selected files",msgNo:"No",msgCancelled:"Cancelled",msgZoomTitle:"View details",msgZoomModalHeading:"Detailed Preview",msgSizeTooLarge:'File "{name}" ({size} KB) exceeds maximum allowed upload size of {maxSize} KB.',msgFilesTooLess:"You must select at least {n} {files} to upload.",msgFilesTooMany:"Number of files selected for upload ({n}) exceeds maximum allowed limit of {m}.",msgFileNotFound:'File "{name}" not found!',msgFileSecured:'Security restrictions prevent reading the file "{name}".',msgFileNotReadable:'File "{name}" is not readable.',msgFilePreviewAborted:'File preview aborted for "{name}".',msgFilePreviewError:'An error occurred while reading the file "{name}".',msgInvalidFileType:'Invalid type for file "{name}". Only "{types}" files are supported.',msgInvalidFileExtension:'Invalid extension for file "{name}". Only "{extensions}" files are supported.',msgUploadAborted:"The file upload was aborted",msgValidationError:"Validation Error",msgLoading:"Loading file {index} of {files} …",msgProgress:"Loading file {index} of {files} - {name} - {percent}% completed.",msgSelected:"{n} {files} selected",msgFoldersNotAllowed:"Drag & drop files only! {n} folder(s) dropped were skipped.",msgImageWidthSmall:'Width of image file "{name}" must be at least {size} px.',msgImageHeightSmall:'Height of image file "{name}" must be at least {size} px.',msgImageWidthLarge:'Width of image file "{name}" cannot exceed {size} px.',msgImageHeightLarge:'Height of image file "{name}" cannot exceed {size} px.',msgImageResizeError:"Could not get the image dimensions to resize.",msgImageResizeException:"Error while resizing the image.
    {errors}
    ",dropZoneTitle:"Drag & drop files here …"},e.fn.fileinput.Constructor=q,e(document).ready(function(){var i=e("input.file[type=file]");i.length&&i.fileinput()})}); \ No newline at end of file +/*! + * bootstrap-fileinput v4.3.7 + * http://plugins.krajee.com/file-input + * + * Author: Kartik Visweswaran + * Copyright: 2014 - 2017, Kartik Visweswaran, Krajee.com + * + * Licensed under the BSD 3-Clause + * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md + */!function(e){"use strict";"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof module&&module.exports?module.exports=e(require("jquery")):e(window.jQuery)}(function(e){"use strict";e.fn.fileinputLocales={},e.fn.fileinputThemes={};var i,t;i={NAMESPACE:".fileinput",MODAL_ID:"kvFileinputModal",FRAMES:".kv-preview-thumb",SORT_CSS:"file-sortable",STYLE_SETTING:'style="width:{width};height:{height};"',OBJECT_PARAMS:'\n\n\n\n\n\n',DEFAULT_PREVIEW:'
    \n{previewFileIcon}\n
    ',objUrl:window.URL||window.webkitURL,compare:function(e,i,t){return void 0!==e&&(t?e===i:e.match(i))},handler:function(e,t,a,r){var n=r?t:t.split(" ").join(i.NAMESPACE+" ")+i.NAMESPACE;e.length&&e.off(n).on(n,a)},isIE:function(e){if("Microsoft Internet Explorer"!==navigator.appName)return!1;if(10===e)return new RegExp("msie\\s"+e,"i").test(navigator.userAgent);var i,t=document.createElement("div");return t.innerHTML="",i=t.getElementsByTagName("i").length,document.body.appendChild(t),t.parentNode.removeChild(t),i},isEmpty:function(i,t){return void 0===i||null===i||0===i.length||t&&""===e.trim(i)},isArray:function(e){return Array.isArray(e)||"[object Array]"===Object.prototype.toString.call(e)},ifSet:function(e,i,t){return t=t||"",i&&"object"==typeof i&&e in i?i[e]:t},getNum:function(e,i){return i=i||0,"number"==typeof e?e:("string"==typeof e&&(e=parseFloat(e)),isNaN(e)?i:e)},hasFileAPISupport:function(){return!(!window.File||!window.FileReader)},hasDragDropSupport:function(){var e=document.createElement("div");return!i.isIE(9)&&(void 0!==e.draggable||void 0!==e.ondragstart&&void 0!==e.ondrop)},hasFileUploadSupport:function(){return i.hasFileAPISupport()&&window.FormData},addCss:function(e,i){e.removeClass(i).addClass(i)},getElement:function(t,a,r){return i.isEmpty(t)||i.isEmpty(t[a])?r:e(t[a])},uniqId:function(){return Math.round((new Date).getTime()+100*Math.random())},htmlEncode:function(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")},replaceTags:function(i,t){var a=i;return t?(e.each(t,function(e,i){"function"==typeof i&&(i=i()),a=a.split(e).join(i)}),a):a},cleanMemory:function(e){var t=e.is("img")?e.attr("src"):e.find("source").attr("src");i.objUrl.revokeObjectURL(t)},findFileName:function(e){var i=e.lastIndexOf("/");return-1===i&&(i=e.lastIndexOf("\\")),e.split(e.substring(i,i+1)).pop()},checkFullScreen:function(){return document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement},toggleFullScreen:function(e){var t=document,a=t.documentElement;a&&e&&!i.checkFullScreen()?a.requestFullscreen?a.requestFullscreen():a.msRequestFullscreen?a.msRequestFullscreen():a.mozRequestFullScreen?a.mozRequestFullScreen():a.webkitRequestFullscreen&&a.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT):t.exitFullscreen?t.exitFullscreen():t.msExitFullscreen?t.msExitFullscreen():t.mozCancelFullScreen?t.mozCancelFullScreen():t.webkitExitFullscreen&&t.webkitExitFullscreen()},moveArray:function(e,i,t){if(t>=e.length)for(var a=t-e.length;a--+1;)e.push(void 0);return e.splice(t,0,e.splice(i,1)[0]),e},cleanZoomCache:function(e){var i=e.closest(".kv-zoom-cache-theme");i.length||(i=e.closest(".kv-zoom-cache")),i.remove()}},t=function(t,a){var r=this;r.$element=e(t),r._validate()&&(r.isPreviewable=i.hasFileAPISupport(),r.isIE9=i.isIE(9),r.isIE10=i.isIE(10),r.isPreviewable||r.isIE9?(r._init(a),r._listen()):r.$element.removeClass("file-loading"))},t.prototype={constructor:t,_init:function(t){var a,r=this,n=r.$element;r.options=t,e.each(t,function(e,t){switch(e){case"minFileCount":case"maxFileCount":case"maxFileSize":r[e]=i.getNum(t);break;default:r[e]=t}}),r._initTemplateDefaults(),r.fileInputCleared=!1,r.fileBatchCompleted=!0,r.isPreviewable||(r.showPreview=!1),r.uploadFileAttr=i.isEmpty(n.attr("name"))?"file_data":n.attr("name"),r.reader=null,r.formdata={},r.clearStack(),r.uploadCount=0,r.uploadStatus={},r.uploadLog=[],r.uploadAsyncCount=0,r.loadedImages=[],r.totalImagesCount=0,r.ajaxRequests=[],r.isError=!1,r.ajaxAborted=!1,r.cancelling=!1,a=r._getLayoutTemplate("progress"),r.progressTemplate=a.replace("{class}",r.progressClass),r.progressCompleteTemplate=a.replace("{class}",r.progressCompleteClass),r.progressErrorTemplate=a.replace("{class}",r.progressErrorClass),r.dropZoneEnabled=i.hasDragDropSupport()&&r.dropZoneEnabled,r.isDisabled=r.$element.attr("disabled")||r.$element.attr("readonly"),r.isUploadable=i.hasFileUploadSupport()&&!i.isEmpty(r.uploadUrl),r.isClickable=r.browseOnZoneClick&&r.showPreview&&(r.isUploadable&&r.dropZoneEnabled||!i.isEmpty(r.defaultPreviewContent)),r.slug="function"==typeof t.slugCallback?t.slugCallback:r._slugDefault,r.mainTemplate=r.showCaption?r._getLayoutTemplate("main1"):r._getLayoutTemplate("main2"),r.captionTemplate=r._getLayoutTemplate("caption"),r.previewGenericTemplate=r._getPreviewTemplate("generic"),r.resizeImage&&(r.maxImageWidth||r.maxImageHeight)&&(r.imageCanvas=document.createElement("canvas"),r.imageCanvasContext=r.imageCanvas.getContext("2d")),i.isEmpty(r.$element.attr("id"))&&r.$element.attr("id",i.uniqId()),void 0===r.$container?r.$container=r._createContainer():r._refreshContainer(),r.$dropZone=r.$container.find(".file-drop-zone"),r.$progress=r.$container.find(".kv-upload-progress"),r.$btnUpload=r.$container.find(".fileinput-upload"),r.$captionContainer=i.getElement(t,"elCaptionContainer",r.$container.find(".file-caption")),r.$caption=i.getElement(t,"elCaptionText",r.$container.find(".file-caption-name")),r.$previewContainer=i.getElement(t,"elPreviewContainer",r.$container.find(".file-preview")),r.$preview=i.getElement(t,"elPreviewImage",r.$container.find(".file-preview-thumbnails")),r.$previewStatus=i.getElement(t,"elPreviewStatus",r.$container.find(".file-preview-status")),r.$errorContainer=i.getElement(t,"elErrorContainer",r.$previewContainer.find(".kv-fileinput-error")),i.isEmpty(r.msgErrorClass)||i.addCss(r.$errorContainer,r.msgErrorClass),r.$errorContainer.hide(),r.previewInitId="preview-"+i.uniqId(),r._initPreviewCache(),r._initPreview(!0),r._initPreviewActions(),r._setFileDropZoneTitle(),r.$element.removeClass("file-loading"),r.$element.attr("disabled")&&r.disable(),r._initZoom()},_initTemplateDefaults:function(){var t,a,r,n,o,l,s,d,c,p,u,f,m,g,v,h,w,_,b,C,E,y,x,T,S,F,P,I,k,$,A,z,D,U,j=this;t='{preview}\n
    \n
    \n {caption}\n
    \n {remove}\n {cancel}\n {upload}\n {browse}\n
    \n
    ',a='{preview}\n
    \n{remove}\n{cancel}\n{upload}\n{browse}\n',r='
    \n {close}
    \n
    \n
    \n
    \n
    \n
    \n
    ',o='
    ×
    \n',n='',l='
    \n
    \n
    \n',s='',d='{icon} {label}',c='
    {icon} {label}
    ',p='',u='\n',f='
    \n
    \n {percent}%\n
    \n
    ',m=" ({sizeText})",g='',v='
    {indicator}
    \n{drag}\n
    \n \n
    \n
    ',h='\n',w='',_='',b='{dragIcon}',C='
    \n',y=C+' title="{caption}" '+i.STYLE_SETTING+'>
    \n',x="
    {footer}\n
    \n",T="{content}\n",S='
    {data}
    \n",F='{caption}\n",P='\n",I='\n",k='\n",$='\n'+i.OBJECT_PARAMS+" "+i.DEFAULT_PREVIEW+"\n\n",A='\n\n'+i.OBJECT_PARAMS+" "+i.DEFAULT_PREVIEW+"\n\n",z='\n',D='
    \n'+i.DEFAULT_PREVIEW+"\n
    \n",U='',j.defaults={layoutTemplates:{main1:t,main2:a,preview:r,close:o,fileIcon:n,caption:l,modalMain:p,modal:u,progress:f,size:m,footer:g,actions:v,actionDelete:h,actionUpload:w,actionZoom:_,actionDrag:b,btnDefault:s,btnLink:d,btnBrowse:c,zoomCache:U},previewMarkupTags:{tagBefore1:E,tagBefore2:y,tagAfter:x},previewContentTemplates:{generic:T,html:S,image:F,text:P,video:I,audio:k,flash:$,object:A,pdf:z,other:D},allowedPreviewTypes:["image","html","text","video","audio","flash","pdf","object"],previewTemplates:{},previewSettings:{image:{width:"auto",height:"160px"},html:{width:"213px",height:"160px"},text:{width:"213px",height:"160px"},video:{width:"213px",height:"160px"},audio:{width:"213px",height:"80px"},flash:{width:"213px",height:"160px"},object:{width:"160px",height:"160px"},pdf:{width:"160px",height:"160px"},other:{width:"160px",height:"160px"}},previewZoomSettings:{image:{width:"auto",height:"auto","max-width":"100%","max-height":"100%"},html:{width:"100%",height:"100%","min-height":"480px"},text:{width:"100%",height:"100%","min-height":"480px"},video:{width:"auto",height:"100%","max-width":"100%"},audio:{width:"100%",height:"30px"},flash:{width:"auto",height:"480px"},object:{width:"auto",height:"100%","min-height":"480px"},pdf:{width:"100%",height:"100%","min-height":"480px"},other:{width:"auto",height:"100%","min-height":"480px"}},fileTypeSettings:{image:function(e,t){return i.compare(e,"image.*")||i.compare(t,/\.(gif|png|jpe?g)$/i)},html:function(e,t){return i.compare(e,"text/html")||i.compare(t,/\.(htm|html)$/i)},text:function(e,t){return i.compare(e,"text.*")||i.compare(t,/\.(xml|javascript)$/i)||i.compare(t,/\.(txt|md|csv|nfo|ini|json|php|js|css)$/i)},video:function(e,t){return i.compare(e,"video.*")&&(i.compare(e,/(ogg|mp4|mp?g|mov|webm|3gp)$/i)||i.compare(t,/\.(og?|mp4|webm|mp?g|mov|3gp)$/i))},audio:function(e,t){return i.compare(e,"audio.*")&&(i.compare(t,/(ogg|mp3|mp?g|wav)$/i)||i.compare(t,/\.(og?|mp3|mp?g|wav)$/i))},flash:function(e,t){return i.compare(e,"application/x-shockwave-flash",!0)||i.compare(t,/\.(swf)$/i)},pdf:function(e,t){return i.compare(e,"application/pdf",!0)||i.compare(t,/\.(pdf)$/i)},object:function(){return!0},other:function(){return!0}},fileActionSettings:{showRemove:!0,showUpload:!0,showZoom:!0,showDrag:!0,removeIcon:'',removeClass:"btn btn-xs btn-default",removeTitle:"Remove file",uploadIcon:'',uploadClass:"btn btn-xs btn-default",uploadTitle:"Upload file",zoomIcon:'',zoomClass:"btn btn-xs btn-default",zoomTitle:"View Details",dragIcon:'',dragClass:"text-info",dragTitle:"Move / Rearrange",dragSettings:{},indicatorNew:'',indicatorSuccess:'',indicatorError:'',indicatorLoading:'',indicatorNewTitle:"Not uploaded yet",indicatorSuccessTitle:"Uploaded",indicatorErrorTitle:"Upload Error",indicatorLoadingTitle:"Uploading ..."}},e.each(j.defaults,function(i,t){return"allowedPreviewTypes"===i?void(void 0===j.allowedPreviewTypes&&(j.allowedPreviewTypes=t)):void(j[i]=e.extend(!0,t,j[i]))}),j._initPreviewTemplates()},_initPreviewTemplates:function(){var t,a=this,r=a.defaults,n=a.previewMarkupTags,o=n.tagAfter;e.each(r.previewContentTemplates,function(e,r){i.isEmpty(a.previewTemplates[e])&&(t=n.tagBefore2,"generic"!==e&&"image"!==e&&"html"!==e&&"text"!==e||(t=n.tagBefore1),a.previewTemplates[e]=t+r+o)})},_initPreviewCache:function(){var t=this;t.previewCache={data:{},init:function(){var e=t.initialPreview;e.length>0&&!i.isArray(e)&&(e=e.split(t.initialPreviewDelimiter)),t.previewCache.data={content:e,config:t.initialPreviewConfig,tags:t.initialPreviewThumbTags}},fetch:function(){return t.previewCache.data.content.filter(function(e){return null!==e})},count:function(e){return t.previewCache.data&&t.previewCache.data.content?e?t.previewCache.data.content.length:t.previewCache.fetch().length:0},get:function(a,r){var n,o,l,s,d,c,p,u="init_"+a,f=t.previewCache.data,m=f.config[a],g=f.content[a],v=t.previewInitId+"-"+u,h=i.ifSet("previewAsData",m,t.initialPreviewAsData),w=function(e,a,r,n,o,l,s,d,c){return d=" file-preview-initial "+i.SORT_CSS+(d?" "+d:""),t._generatePreviewTemplate(e,a,r,n,o,!1,null,d,l,s,c)};return g?(r=void 0===r?!0:r,l=i.ifSet("type",m,t.initialPreviewFileType||"generic"),d=i.ifSet("filename",m,i.ifSet("caption",m)),c=i.ifSet("filetype",m,l),s=t.previewCache.footer(a,r,m&&m.size||null),p=i.ifSet("frameClass",m),n=h?w(l,g,d,c,v,s,u,p):w("generic",g,d,c,v,s,u,p,l).replace(/\{content}/g,f.content[a]),f.tags.length&&f.tags[a]&&(n=i.replaceTags(n,f.tags[a])),i.isEmpty(m)||i.isEmpty(m.frameAttr)||(o=e(document.createElement("div")).html(n),o.find(".file-preview-initial").attr(m.frameAttr),n=o.html(),o.remove()),n):""},add:function(e,a,r,n){var o,l=t.previewCache.data;return i.isArray(e)||(e=e.split(t.initialPreviewDelimiter)),n?(o=l.content.push(e)-1,l.config[o]=a,l.tags[o]=r):(o=e.length-1,l.content=e,l.config=a,l.tags=r),t.previewCache.data=l,o},set:function(e,a,r,n){var o,l,s=t.previewCache.data;if(e&&e.length&&(i.isArray(e)||(e=e.split(t.initialPreviewDelimiter)),l=e.filter(function(e){return null!==e}),l.length)){if(void 0===s.content&&(s.content=[]),void 0===s.config&&(s.config=[]),void 0===s.tags&&(s.tags=[]),n){for(o=0;oi;i++)a+=t.previewCache.get(i);return e=t._getMsgSelected(t.previewCache.count()),{content:a,caption:e}},footer:function(e,a,r){var n=t.previewCache.data;if(!n||!n.config||0===n.config.length||i.isEmpty(n.config[e]))return"";a=void 0===a?!0:a;var o=n.config[e],l=i.ifSet("caption",o),s="",d=i.ifSet("width",o,"auto"),c=i.ifSet("url",o,!1),p=i.ifSet("key",o,null),u=t.fileActionSettings,f=i.ifSet("showDelete",o,!0),m=i.ifSet("showZoom",o,u.showZoom),g=i.ifSet("showDrag",o,u.showDrag),v=c===!1&&a;return t.initialPreviewShowDelete&&(s=t._renderFileActions(!1,f,m,g,v,c,p,!0)),t._getLayoutTemplate("footer").replace(/\{progress}/g,t._renderThumbProgress()).replace(/\{actions}/g,s).replace(/\{caption}/g,l).replace(/\{size}/g,t._getSize(r)).replace(/\{width}/g,d).replace(/\{indicator}/g,"").replace(/\{indicatorTitle}/g,"")}},t.previewCache.init()},_log:function(e){var i=this,t=i.$element.attr("id");t&&(e='"'+t+'": '+e),"undefined"!=typeof window.console.log?window.console.log(e):window.alert(e)},_validate:function(){var e=this,i="file"===e.$element.attr("type");return i||e._log('The input "type" must be set to "file" for initializing the "bootstrap-fileinput" plugin.'),i},_errorsExist:function(){var i,t=this;return t.$errorContainer.find("li").length?!0:(i=e(document.createElement("div")).html(t.$errorContainer.html()),i.find("span.kv-error-close").remove(),i.find("ul").remove(),!!e.trim(i.text()).length)},_errorHandler:function(e,i){var t=this,a=e.target.error;a.code===a.NOT_FOUND_ERR?t._showError(t.msgFileNotFound.replace("{name}",i)):a.code===a.SECURITY_ERR?t._showError(t.msgFileSecured.replace("{name}",i)):a.code===a.NOT_READABLE_ERR?t._showError(t.msgFileNotReadable.replace("{name}",i)):a.code===a.ABORT_ERR?t._showError(t.msgFilePreviewAborted.replace("{name}",i)):t._showError(t.msgFilePreviewError.replace("{name}",i))},_addError:function(e){var t=this,a=t.$errorContainer;e&&a.length&&(a.html(t.errorCloseButton+e),i.handler(a.find(".kv-error-close"),"click",function(){a.fadeOut("slow")}))},_resetErrors:function(e){var i=this,t=i.$errorContainer;i.isError=!1,i.$container.removeClass("has-error"),t.html(""),e?t.fadeOut("slow"):t.hide()},_showFolderError:function(e){var t,a=this,r=a.$errorContainer;e&&(t=a.msgFoldersNotAllowed.replace(/\{n}/g,e),a._addError(t),i.addCss(a.$container,"has-error"),r.fadeIn(800),a._raise("filefoldererror",[e,t]))},_showUploadError:function(e,t,a){var r=this,n=r.$errorContainer,o=a||"fileuploaderror",l=t&&t.id?'
  • '+e+"
  • ":"
  • "+e+"
  • ";return 0===n.find("ul").length?r._addError("
      "+l+"
    "):n.find("ul").append(l),n.fadeIn(800),r._raise(o,[t,e]),r.$container.removeClass("file-input-new"),i.addCss(r.$container,"has-error"),!0},_showError:function(e,t,a){var r=this,n=r.$errorContainer,o=a||"fileerror";return t=t||{},t.reader=r.reader,r._addError(e),n.fadeIn(800),r._raise(o,[t,e]),r.isUploadable||r._clearFileInput(),r.$container.removeClass("file-input-new"),i.addCss(r.$container,"has-error"),r.$btnUpload.attr("disabled",!0),!0},_noFilesError:function(e){var t=this,a=t.minFileCount>1?t.filePlural:t.fileSingle,r=t.msgFilesTooLess.replace("{n}",t.minFileCount).replace("{files}",a),n=t.$errorContainer;t._addError(r),t.isError=!0,t._updateFileDetails(0),n.fadeIn(800),t._raise("fileerror",[e,r]),t._clearFileInput(),i.addCss(t.$container,"has-error")},_parseError:function(i,t,a,r){var n=this,o=e.trim(a+""),l="."===o.slice(-1)?"":".",s=void 0!==t.responseJSON&&void 0!==t.responseJSON.error?t.responseJSON.error:t.responseText;return n.cancelling&&n.msgUploadAborted&&(o=n.msgUploadAborted),n.showAjaxErrorDetails&&s?(s=e.trim(s.replace(/\n\s*\n/g,"\n")),s=s.length>0?"
    "+s+"
    ":"",o+=l+s):o+=l,o===l&&(o=n.msgAjaxError.replace("{operation}",i)),n.cancelling=!1,r?""+r+": "+o:o},_parseFileType:function(e){var t,a,r,n,o=this,l=o.allowedPreviewTypes;for(n=0;n-1&&(t=i.split(".").pop(),a.previewFileIconSettings&&a.previewFileIconSettings[t]&&(r=a.previewFileIconSettings[t]),a.previewFileExtSettings&&e.each(a.previewFileExtSettings,function(e,i){return a.previewFileIconSettings[e]&&i(t)?void(r=a.previewFileIconSettings[e]):void 0})),r},_parseFilePreviewIcon:function(e,i){var t=this,a=t._getPreviewIcon(i)||t.previewFileIcon;return e.indexOf("{previewFileIcon}")>-1&&(e=e.replace(/\{previewFileIconClass}/g,t.previewFileIconClass).replace(/\{previewFileIcon}/g,a)),e},_raise:function(i,t){var a=this,r=e.Event(i);if(void 0!==t?a.$element.trigger(r,t):a.$element.trigger(r),r.isDefaultPrevented())return!1;if(!r.result)return r.result;switch(i){case"filebatchuploadcomplete":case"filebatchuploadsuccess":case"fileuploaded":case"fileclear":case"filecleared":case"filereset":case"fileerror":case"filefoldererror":case"fileuploaderror":case"filebatchuploaderror":case"filedeleteerror":case"filecustomerror":case"filesuccessremove":break;default:a.ajaxAborted=r.result}return!0},_listenFullScreen:function(e){var i,t,a=this,r=a.$modal;r&&r.length&&(i=r&&r.find(".btn-fullscreen"),t=r&&r.find(".btn-borderless"),i.length&&t.length&&(i.removeClass("active").attr("aria-pressed","false"),t.removeClass("active").attr("aria-pressed","false"),e?i.addClass("active").attr("aria-pressed","true"):t.addClass("active").attr("aria-pressed","true"),r.hasClass("file-zoom-fullscreen")?a._maximizeZoomDialog():e?a._maximizeZoomDialog():t.removeClass("active").attr("aria-pressed","false")))},_listen:function(){var t=this,a=t.$element,r=a.closest("form"),n=t.$container;i.handler(a,"change",e.proxy(t._change,t)),t.showBrowse&&i.handler(t.$btnFile,"click",e.proxy(t._browse,t)),i.handler(r,"reset",e.proxy(t.reset,t)),i.handler(n.find(".fileinput-remove:not([disabled])"),"click",e.proxy(t.clear,t)),i.handler(n.find(".fileinput-cancel"),"click",e.proxy(t.cancel,t)),t._initDragDrop(),t.isUploadable||i.handler(r,"submit",e.proxy(t._submitForm,t)),i.handler(t.$container.find(".fileinput-upload"),"click",e.proxy(t._uploadClick,t)),i.handler(e(window),"resize",function(){t._listenFullScreen(screen.width===window.innerWidth&&screen.height===window.innerHeight)}),i.handler(e(document),"webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange",function(){t._listenFullScreen(i.checkFullScreen())}),t._initClickable()},_initClickable:function(){var t,a=this;a.isClickable&&(t=a.isUploadable?a.$dropZone:a.$preview.find(".file-default-preview"),i.addCss(t,"clickable"),t.attr("tabindex",-1),i.handler(t,"click",function(i){var r=e(i.target);r.parents(".file-preview-thumbnails").length&&!r.parents(".file-default-preview").length||(a.$element.trigger("click"),t.blur())}))},_initDragDrop:function(){var t=this,a=t.$dropZone;t.isUploadable&&t.dropZoneEnabled&&t.showPreview&&(i.handler(a,"dragenter dragover",e.proxy(t._zoneDragEnter,t)),i.handler(a,"dragleave",e.proxy(t._zoneDragLeave,t)),i.handler(a,"drop",e.proxy(t._zoneDrop,t)),i.handler(e(document),"dragenter dragover drop",t._zoneDragDropInit))},_zoneDragDropInit:function(e){e.stopPropagation(),e.preventDefault()},_zoneDragEnter:function(t){var a=this,r=e.inArray("Files",t.originalEvent.dataTransfer.types)>-1;return a._zoneDragDropInit(t),a.isDisabled||!r?(t.originalEvent.dataTransfer.effectAllowed="none",void(t.originalEvent.dataTransfer.dropEffect="none")):void i.addCss(a.$dropZone,"file-highlighted")},_zoneDragLeave:function(e){var i=this;i._zoneDragDropInit(e),i.isDisabled||i.$dropZone.removeClass("file-highlighted")},_zoneDrop:function(e){var t=this;e.preventDefault(),t.isDisabled||i.isEmpty(e.originalEvent.dataTransfer.files)||(t._change(e,"dragdrop"),t.$dropZone.removeClass("file-highlighted"))},_uploadClick:function(e){var t,a=this,r=a.$container.find(".fileinput-upload"),n=!r.hasClass("disabled")&&i.isEmpty(r.attr("disabled"));if(!e||!e.isDefaultPrevented()){if(!a.isUploadable)return void(n&&"submit"!==r.attr("type")&&(t=r.closest("form"),t.length&&t.trigger("submit"),e.preventDefault()));e.preventDefault(),n&&a.upload()}},_submitForm:function(){var e=this,i=e.$element,t=i.get(0).files;return t&&e.minFileCount>0&&e._getFileCount(t.length)
    - -EOT; - } - - /** - * Get file visit url. + * Initialize the caption. * - * @param $path + * @param string $caption * * @return string */ - public function objectUrl($path) + protected function initialCaption($caption) { - if (Str::startsWith($path, ['http://', 'https://'])) { - return $path; - } - - return rtrim(config('admin.upload.host'), '/').'/'.trim($path, '/'); + return basename($caption); } /** - * Initialize the caption. - * - * @param string $caption - * - * @return string + * @return array */ - protected function initialCaption($caption) + protected function initialPreviewConfig() { - if (empty($caption)) { - return ''; - } - - if ($this->multiple) { - if (is_string($caption)) { - $caption = json_decode($caption, true); - } - } else { - $caption = [$caption]; - } - - $caption = array_map('basename', $caption); - - return implode(',', $caption); + return [ + ['caption' => basename($this->value), 'key' => 0], + ]; } /** @@ -450,114 +133,22 @@ protected function initialCaption($caption) */ public function render() { - $this->options['initialCaption'] = $this->initialCaption($this->value); - $this->options['removeLabel'] = trans('admin::lang.remove'); - $this->options['browseLabel'] = trans('admin::lang.browse'); + $this->setupDefaultOptions(); if (!empty($this->value)) { - $this->options['initialPreview'] = $this->preview(); + $this->setupPreviewOptions(); } - $options = json_encode($this->options); + $this->options(['overwriteInitial' => true]); - $class = $this->getElementClass(); + $options = json_encode($this->options); $this->script = <<getElementClass()}").fileinput({$options}); EOT; - return parent::render()->with(['multiple' => $this->multiple, 'actionName' => $this->getActionName()]); - } - - /** - * Get action element name. - * - * @return array|mixed|string - */ - protected function getActionName() - { - return $this->formatName($this->column.'_action'); - } - - /** - * If is delete request then delete original image. - * - * @return bool - */ - public function handleDeleteRequest() - { - $action = Input::get($this->column.'_action'); - - if ($action == static::ACTION_REMOVE) { - $this->destroy(); - - return true; - } - - return false; - } - - /** - * Generate a unique name for uploaded file. - * - * @param UploadedFile $file - * - * @return string - */ - protected function generateUniqueName(UploadedFile $file) - { - return md5(uniqid()).'.'.$file->guessExtension(); - } - - /** - * If name already exists, rename it. - * - * @param $file - * - * @return void - */ - public function renameIfExists(UploadedFile $file) - { - if ($this->storage->exists("{$this->getDirectory()}/$this->name")) { - $this->name = $this->generateUniqueName($file); - } - } - - /** - * Destroy original files. - * - * @return void. - */ - public function destroy() - { - $files = $this->original; - - if (is_string($this->original)) { - $files = json_decode($this->original, true); - } - - if (!is_array($files)) { - $files = [$this->original]; - } - - array_map([$this, 'destroyItem'], $files); - } - - /** - * Destroy single original file. - * - * @param string $item - */ - protected function destroyItem($item) - { - if ($this->storage->exists($item)) { - $this->storage->delete($item); - } + return parent::render(); } } diff --git a/src/Form/Field/Image.php b/src/Form/Field/Image.php index 9d37f47348..cec2186ce9 100644 --- a/src/Form/Field/Image.php +++ b/src/Form/Field/Image.php @@ -2,120 +2,39 @@ namespace Encore\Admin\Form\Field; -use Intervention\Image\ImageManagerStatic; use Symfony\Component\HttpFoundation\File\UploadedFile; class Image extends File { - /** - * Validation rules. - * - * @var string - */ - protected $rules = 'image'; + use ImageField; /** - * Intervention calls. - * - * @var array + * {@inheritdoc} */ - protected $calls = []; + protected $view = 'admin::form.file'; /** - * Get default storage path. + * Validation rules. * - * @return mixed + * @var string */ - public function defaultStorePath() - { - return config('admin.upload.directory.image'); - } + protected $rules = 'image'; /** - * Prepare for single upload file. - * - * @param UploadedFile|null $image + * @param array|UploadedFile $image * * @return string */ - protected function prepareForSingle(UploadedFile $image = null) + public function prepare($image) { - $this->directory = $this->directory ?: $this->defaultStorePath(); - - $this->name = $this->getStoreName($image); - - $this->executeCalls($image->getRealPath()); - - $target = $this->uploadAndDeleteOriginal($image); - - return $target; - } - - /** - * Execute Intervention calls. - * - * @param string $target - * - * @return mixed - */ - public function executeCalls($target) - { - if (!empty($this->calls)) { - $image = ImageManagerStatic::make($target); - - foreach ($this->calls as $call) { - call_user_func_array([$image, $call['method']], $call['arguments'])->save($target); - } + if (request()->has(static::FILE_DELETE_FLAG)) { + return $this->destroy(); } - return $target; - } - - /** - * Build a preview item. - * - * @param string $image - * - * @return string - */ - protected function buildPreviewItem($image) - { - return ''; - } - - /** - * Render a image form field. - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function render() - { - $this->options(['allowedFileTypes' => ['image']]); - - return parent::render(); - } - - /** - * Call intervention methods. - * - * @param string $method - * @param array $arguments - * - * @throws \Exception - * - * @return $this - */ - public function __call($method, $arguments) - { - if (!class_exists(ImageManagerStatic::class)) { - throw new \Exception('To use image handling and manipulation, please install [intervention/image] first.'); - } + $this->name = $this->getStoreName($image); - $this->calls[] = [ - 'method' => $method, - 'arguments' => $arguments, - ]; + $this->callInterventionMethods($image->getRealPath()); - return $this; + return $this->uploadAndDeleteOriginal($image); } } diff --git a/src/Form/Field/ImageField.php b/src/Form/Field/ImageField.php new file mode 100644 index 0000000000..81f29c7d70 --- /dev/null +++ b/src/Form/Field/ImageField.php @@ -0,0 +1,84 @@ +interventionCalls)) { + $image = ImageManagerStatic::make($target); + + foreach ($this->interventionCalls as $call) { + call_user_func_array( + [$image, $call['method']], + $call['arguments'] + )->save($target); + } + } + + return $target; + } + + /** + * Call intervention methods. + * + * @param string $method + * @param array $arguments + * + * @throws \Exception + * + * @return $this + */ + public function __call($method, $arguments) + { + if (!class_exists(ImageManagerStatic::class)) { + throw new \Exception('To use image handling and manipulation, please install [intervention/image] first.'); + } + + $this->interventionCalls[] = [ + 'method' => $method, + 'arguments' => $arguments, + ]; + + return $this; + } + + /** + * Render a image form field. + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function render() + { + $this->options(['allowedFileTypes' => ['image']]); + + return parent::render(); + } +} diff --git a/src/Form/Field/MultipleFile.php b/src/Form/Field/MultipleFile.php new file mode 100644 index 0000000000..0da726e3f1 --- /dev/null +++ b/src/Form/Field/MultipleFile.php @@ -0,0 +1,219 @@ +has(static::FILE_DELETE_FLAG)) { + return false; + } + + $attributes = []; + + if (!$fieldRules = $this->getRules()) { + return false; + } + + $attributes[$this->column] = $this->label; + + list($rules, $input) = $this->hydrateFiles(array_get($input, $this->column, [])); + + return Validator::make($input, $rules, [], $attributes); + } + + /** + * Hydrate the files array. + * + * @param array $value + * + * @return array + */ + protected function hydrateFiles(array $value) + { + $rules = $input = []; + + foreach ($value as $key => $file) { + $rules[$this->column . $key] = $this->getRules(); + $input[$this->column . $key] = $file; + } + + return [$rules, $input]; + } + + /** + * Prepare for saving. + * + * @param UploadedFile|array $files + * + * @return mixed|string + */ + public function prepare($files) + { + if (request()->has(static::FILE_DELETE_FLAG)) { + return $this->destroy(request(static::FILE_DELETE_FLAG)); + } + + $targets = array_map([$this, 'prepareForeach'], $files); + + return array_merge($this->original(), $targets); + } + + /** + * @return array|mixed + */ + public function original() + { + if (empty($this->original)) { + return []; + } + + return $this->original; + } + + /** + * Prepare for each file. + * + * @param UploadedFile $file + * + * @return mixed|string + */ + protected function prepareForeach(UploadedFile $file = null) + { + $this->name = $this->getStoreName($file); + + return $this->upload($file); + } + + /** + * Preview html for file-upload plugin. + * + * @return array + */ + protected function preview() + { + $files = $this->value ?: []; + + return array_map([$this, 'objectUrl'], $files); + } + + /** + * Initialize the caption. + * + * @param array $caption + * + * @return string + */ + protected function initialCaption(array $caption) + { + if (empty($caption)) { + return ''; + } + + $caption = array_map('basename', $caption); + + return implode(',', $caption); + } + + /** + * @return array + */ + protected function initialPreviewConfig() + { + $files = $this->value ?: []; + + $config = []; + + foreach ($files as $index => $file) { + $config[] = [ + 'caption' => basename($file), + 'key' => $index, + ]; + } + + return $config; + } + + /** + * Render file upload field. + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function render() + { + $this->attribute('multiple', true); + + $this->setupDefaultOptions(); + + if (!empty($this->value)) { + $this->setupPreviewOptions(); + } + + $options = json_encode($this->options); + + $this->script = <<getElementClass()}").fileinput({$options}); +EOT; + + return parent::render(); + } + + /** + * Destroy original files. + * + * @return string. + */ + public function destroy($key) + { + $files = $this->original ?: []; + + $file = array_get($files, $key); + + if ($this->storage->exists($file)) { + $this->storage->delete($file); + } + + unset($files[$key]); + + return array_values($files); + } +} diff --git a/src/Form/Field/MultipleImage.php b/src/Form/Field/MultipleImage.php new file mode 100644 index 0000000000..7e22253cf6 --- /dev/null +++ b/src/Form/Field/MultipleImage.php @@ -0,0 +1,39 @@ +name = $this->getStoreName($image); + + $this->callInterventionMethods($image->getRealPath()); + + return $this->upload($image); + } +} diff --git a/src/Form/Field/UploadField.php b/src/Form/Field/UploadField.php new file mode 100644 index 0000000000..ddbf9519a8 --- /dev/null +++ b/src/Form/Field/UploadField.php @@ -0,0 +1,288 @@ +initStorage(); + + parent::__construct($column, $arguments); + } + + /** + * Initialize the storage instance. + * + * @return void. + */ + protected function initStorage() + { + $this->disk(config('admin.upload.disk')); + } + + public function setupDefaultOptions() + { + $this->options([ + 'overwriteInitial' => false, + 'initialPreviewAsData' => true, + 'browseLabel' => trans('admin::lang.browse'), + 'showRemove' => false, + 'showUpload' => false, + 'initialCaption' => $this->initialCaption($this->value), + 'deleteUrl' => $this->form->resource() . '/'. $this->form->model()->getKey(), + 'deleteExtraData' => [ + $this->column => '', + static::FILE_DELETE_FLAG => '', + '_token' => csrf_token(), + '_method' => 'PUT' + ] + ]); + } + + public function setupPreviewOptions() + { + $this->options([ + 'initialPreview' => $this->preview(), + 'initialPreviewConfig' => $this->initialPreviewConfig(), + ]); + } + + /** + * Set options for file-upload plugin. + * + * @param array $options + * + * @return $this + */ + public function options($options = []) + { + $this->options = array_merge($this->options, $options); + + return $this; + } + + /** + * Set disk for storage. + * + * @param string $disk Disks defined in `config/filesystems.php`. + * + * @return $this + */ + public function disk($disk) + { + if (!array_key_exists($disk, config('filesystems.disks'))) { + $error = new MessageBag([ + 'title' => 'Config error.', + 'message' => "Disk [$disk] not configured, please add a disk config in `config/filesystems.php`.", + ]); + + return session()->flash('error', $error); + } + + $this->storage = Storage::disk($disk); + + return $this; + } + + /** + * Specify the directory and name for uplaod file. + * + * @param string $directory + * @param null|string $name + * + * @return $this + */ + public function move($directory, $name = null) + { + $this->directory = $directory; + + $this->name = $name; + + return $this; + } + + /** + * Set name of store name. + * + * @param string|callable $name + * + * @return $this + */ + public function name($name) + { + $this->name = $name; + + return $this; + } + + /** + * Use unique name for store upload file. + * + * @return $this + */ + public function uniqueName() + { + $this->useUniqueName = true; + + return $this; + } + + /** + * Get store name of upload file. + * + * @param UploadedFile $file + * + * @return string + */ + protected function getStoreName(UploadedFile $file) + { + if ($this->useUniqueName) { + return $this->generateUniqueName($file); + } + + if (is_callable($this->name)) { + $callback = $this->name->bindTo($this); + + return call_user_func($callback, $file); + } + + if (is_string($this->name)) { + return $this->name; + } + + return $file->getClientOriginalName(); + } + + /** + * Get directory for store file. + * + * @return mixed|string + */ + public function getDirectory() + { + if ($this->directory instanceof \Closure) { + return call_user_func($this->directory, $this->form); + } + + return $this->directory ?: $this->defaultDirectory(); + } + + /** + * Upload file and delete original file. + * + * @param UploadedFile $file + * + * @return mixed + */ + protected function upload(UploadedFile $file) + { + $this->renameIfExists($file); + + $target = $this->getDirectory() . '/' . $this->name; + + $this->storage->put($target, file_get_contents($file->getRealPath())); + + return $target; + } + + /** + * If name already exists, rename it. + * + * @param $file + * + * @return void + */ + public function renameIfExists(UploadedFile $file) + { + if ($this->storage->exists("{$this->getDirectory()}/$this->name")) { + $this->name = $this->generateUniqueName($file); + } + } + + /** + * Get file visit url. + * + * @param $path + * + * @return string + */ + public function objectUrl($path) + { + if (URL::isValidUrl($path)) { + return $path; + } + + return rtrim(config('admin.upload.host'), '/') . '/' . trim($path, '/'); + } + + /** + * Generate a unique name for uploaded file. + * + * @param UploadedFile $file + * + * @return string + */ + protected function generateUniqueName(UploadedFile $file) + { + return md5(uniqid()).'.'.$file->guessExtension(); + } + + /** + * Destroy original files. + * + * @return void. + */ + public function destroy() + { + if ($this->storage->exists($this->original)) { + $this->storage->delete($this->original); + } + } +} diff --git a/views/form/file.blade.php b/views/form/file.blade.php index 612abd28bc..97b5ae29a5 100644 --- a/views/form/file.blade.php +++ b/views/form/file.blade.php @@ -5,8 +5,8 @@
    @include('admin::form.error') - - + + @include('admin::form.help-block') diff --git a/views/form/image.blade.php b/views/form/multiplefile.blade.php similarity index 61% rename from views/form/image.blade.php rename to views/form/multiplefile.blade.php index 4f7a5a9c71..d97b9e66c0 100644 --- a/views/form/image.blade.php +++ b/views/form/multiplefile.blade.php @@ -5,8 +5,8 @@
    @include('admin::form.error') - - + + @include('admin::form.help-block') From e4b8c7fba048363caca164d75bda3b2133f8b5e4 Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 22 Feb 2017 16:00:07 +0800 Subject: [PATCH 0529/2161] fix issue #446 closed #446 --- src/Grid.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Grid.php b/src/Grid.php index c922da2474..2b979cb50b 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -260,6 +260,8 @@ public function column($name, $label = '') $relation = $this->model()->eloquent()->$relationName(); $label = empty($label) ? ucfirst($relationColumn) : $label; + + $name = snake_case($relationName) . '.' . $relationColumn; } $column = $this->addColumn($name, $label); @@ -811,7 +813,7 @@ protected function handleRelationColumn($method, $label) if ($relation instanceof HasOne || $relation instanceof BelongsTo) { $this->model()->with($method); - return $this->addColumn($method, $label)->setRelation($method); + return $this->addColumn($method, $label)->setRelation(snake_case($method)); } if ($relation instanceof HasMany || $relation instanceof BelongsToMany || $relation instanceof MorphToMany) { From 6217de9f3985cc4c2a06610807dce37dd5038e73 Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 24 Feb 2017 00:36:55 +0800 Subject: [PATCH 0530/2161] update tests --- src/Form/Field/MultipleFile.php | 2 +- src/Form/Field/UploadField.php | 7 ------- tests/ImageUploadTest.php | 18 ++++++++++++------ tests/MenuTest.php | 4 ++-- tests/TestCase.php | 2 ++ tests/controllers/MultipleImageController.php | 2 +- tests/models/MultipleImage.php | 12 ++++++++++++ 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/Form/Field/MultipleFile.php b/src/Form/Field/MultipleFile.php index 0da726e3f1..96c466d099 100644 --- a/src/Form/Field/MultipleFile.php +++ b/src/Form/Field/MultipleFile.php @@ -143,7 +143,7 @@ protected function preview() * * @return string */ - protected function initialCaption(array $caption) + protected function initialCaption($caption) { if (empty($caption)) { return ''; diff --git a/src/Form/Field/UploadField.php b/src/Form/Field/UploadField.php index ddbf9519a8..996d7b9bcf 100644 --- a/src/Form/Field/UploadField.php +++ b/src/Form/Field/UploadField.php @@ -23,13 +23,6 @@ trait UploadField */ protected $name = null; - /** - * Options for file-upload plugin. - * - * @var array - */ - protected $options = []; - /** * Storage instance. * diff --git a/tests/ImageUploadTest.php b/tests/ImageUploadTest.php index a59e939d5a..a9700d3eb3 100644 --- a/tests/ImageUploadTest.php +++ b/tests/ImageUploadTest.php @@ -78,13 +78,21 @@ public function testRemoveImage() $this->assertEquals($this->fileCountInImageDir(), 6); + // remove image2 $this->call( 'PUT', // $method '/admin/images/1', // $action - ['image2_action' => 1, 'image5_action' => 1] // $parameters + ['image2' => '', '__del__' => '', 'key' => 0] // $parameters ); - $this->assertRedirectedTo('/admin/images'); + $this->assertEquals($this->fileCountInImageDir(), 5); + + // remove image5 + $this->call( + 'PUT', // $method + '/admin/images/1', // $action + ['image5' => '', '__del__' => '', 'key' => 0] // $parameters + ); $this->assertEquals($this->fileCountInImageDir(), 4); } @@ -212,8 +220,6 @@ public function testUploadMultipleImage() $pictures = MultipleImage::first()->pictures; - $pictures = json_decode($pictures, true); - $this->assertCount($size, $pictures); foreach ($pictures as $picture) { @@ -249,10 +255,10 @@ public function testRemoveMultipleFiles() $this->call( 'PUT', // $method '/admin/multiple-images/1', // $action - ['pictures_action' => 1] // $parameters + ['pictures' => '', '__del__' => '', 'key' => 0] // $parameters ); - $this->assertEquals($this->fileCountInImageDir(), 0); + $this->assertEquals($this->fileCountInImageDir(), $size - 1); } protected function fileCountInImageDir($dir = 'upload/image') diff --git a/tests/MenuTest.php b/tests/MenuTest.php index c80ab3f25c..386042a98e 100644 --- a/tests/MenuTest.php +++ b/tests/MenuTest.php @@ -34,7 +34,7 @@ public function testAddMenu() ->submitForm('Submit', $item) ->seePageIs('admin/auth/menu') ->seeInDatabase(config('admin.database.menu_table'), $item) - ->assertEquals(8, Menu::count()); + ->assertEquals(12, Menu::count()); $this->setExpectedException(Illuminate\Foundation\Testing\HttpException::class); @@ -56,7 +56,7 @@ public function testEditMenu() ->submitForm('Submit', ['title' => 'blablabla']) ->seePageIs('admin/auth/menu') ->seeInDatabase(config('admin.database.menu_table'), ['title' => 'blablabla']) - ->assertEquals(7, Menu::count()); + ->assertEquals(11, Menu::count()); } public function testShowPage() diff --git a/tests/TestCase.php b/tests/TestCase.php index e20f00c1f6..502baf8f14 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -54,6 +54,8 @@ public function setUp() //$this->artisan('admin:install'); + \Encore\Admin\Facades\Admin::registerAuthRoutes(); + if (file_exists($routes = admin_path('routes.php'))) { require $routes; //$this->app['admin.router']->register(); diff --git a/tests/controllers/MultipleImageController.php b/tests/controllers/MultipleImageController.php index 70e6fa8607..be7867f864 100644 --- a/tests/controllers/MultipleImageController.php +++ b/tests/controllers/MultipleImageController.php @@ -87,7 +87,7 @@ protected function form() return Admin::form(MultipleImage::class, function (Form $form) { $form->display('id', 'ID'); - $form->image('pictures')->multiple(); + $form->multipleImage('pictures'); $form->display('created_at', 'Created At'); $form->display('updated_at', 'Updated At'); diff --git a/tests/models/MultipleImage.php b/tests/models/MultipleImage.php index 1116be530b..a3b66d45ff 100644 --- a/tests/models/MultipleImage.php +++ b/tests/models/MultipleImage.php @@ -7,4 +7,16 @@ class MultipleImage extends Model { protected $table = 'test_multiple_images'; + + public function setPicturesAttribute($pictures) + { + if (is_array($pictures)) { + $this->attributes['pictures'] = json_encode($pictures); + } + } + + public function getPicturesAttribute($pictures) + { + return json_decode($pictures, true) ?: []; + } } From 467db6661ffda85203ed13874b1bbfbd7b62a0d4 Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 24 Feb 2017 16:37:33 +0800 Subject: [PATCH 0531/2161] fix scaffold form submit issue. --- src/Controllers/ScaffoldController.php | 5 ++++- views/helpers/scaffold.blade.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Controllers/ScaffoldController.php b/src/Controllers/ScaffoldController.php index 2fbcce2550..87765b1cc7 100644 --- a/src/Controllers/ScaffoldController.php +++ b/src/Controllers/ScaffoldController.php @@ -10,6 +10,7 @@ use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\URL; use Illuminate\Support\MessageBag; class ScaffoldController extends Controller @@ -27,7 +28,9 @@ public function index() 'timestampTz', 'nullableTimestamps', 'binary', 'ipAddress', 'macAddress', ]; - $content->row(view('admin::helpers.scaffold', compact('dbTypes'))); + $action = URL::current(); + + $content->row(view('admin::helpers.scaffold', compact('dbTypes', 'action'))); }); } diff --git a/views/helpers/scaffold.blade.php b/views/helpers/scaffold.blade.php index e4a967a192..c8eeec1243 100644 --- a/views/helpers/scaffold.blade.php +++ b/views/helpers/scaffold.blade.php @@ -5,7 +5,7 @@
    -
    +
    From 3a5a1a23be316e107c421fc7c1d8517281d20ecc Mon Sep 17 00:00:00 2001 From: Edwin Hui Date: Fri, 24 Feb 2017 11:02:27 +0800 Subject: [PATCH 0532/2161] fix #444 --- views/form/tab.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/form/tab.blade.php b/views/form/tab.blade.php index 3b5c40ad1c..752bb4022a 100644 --- a/views/form/tab.blade.php +++ b/views/form/tab.blade.php @@ -3,7 +3,7 @@ @foreach($tabObj->getTabs() as $tab)
  • - + {{ $tab['title'] }}
  • From 3027438e980e9b2a116bc7f18c83cf4fbd12c0f4 Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 24 Feb 2017 16:40:48 +0800 Subject: [PATCH 0533/2161] add captcha form field. --- lang/en/lang.php | 1 + lang/zh_CN/lang.php | 1 + src/Form.php | 8 ++++++- src/Form/Field/Captcha.php | 44 +++++++++++++++++++++++++++++++++++ src/Form/Field/PlainInput.php | 8 +++---- views/form/captcha.blade.php | 20 ++++++++++++++++ 6 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 src/Form/Field/Captcha.php create mode 100644 views/form/captcha.blade.php diff --git a/lang/en/lang.php b/lang/en/lang.php index 237dac1473..eee1e89798 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -33,6 +33,7 @@ 'close' => 'Close', 'show' => 'Show', 'entries' => 'entries', + 'captcha' => 'Captcha', 'action' => 'Action', 'title' => 'Title', diff --git a/lang/zh_CN/lang.php b/lang/zh_CN/lang.php index 0ae936dff5..542abe04a4 100644 --- a/lang/zh_CN/lang.php +++ b/lang/zh_CN/lang.php @@ -33,6 +33,7 @@ 'close' => '关闭', 'show' => '显示', 'entries' => '条', + 'captcha' => '验证码', 'action' => '操作', 'title' => '标题', diff --git a/src/Form.php b/src/Form.php index 6cbda04b4e..40b49b1303 100644 --- a/src/Form.php +++ b/src/Form.php @@ -62,6 +62,9 @@ * @method Field\Tags tags($column, $label = '') * @method Field\Icon icon($column, $label = '') * @method Field\Embeds embeds($column, $label = '') + * @method Field\MultipleImage multipleImage($column, $label = '') + * @method Field\MultipleFile multipleFile($column, $label = '') + * @method Field\Captcha captcha($column, $label = '') */ class Form { @@ -490,6 +493,7 @@ public function update($id) return back()->withInput()->withErrors($validationMessages); } + /* @var Model $this->model */ $this->model = $this->model->with($this->getRelations())->findOrFail($id); $this->setFieldOriginalValue(); @@ -502,6 +506,7 @@ public function update($id) $updates = $this->prepareUpdate($this->updates); foreach ($updates as $column => $value) { + /* @var Model $this->model */ $this->model->setAttribute($column, $value); } @@ -820,7 +825,7 @@ public function saved(Closure $callback) */ public function ignore($fields) { - $this->ignored = (array) $fields; + $this->ignored = array_merge($this->ignored, (array) $fields); return $this; } @@ -1163,6 +1168,7 @@ public static function registerBuiltinFields() 'icon' => \Encore\Admin\Form\Field\Icon::class, 'multipleFile' => \Encore\Admin\Form\Field\MultipleFile::class, 'multipleImage' => \Encore\Admin\Form\Field\MultipleImage::class, + 'captcha' => \Encore\Admin\Form\Field\Captcha::class, ]; foreach ($map as $abstract => $class) { diff --git a/src/Form/Field/Captcha.php b/src/Form/Field/Captcha.php new file mode 100644 index 0000000000..517541cfb0 --- /dev/null +++ b/src/Form/Field/Captcha.php @@ -0,0 +1,44 @@ +column = '__captcha__'; + $this->label = trans('admin::lang.captcha'); + } + + public function setForm(Form $form = null) + { + $this->form = $form; + + $this->form->ignore($this->column); + + return $this; + } + + public function render() + { + $this->script = <<column}-captcha').click(function () { + $(this).attr('src', $(this).attr('src')+'?'+Math.random()); +}); + +EOT; + + return parent::render(); + } +} diff --git a/src/Form/Field/PlainInput.php b/src/Form/Field/PlainInput.php index 13f999ead1..6f3f47c657 100644 --- a/src/Form/Field/PlainInput.php +++ b/src/Form/Field/PlainInput.php @@ -28,7 +28,9 @@ public function append($string) protected function initPlainInput() { - $this->view = 'admin::form.input'; + if (empty($this->view)) { + $this->view = 'admin::form.input'; + } } protected function defaultAttribute($attribute, $value) @@ -39,8 +41,4 @@ protected function defaultAttribute($attribute, $value) return $this; } - - public function hasError() - { - } } diff --git a/views/form/captcha.blade.php b/views/form/captcha.blade.php new file mode 100644 index 0000000000..6d7b254e29 --- /dev/null +++ b/views/form/captcha.blade.php @@ -0,0 +1,20 @@ +
    + + + +
    + + @include('admin::form.error') + +
    + + + + + +
    + + @include('admin::form.help-block') + +
    +
    \ No newline at end of file From 0fd3f1236c387d1f56e8442a0b00ca0bac3e7c72 Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 24 Feb 2017 17:04:50 +0800 Subject: [PATCH 0534/2161] optimize success and toastr --- src/Form.php | 14 ++++---------- views/content.blade.php | 1 + views/partials/success.blade.php | 10 ++++++---- views/partials/toastr.blade.php | 10 ++++++++++ 4 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 views/partials/toastr.blade.php diff --git a/src/Form.php b/src/Form.php index 40b49b1303..ea4786e1cb 100644 --- a/src/Form.php +++ b/src/Form.php @@ -346,14 +346,11 @@ public function store() */ protected function redirectAfterStore() { - $success = new MessageBag([ - 'title' => trans('admin::lang.succeeded'), - 'message' => trans('admin::lang.save_succeeded'), - ]); + $toastr = new MessageBag(['message' => trans('admin::lang.save_succeeded')]); $url = Input::get(Builder::PREVIOUS_URL_KEY) ?: $this->resource(0); - return redirect($url)->with(compact('success')); + return redirect($url)->with(compact('toastr')); } /** @@ -533,14 +530,11 @@ public function update($id) */ protected function redirectAfterUpdate() { - $success = new MessageBag([ - 'title' => trans('admin::lang.succeeded'), - 'message' => trans('admin::lang.update_succeeded'), - ]); + $toastr = new MessageBag(['message' => trans('admin::lang.update_succeeded')]); $url = Input::get(Builder::PREVIOUS_URL_KEY) ?: $this->resource(-1); - return redirect($url)->with(compact('success')); + return redirect($url)->with(compact('toastr')); } /** diff --git a/views/content.blade.php b/views/content.blade.php index 66e5828a7e..c9fac5d0d3 100644 --- a/views/content.blade.php +++ b/views/content.blade.php @@ -14,6 +14,7 @@ @include('admin::partials.error') @include('admin::partials.success') @include('admin::partials.exception') + @include('admin::partials.toastr') {!! $content !!} diff --git a/views/partials/success.blade.php b/views/partials/success.blade.php index 0b60a03a57..b173eac623 100644 --- a/views/partials/success.blade.php +++ b/views/partials/success.blade.php @@ -1,6 +1,8 @@ @if(Session::has('success')) - @php $success = Session::get('success');@endphp - + +
    + +

    {{ array_get($success->get('title'), 0) }}

    +

    {!! array_get($success->get('message'), 0) !!}

    +
    @endif \ No newline at end of file diff --git a/views/partials/toastr.blade.php b/views/partials/toastr.blade.php new file mode 100644 index 0000000000..92704c248e --- /dev/null +++ b/views/partials/toastr.blade.php @@ -0,0 +1,10 @@ +@if(Session::has('toastr')) + @php + $toastr = Session::get('toastr'); + $type = array_get($toastr->get('type'), 0, 'success'); + $message = array_get($toastr->get('message'), 0, ''); + @endphp + +@endif \ No newline at end of file From a085d62f3115fe9d4d8b0bc6d91a6ab238c61d21 Mon Sep 17 00:00:00 2001 From: z-song Date: Fri, 24 Feb 2017 18:43:16 +0800 Subject: [PATCH 0535/2161] optimize toastr --- lang/en/lang.php | 1 + lang/zh_CN/lang.php | 1 + src/Controllers/AuthController.php | 13 ++++++------- src/Form.php | 8 ++++---- src/helpers.php | 18 ++++++++++++++++++ views/partials/toastr.blade.php | 5 ++++- 6 files changed, 34 insertions(+), 12 deletions(-) diff --git a/lang/en/lang.php b/lang/en/lang.php index eee1e89798..9407f09141 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -51,6 +51,7 @@ 'update_succeeded' => 'Update succeeded !', 'save_succeeded' => 'Save succeeded !', 'refresh_succeeded'=> 'Refresh succeeded !', + 'login_successful' => 'Login successful', 'choose' => 'Select', 'choose_file' => 'Select file', diff --git a/lang/zh_CN/lang.php b/lang/zh_CN/lang.php index 542abe04a4..5f08403e2a 100644 --- a/lang/zh_CN/lang.php +++ b/lang/zh_CN/lang.php @@ -51,6 +51,7 @@ 'update_succeeded' => '更新成功 !', 'save_succeeded' => '保存成功 !', 'refresh_succeeded'=> '刷新成功 !', + 'login_successful' => '登陆成功 !', 'choose' => '选择', 'choose_file' => '选择文件', diff --git a/src/Controllers/AuthController.php b/src/Controllers/AuthController.php index ed954e8f18..901d8ddab9 100644 --- a/src/Controllers/AuthController.php +++ b/src/Controllers/AuthController.php @@ -12,7 +12,6 @@ use Illuminate\Support\Facades\Lang; use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Validator; -use Illuminate\Support\MessageBag; class AuthController extends Controller { @@ -48,7 +47,10 @@ public function postLogin(Request $request) } if (Auth::guard('admin')->attempt($credentials)) { - return Redirect::intended(config('admin.prefix')); + + admin_toastr(trans('admin::lang.login_successful')); + + return redirect()->intended(config('admin.prefix')); } return Redirect::back()->withInput()->withErrors(['username' => $this->getFailedLoginMessage()]); @@ -117,12 +119,9 @@ protected function settingForm() }); $form->saved(function () { - $success = new MessageBag([ - 'title' => trans('admin::lang.succeeded'), - 'message' => trans('admin::lang.update_succeeded'), - ]); + admin_toastr(trans('admin::lang.update_succeeded')); - return redirect(admin_url('https://github.com/auth/setting'))->with(compact('success')); + return redirect(admin_url('https://github.com/auth/setting')); }); }); } diff --git a/src/Form.php b/src/Form.php index ea4786e1cb..42abeba790 100644 --- a/src/Form.php +++ b/src/Form.php @@ -346,11 +346,11 @@ public function store() */ protected function redirectAfterStore() { - $toastr = new MessageBag(['message' => trans('admin::lang.save_succeeded')]); + admin_toastr(trans('admin::lang.save_succeeded')); $url = Input::get(Builder::PREVIOUS_URL_KEY) ?: $this->resource(0); - return redirect($url)->with(compact('toastr')); + return redirect($url); } /** @@ -530,11 +530,11 @@ public function update($id) */ protected function redirectAfterUpdate() { - $toastr = new MessageBag(['message' => trans('admin::lang.update_succeeded')]); + admin_toastr(trans('admin::lang.update_succeeded')); $url = Input::get(Builder::PREVIOUS_URL_KEY) ?: $this->resource(-1); - return redirect($url)->with(compact('toastr')); + return redirect($url); } /** diff --git a/src/helpers.php b/src/helpers.php index 8a7556b115..4f4fc4b808 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -30,3 +30,21 @@ function admin_url(https://github.com/$url%20=%20'') return url(https://github.com/$prefix%20?%20%22/$prefix%22%20:%20%27').'/'.trim($url, '/'); } } + +if (!function_exists('admin_toastr')) { + + /** + * Flash a toastr messaage bag to session. + * + * @param string $message + * @param string $type + * @param array $options + * @return string + */ + function admin_toastr($message = '', $type = 'success', $options = []) + { + $toastr = new \Illuminate\Support\MessageBag(get_defined_vars()); + + \Illuminate\Support\Facades\Session::flash('toastr', $toastr); + } +} diff --git a/views/partials/toastr.blade.php b/views/partials/toastr.blade.php index 92704c248e..ca028e2898 100644 --- a/views/partials/toastr.blade.php +++ b/views/partials/toastr.blade.php @@ -3,8 +3,11 @@ $toastr = Session::get('toastr'); $type = array_get($toastr->get('type'), 0, 'success'); $message = array_get($toastr->get('message'), 0, ''); + $options = json_encode($toastr->get('options', [])); @endphp @endif \ No newline at end of file From 9091e08f2f96d998de99b450860cba85f7750128 Mon Sep 17 00:00:00 2001 From: Edwin Hui Date: Sat, 25 Feb 2017 00:22:37 +0800 Subject: [PATCH 0536/2161] change fields element class to array --- src/Form/Field.php | 73 +++++++++++++++++++++++++++++--- src/Form/Field/Button.php | 2 +- src/Form/Field/Checkbox.php | 2 +- src/Form/Field/Color.php | 2 +- src/Form/Field/Currency.php | 2 +- src/Form/Field/Date.php | 2 +- src/Form/Field/DateRange.php | 14 +++--- src/Form/Field/Decimal.php | 2 +- src/Form/Field/File.php | 2 +- src/Form/Field/Icon.php | 2 +- src/Form/Field/Ip.php | 2 +- src/Form/Field/Mobile.php | 2 +- src/Form/Field/MultipleFile.php | 2 +- src/Form/Field/Number.php | 2 +- src/Form/Field/Radio.php | 2 +- src/Form/Field/Select.php | 8 ++-- src/Form/Field/Slider.php | 2 +- src/Form/Field/SwitchField.php | 4 +- src/Form/Field/Tags.php | 2 +- src/Form/Field/Text.php | 2 +- src/Form/NestedForm.php | 4 +- views/form/switchfield.blade.php | 2 +- 22 files changed, 100 insertions(+), 37 deletions(-) diff --git a/src/Form/Field.php b/src/Form/Field.php index db2cc29b9d..47c4561013 100644 --- a/src/Form/Field.php +++ b/src/Form/Field.php @@ -64,7 +64,7 @@ class Field implements Renderable * * @var string */ - protected $elementName = ''; + protected $elementName = []; /** * Form element name. @@ -647,7 +647,7 @@ protected function formatAttributes() */ public function setElementClass($class) { - $this->elementClass = $class; + $this->elementClass = (array)$class; return $this; } @@ -655,19 +655,82 @@ public function setElementClass($class) /** * Get element class. * - * @return string + * @return array */ protected function getElementClass() { if (!$this->elementClass) { $name = $this->elementName ?: $this->formatName($this->column); - $this->elementClass = str_replace(['[', ']'], '_', $name); + $this->elementClass = (array)str_replace(['[', ']'], '_', $name); } return $this->elementClass; } + /** + * Get element class string + * + * @return mixed + */ + protected function getElementClassString() + { + return implode(' ', $this->getElementClass()); + } + + /** + * Get element class selector + * + * @return string + */ + protected function getElementClassSelector() + { + return '.' . implode('.', $this->getElementClass()); + } + + /** + * Add the element class + * + * @param $class + * @return $this + */ + public function addElementClass($class) + { + if(is_array($class) || is_string($class)){ + + $this->elementClass = array_merge($this->elementClass, (array)$class); + + $this->elementClass = array_unique($this->elementClass); + } + + return $this; + } + + + /** + * Remove element class + * + * @param $class + * @return $this + */ + public function removeElementClass($class) + { + $delClass = []; + + if(is_string($class) || is_array($class)){ + $delClass = (array)$class; + } + + foreach($delClass as $del){ + if(($key = array_search($del, $this->elementClass))){ + unset($this->elementClass[$key]); + } + } + + return $this; + + } + /** * Get the view variables of this field. * @@ -679,7 +742,7 @@ protected function variables() 'id' => $this->id, 'name' => $this->elementName ?: $this->formatName($this->column), 'help' => $this->help, - 'class' => $this->getElementClass(), + 'class' => $this->getElementClassString(), 'value' => $this->value(), 'label' => $this->label, 'width' => $this->width, diff --git a/src/Form/Field/Button.php b/src/Form/Field/Button.php index d58f1ce6f6..e55c11cb1c 100644 --- a/src/Form/Field/Button.php +++ b/src/Form/Field/Button.php @@ -19,7 +19,7 @@ public function on($event, $callback) { $this->script = <<getElementClass()}').on('$event', function() { + $('{$this->getElementClassSelector()}').on('$event', function() { $callback }); diff --git a/src/Form/Field/Checkbox.php b/src/Form/Field/Checkbox.php index 2e8a8e70a3..9eb2065952 100644 --- a/src/Form/Field/Checkbox.php +++ b/src/Form/Field/Checkbox.php @@ -37,7 +37,7 @@ public function options($options = []) */ public function render() { - $this->script = "$('.{$this->getElementClass()}').iCheck({checkboxClass:'icheckbox_minimal-blue'});"; + $this->script = "$('{$this->getElementClassSelector()}').iCheck({checkboxClass:'icheckbox_minimal-blue'});"; return parent::render(); } diff --git a/src/Form/Field/Color.php b/src/Form/Field/Color.php index 6441ea5706..b2fc36f27d 100644 --- a/src/Form/Field/Color.php +++ b/src/Form/Field/Color.php @@ -51,7 +51,7 @@ public function render() { $options = json_encode($this->options); - $this->script = "$('.{$this->getElementClass()}').parent().colorpicker($options);"; + $this->script = "$('{$this->getElementClassSelector()}').parent().colorpicker($options);"; $this->prepend('') ->defaultAttribute('style', 'width: 140px'); diff --git a/src/Form/Field/Currency.php b/src/Form/Field/Currency.php index 3226db83bd..139f2cb85c 100644 --- a/src/Form/Field/Currency.php +++ b/src/Form/Field/Currency.php @@ -40,7 +40,7 @@ public function render() $this->script = <<getElementClass()}').inputmask($options); +$('{$this->getElementClassSelector()}').inputmask($options); EOT; diff --git a/src/Form/Field/Date.php b/src/Form/Field/Date.php index e4e8ce5316..37f2f36eb0 100644 --- a/src/Form/Field/Date.php +++ b/src/Form/Field/Date.php @@ -36,7 +36,7 @@ public function render() $this->options['format'] = $this->format; $this->options['locale'] = config('app.locale'); - $this->script = "$('.{$this->getElementClass()}').datetimepicker(".json_encode($this->options).');'; + $this->script = "$('{$this->getElementClassSelector()}').datetimepicker(".json_encode($this->options).');'; $this->prepend('') ->defaultAttribute('style', 'width: 110px'); diff --git a/src/Form/Field/DateRange.php b/src/Form/Field/DateRange.php index 677f57d878..b4d312d0d0 100644 --- a/src/Form/Field/DateRange.php +++ b/src/Form/Field/DateRange.php @@ -52,16 +52,16 @@ public function render() $startOptions = json_encode($this->options); $endOptions = json_encode($this->options + ['useCurrent' => false]); - $class = $this->getElementClass(); + $class = $this->getElementClassSelector(); $this->script = <<options); - $this->script = "$('.{$this->getElementClass()}').inputmask($options);"; + $this->script = "$('{$this->getElementClassSelector()}').inputmask($options);"; $this->prepend('') ->defaultAttribute('style', 'width: 130px'); diff --git a/src/Form/Field/File.php b/src/Form/Field/File.php index e141d77156..12a3189560 100644 --- a/src/Form/Field/File.php +++ b/src/Form/Field/File.php @@ -145,7 +145,7 @@ public function render() $this->script = <<getElementClass()}").fileinput({$options}); +$("input{$this->getElementClassSelector()}").fileinput({$options}); EOT; diff --git a/src/Form/Field/Icon.php b/src/Form/Field/Icon.php index fff396856e..1663bdb6ef 100644 --- a/src/Form/Field/Icon.php +++ b/src/Form/Field/Icon.php @@ -18,7 +18,7 @@ public function render() { $this->script = <<getElementClass()}').iconpicker({placement:'bottomLeft'}); +$('{$this->getElementClassSelector()}').iconpicker({placement:'bottomLeft'}); EOT; diff --git a/src/Form/Field/Ip.php b/src/Form/Field/Ip.php index 500acbd3c9..7cf6de25ed 100644 --- a/src/Form/Field/Ip.php +++ b/src/Form/Field/Ip.php @@ -25,7 +25,7 @@ public function render() $this->script = <<getElementClass()}').inputmask($options); +$('{$this->getElementClassSelector()}').inputmask($options); EOT; $this->prepend('') diff --git a/src/Form/Field/Mobile.php b/src/Form/Field/Mobile.php index a352834a4b..5f69cd41f1 100644 --- a/src/Form/Field/Mobile.php +++ b/src/Form/Field/Mobile.php @@ -23,7 +23,7 @@ public function render() $this->script = <<getElementClass()}').inputmask($options); +$('{$this->getElementClassSelector()}').inputmask($options); EOT; $this->prepend('') diff --git a/src/Form/Field/MultipleFile.php b/src/Form/Field/MultipleFile.php index 96c466d099..7bfc030dc2 100644 --- a/src/Form/Field/MultipleFile.php +++ b/src/Form/Field/MultipleFile.php @@ -191,7 +191,7 @@ public function render() $options = json_encode($this->options); $this->script = <<getElementClass()}").fileinput({$options}); +$("input{$this->getElementClassSelector()}").fileinput({$options}); EOT; return parent::render(); diff --git a/src/Form/Field/Number.php b/src/Form/Field/Number.php index a15b2970f4..934aaa9b44 100644 --- a/src/Form/Field/Number.php +++ b/src/Form/Field/Number.php @@ -14,7 +14,7 @@ public function render() $this->script = <<getElementClass()}:not(.initialized)') +$('{$this->getElementClassSelector()}:not(.initialized)') .addClass('initialized') .bootstrapNumber({ upClass: 'success', diff --git a/src/Form/Field/Radio.php b/src/Form/Field/Radio.php index a038006cb6..d0bcd4dbd8 100644 --- a/src/Form/Field/Radio.php +++ b/src/Form/Field/Radio.php @@ -50,7 +50,7 @@ public function values($values) */ public function render() { - $this->script = "$('.{$this->getElementClass()}').iCheck({radioClass:'iradio_minimal-blue'});"; + $this->script = "$('{$this->getElementClassSelector()}').iCheck({radioClass:'iradio_minimal-blue'});"; return parent::render()->with(['options' => $this->options]); } diff --git a/src/Form/Field/Select.php b/src/Form/Field/Select.php index f3669252aa..16010be9c9 100644 --- a/src/Form/Field/Select.php +++ b/src/Form/Field/Select.php @@ -20,7 +20,7 @@ class Select extends Field public function render() { if (empty($this->script)) { - $this->script = "$(\".{$this->getElementClass()}\").select2({allowClear: true});"; + $this->script = "$(\"{$this->getElementClassSelector()}\").select2({allowClear: true});"; } if ($this->options instanceof \Closure) { @@ -84,7 +84,7 @@ public function load($field, $sourceUrl, $idField = 'id', $textField = 'text') $script = <<getElementClass()}", function () { +$(document).on('change', "{$this->getElementClassSelector()}", function () { var target = $(this).closest('.fields-group').find(".$class"); $.get("$sourceUrl?q="+this.value, function (data) { target.find("option").remove(); @@ -124,7 +124,7 @@ protected function loadOptionsFromRemote($url, $parameters = [], $options = []) $this->script = <<getElementClass()}").select2({data: data}); + $("{$this->getElementClassSelector()}").select2({data: data}); }); EOT; @@ -145,7 +145,7 @@ public function ajax($url, $idField = 'id', $textField = 'text') { $this->script = <<getElementClass()}").select2({ +$("{$this->getElementClassSelector()}").select2({ ajax: { url: "$url", dataType: 'json', diff --git a/src/Form/Field/Slider.php b/src/Form/Field/Slider.php index 13000a0be4..fbb617e3cc 100644 --- a/src/Form/Field/Slider.php +++ b/src/Form/Field/Slider.php @@ -25,7 +25,7 @@ public function render() { $option = json_encode($this->options); - $this->script = "$('.{$this->getElementClass()}').ionRangeSlider($option)"; + $this->script = "$('{$this->getElementClassSelector()}').ionRangeSlider($option)"; return parent::render(); } diff --git a/src/Form/Field/SwitchField.php b/src/Form/Field/SwitchField.php index 310aec4a76..e0504f142f 100644 --- a/src/Form/Field/SwitchField.php +++ b/src/Form/Field/SwitchField.php @@ -48,14 +48,14 @@ public function render() $this->script = <<getElementClass()}_checkbox').bootstrapSwitch({ +$('{$this->getElementClassSelector()}.la_checkbox').bootstrapSwitch({ size:'small', onText: '{$this->states['on']['text']}', offText: '{$this->states['off']['text']}', onColor: '{$this->states['on']['color']}', offColor: '{$this->states['off']['color']}', onSwitchChange: function(event, state) { - $('.{$this->getElementClass()}').val(state ? 'on' : 'off'); + $('{$this->getElementClassSelector()}').val(state ? 'on' : 'off'); } }); diff --git a/src/Form/Field/Tags.php b/src/Form/Field/Tags.php index 58e899222b..815d4ab8b7 100644 --- a/src/Form/Field/Tags.php +++ b/src/Form/Field/Tags.php @@ -34,7 +34,7 @@ public function prepare($value) public function render() { - $this->script = "$(\".{$this->getElementClass()}\").select2({ + $this->script = "$(\"{$this->getElementClassSelector()}\").select2({ tags: true, tokenSeparators: [','] });"; diff --git a/src/Form/Field/Text.php b/src/Form/Field/Text.php index 6258e2fe4e..14ba169c5f 100644 --- a/src/Form/Field/Text.php +++ b/src/Form/Field/Text.php @@ -22,7 +22,7 @@ public function render() ->defaultAttribute('id', $this->id) ->defaultAttribute('name', $this->elementName ?: $this->formatName($this->column)) ->defaultAttribute('value', old($this->column, $this->value())) - ->defaultAttribute('class', 'form-control '.$this->getElementClass()) + ->defaultAttribute('class', 'form-control '.$this->getElementClassString()) ->defaultAttribute('placeholder', $this->getPlaceholder()); return parent::render()->with([ diff --git a/src/Form/NestedForm.php b/src/Form/NestedForm.php index 2be164fe2f..2add4cf63b 100644 --- a/src/Form/NestedForm.php +++ b/src/Form/NestedForm.php @@ -335,12 +335,12 @@ protected function formatField(Field $field) foreach ($column as $k => $name) { $errorKey[$k] = sprintf('%s.%s.%s', $this->relationName, $key, $name); $elementName[$k] = sprintf('%s[%s][%s]', $this->relationName, $key, $name); - $elementClass[$k] = sprintf('%s_%s', $this->relationName, $name); + $elementClass[$k] = [$this->relationName, $name]; } } else { $errorKey = sprintf('%s.%s.%s', $this->relationName, $key, $column); $elementName = sprintf('%s[%s][%s]', $this->relationName, $key, $column); - $elementClass = sprintf('%s_%s', $this->relationName, $column); + $elementClass = [$this->relationName, $column]; } return $field->setErrorKey($errorKey) diff --git a/views/form/switchfield.blade.php b/views/form/switchfield.blade.php index 458b9698d4..7b92bb7a72 100644 --- a/views/form/switchfield.blade.php +++ b/views/form/switchfield.blade.php @@ -6,7 +6,7 @@ @include('admin::form.error') - + @include('admin::form.help-block') From 1a25cca312d11e5873488a43af76095dd4794cc6 Mon Sep 17 00:00:00 2001 From: z-song Date: Tue, 28 Feb 2017 13:30:02 +0800 Subject: [PATCH 0537/2161] fix form fields class issue --- src/Form/Field.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Form/Field.php b/src/Form/Field.php index 47c4561013..c56a5deb8c 100644 --- a/src/Form/Field.php +++ b/src/Form/Field.php @@ -6,6 +6,7 @@ use Encore\Admin\Form; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Renderable; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\Validator; /** @@ -675,7 +676,13 @@ protected function getElementClass() */ protected function getElementClassString() { - return implode(' ', $this->getElementClass()); + $elementClass = $this->getElementClass(); + + if (Arr::isAssoc($elementClass)) { + return $elementClass; + } + + return implode(' ', $elementClass); } /** @@ -685,7 +692,20 @@ protected function getElementClassString() */ protected function getElementClassSelector() { - return '.' . implode('.', $this->getElementClass()); + $elementClass = $this->getElementClass(); + + if (Arr::isAssoc($elementClass)) { + + $classes = []; + + foreach ($elementClass as $index => $class) { + $classes[$index] = '.' . $class; + } + + return $classes; + } + + return '.' . implode('.', $elementClass); } /** From ab0c2ae20dbadd3fc1ae1ddf8dc0832216ca3a42 Mon Sep 17 00:00:00 2001 From: z-song Date: Tue, 28 Feb 2017 13:46:14 +0800 Subject: [PATCH 0538/2161] fix issue #466 --- src/Form/Field/Map.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Form/Field/Map.php b/src/Form/Field/Map.php index 2d6fc2f795..a72fbb8ee5 100644 --- a/src/Form/Field/Map.php +++ b/src/Form/Field/Map.php @@ -6,6 +6,13 @@ class Map extends Field { + /** + * Column name. + * + * @var string + */ + protected $column = []; + /** * Get assets required by this field. * From 766b830d7ad73cb3fa45cf7562f4a757327696ab Mon Sep 17 00:00:00 2001 From: Edwin Hui Date: Tue, 28 Feb 2017 18:25:16 +0800 Subject: [PATCH 0539/2161] fix multipleselect width --- views/form/multipleselect.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/form/multipleselect.blade.php b/views/form/multipleselect.blade.php index c3dc53ea00..f80d8979e8 100644 --- a/views/form/multipleselect.blade.php +++ b/views/form/multipleselect.blade.php @@ -6,7 +6,7 @@ @include('admin::form.error') - @foreach($options as $select => $option) @endforeach From 811307a6e8613a6622fbb13d2dd7a1574c7f23c5 Mon Sep 17 00:00:00 2001 From: Edwin Hui Date: Tue, 28 Feb 2017 18:41:32 +0800 Subject: [PATCH 0540/2161] add class attribute to hidden field, is useful for custom js --- views/form/hidden.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/form/hidden.blade.php b/views/form/hidden.blade.php index 3a54653fe2..97641d2f92 100644 --- a/views/form/hidden.blade.php +++ b/views/form/hidden.blade.php @@ -1 +1 @@ - + From aec3ffd1bcfe3c6871bfa59461008d9f48a1c8aa Mon Sep 17 00:00:00 2001 From: Edwin Hui Date: Sun, 26 Feb 2017 00:42:21 +0800 Subject: [PATCH 0541/2161] fix Editable class --- src/Grid/Displayers/Editable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grid/Displayers/Editable.php b/src/Grid/Displayers/Editable.php index 39f52ffc59..c47a2c5432 100644 --- a/src/Grid/Displayers/Editable.php +++ b/src/Grid/Displayers/Editable.php @@ -138,7 +138,7 @@ public function display() { $this->options['name'] = $column = $this->column->getName(); - $class = "grid-editable-$column"; + $class = 'grid-editable-' . str_replace(['.','#','[',']'], '-',$column); $this->buildEditableOptions(func_get_args()); From 708546ef1688c5f9e9599ff410f71ea94f048a49 Mon Sep 17 00:00:00 2001 From: Edwin Hui Date: Sun, 26 Feb 2017 01:20:10 +0800 Subject: [PATCH 0542/2161] fix select html display value --- src/Grid/Displayers/Editable.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Grid/Displayers/Editable.php b/src/Grid/Displayers/Editable.php index c47a2c5432..0b4045ae5e 100644 --- a/src/Grid/Displayers/Editable.php +++ b/src/Grid/Displayers/Editable.php @@ -159,6 +159,8 @@ public function display() return "$name='$attribute'"; })->implode(' '); - return "{$this->value}"; + $html = $this->type === 'select' ? '' :$this->value; + + return "{$html}"; } } From 4d8ab513aa7bf87bba71588ced8c061d3311335a Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 1 Mar 2017 10:54:00 +0800 Subject: [PATCH 0543/2161] update docs [skip ci] --- README.md | 4 ++-- docs/en/installation.md | 4 ++-- docs/zh/installation.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ca2f6e4714..5b0c7dddf9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Installation First, install laravel, and make sure that the database connection settings are correct. ``` -Laravel 5.1 +Laravel 5.1, not maintained composer require encore/laravel-admin "1.1.*" Laravel 5.2 @@ -37,7 +37,7 @@ Laravel 5.3 composer require encore/laravel-admin "1.3.*" Laravel 5.4 -composer require encore/laravel-admin "1.4.x-dev" +composer require encore/laravel-admin "1.4.*" ``` diff --git a/docs/en/installation.md b/docs/en/installation.md index 58ddebd511..f2d74c8564 100644 --- a/docs/en/installation.md +++ b/docs/en/installation.md @@ -3,7 +3,7 @@ First, install laravel, and make sure that the database connection settings are correct. ``` -Laravel 5.1 +Laravel 5.1, not maintained composer require encore/laravel-admin "1.1.*" Laravel 5.2 @@ -13,7 +13,7 @@ Laravel 5.3 composer require encore/laravel-admin "1.3.*" Laravel 5.4 -composer require encore/laravel-admin "1.4.x-dev" +composer require encore/laravel-admin "1.4.*" ``` diff --git a/docs/zh/installation.md b/docs/zh/installation.md index 43efe9f58a..52e92a2038 100644 --- a/docs/zh/installation.md +++ b/docs/zh/installation.md @@ -3,7 +3,7 @@ 首先确保安装好了`laravel`,并且数据库连接设置正确。 ``` -Laravel 5.1 +Laravel 5.1, 已经不维护了 composer require encore/laravel-admin "1.1.*" Laravel 5.2 @@ -13,7 +13,7 @@ Laravel 5.3 composer require encore/laravel-admin "1.3.*" Laravel 5.4 -composer require encore/laravel-admin "1.4.x-dev" +composer require encore/laravel-admin "1.4.*" ``` 在`config/app.php`加入`ServiceProvider`: From 41a748f0891d2b3d932a564622affc134964c9bb Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 1 Mar 2017 11:26:08 +0800 Subject: [PATCH 0544/2161] optimize File field --- src/Form/Field/UploadField.php | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Form/Field/UploadField.php b/src/Form/Field/UploadField.php index 996d7b9bcf..5d18442409 100644 --- a/src/Form/Field/UploadField.php +++ b/src/Form/Field/UploadField.php @@ -125,7 +125,7 @@ public function disk($disk) } /** - * Specify the directory and name for uplaod file. + * Specify the directory and name for upload file. * * @param string $directory * @param null|string $name @@ -134,9 +134,25 @@ public function disk($disk) */ public function move($directory, $name = null) { - $this->directory = $directory; + $this->dir($directory); - $this->name = $name; + $this->name($name); + + return $this; + } + + /** + * Specify the directory upload file. + * + * @param string $dir + * + * @return $this + */ + public function dir($dir) + { + if ($dir) { + $this->directory = $dir; + } return $this; } @@ -150,7 +166,9 @@ public function move($directory, $name = null) */ public function name($name) { - $this->name = $name; + if ($name) { + $this->name = $name; + } return $this; } From 6d09c541af1746b3ff7b85bd2b8db1023ee6594a Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 1 Mar 2017 11:30:19 +0800 Subject: [PATCH 0545/2161] optimize html field --- src/Form/Field/Html.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Form/Field/Html.php b/src/Form/Field/Html.php index ee7409df31..c4fe3418ef 100644 --- a/src/Form/Field/Html.php +++ b/src/Form/Field/Html.php @@ -9,7 +9,7 @@ class Html extends Field /** * Htmlable. * - * @var string + * @var string|\Closure */ protected $html = ''; @@ -38,6 +38,12 @@ public function __construct($html, $arguments) */ public function render() { + if ($this->html instanceof \Closure) { + $callback = $this->html->bindTo($this->form->model()); + + $this->html = call_user_func($callback, $this->form); + } + return << From 70daff98e0c82bdd7939de27c3956f3714f67ea3 Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 1 Mar 2017 13:25:44 +0800 Subject: [PATCH 0546/2161] update tests --- tests/FileUploadTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FileUploadTest.php b/tests/FileUploadTest.php index 9a8a054cbe..d8c107dee4 100644 --- a/tests/FileUploadTest.php +++ b/tests/FileUploadTest.php @@ -92,7 +92,7 @@ public function testUpdateFile() ->seeInElement('button[type=reset]', 'Reset') ->seeInElement('button[type=submit]', 'Submit'); - $this->attach(__DIR__.'/RoleTest.php', 'file3') + $this->attach(__DIR__.'/RolesTest.php', 'file3') ->attach(__DIR__.'/MenuTest.php', 'file4') ->attach(__DIR__.'/TestCase.php', 'file5') ->press('Submit'); From 1ebf74d9ae7b11105d28076f1db8e320510532fb Mon Sep 17 00:00:00 2001 From: z-song Date: Wed, 1 Mar 2017 19:56:54 +0800 Subject: [PATCH 0547/2161] implements define global column, fix issue #431 --- src/Grid/Column.php | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/Grid/Column.php b/src/Grid/Column.php index 8dc31a549e..62249cc718 100644 --- a/src/Grid/Column.php +++ b/src/Grid/Column.php @@ -91,6 +91,13 @@ class Column */ public static $displayers = []; + /** + * Defined columns. + * + * @var array + */ + public static $defined = []; + /** * @param string $name * @param string $label @@ -113,6 +120,18 @@ public static function extend($name, $displayer) static::$displayers[$name] = $displayer; } + /** + * Define a column globally. + * + * @param string $name + * + * @param mixed $definition + */ + public static function define($name, $definition) + { + static::$defined[$name] = $definition; + } + /** * Set grid instance for column. * @@ -292,6 +311,10 @@ public function fill(array $data) array_set($row, $this->name, $value); + if ($this->isDefinedColumn()) { + $this->useDefinedColumn(); + } + if ($this->hasDisplayCallbacks()) { $value = $this->callDisplayCallbacks($this->original, $key); array_set($row, $this->name, $value); @@ -301,6 +324,47 @@ public function fill(array $data) return $data; } + /** + * If current column is a defined column. + * + * @return bool + */ + protected function isDefinedColumn() + { + return array_key_exists($this->name, static::$defined); + } + + /** + * Use a defined column + * + * @throws \Exception + */ + protected function useDefinedColumn() + { + // clear all display callbacks. + $this->displayCallbacks = []; + + $class = static::$defined[$this->name]; + + if ($class instanceof Closure) { + $this->display($class); + return; + } + + if (!class_exists($class) || !is_subclass_of($class, AbstractDisplayer::class)) { + throw new \Exception("Invalid column definition [$class]"); + } + + $grid = $this->grid; + $column = $this; + + $this->display(function ($value) use ($grid, $column, $class) { + $definition = new $class($value, $grid, $column, $this); + + return $definition->display(); + }); + } + /** * Convert characters to HTML entities recursively. * From 37566676a087b39b3ba74d133d455252692998cc Mon Sep 17 00:00:00 2001 From: langeuh Date: Wed, 1 Mar 2017 10:02:40 +0100 Subject: [PATCH 0548/2161] swap the order for creating columns through the magic method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit if the column is a relation, there is no need to check if it’s a column in the database this way we don’t need a query if the column is a mutator or relation --- src/Grid.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Grid.php b/src/Grid.php index 2b979cb50b..f913188b9e 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -841,15 +841,15 @@ public function __call($method, $arguments) return $this->addColumn($method, $label); } - if ($column = $this->handleTableColumn($method, $label)) { + if ($column = $this->handleGetMutatorColumn($method, $label)) { return $column; } - if ($column = $this->handleGetMutatorColumn($method, $label)) { + if ($column = $this->handleRelationColumn($method, $label)) { return $column; } - if ($column = $this->handleRelationColumn($method, $label)) { + if ($column = $this->handleTableColumn($method, $label)) { return $column; } From 03ec834febf2cb95bf725d4e53d186e845bb1932 Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 2 Mar 2017 10:37:24 +0800 Subject: [PATCH 0549/2161] fix issue #493 --- src/Form/Builder.php | 16 ++++++++-------- views/form/tab.blade.php | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Form/Builder.php b/src/Form/Builder.php index 6fc4b6fcfe..c2533d30ec 100644 --- a/src/Form/Builder.php +++ b/src/Form/Builder.php @@ -467,23 +467,23 @@ public function render() if (!$tabObj->isEmpty()) { $script = <<<'SCRIPT' -var url = document.location.toString(); -if (url.match('#')) { - $('.nav-tabs a[href="#' + url.split('#')[1] + '"]').tab('show'); +var hash = document.location.hash; +if (hash) { + $('.nav-tabs a[href="' + hash + '"]').tab('show'); } - + // Change hash for page-reload $('.nav-tabs a').on('shown.bs.tab', function (e) { - window.location.hash = e.target.hash; + history.pushState(null,null, e.target.hash); }); if ($('.has-error').length) { - $('.has-error').parent().each(function () { - var tabId = '#'+$(this).attr('id'); + $('.has-error').each(function () { + var tabId = '#'+$(this).closest('.tab-pane').attr('id'); $('li a[href="'+tabId+'"] i').removeClass('hide'); }); - var first = $('.has-error:first').parent().attr('id'); + var first = $('.has-error:first').closest('.tab-pane').attr('id'); $('li a[href="#'+first+'"]').tab('show'); } diff --git a/views/form/tab.blade.php b/views/form/tab.blade.php index 752bb4022a..3b5c40ad1c 100644 --- a/views/form/tab.blade.php +++ b/views/form/tab.blade.php @@ -3,7 +3,7 @@ @foreach($tabObj->getTabs() as $tab)
  • - + {{ $tab['title'] }}
  • From d1f5733de293c8102cea0cc00bcd0791fa6ba35e Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 2 Mar 2017 18:16:39 +0800 Subject: [PATCH 0550/2161] fix range filter issue --- views/filter/between.blade.php | 4 ++-- views/filter/betweenDatetime.blade.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/views/filter/between.blade.php b/views/filter/between.blade.php index 002b772d4d..130b67dde7 100644 --- a/views/filter/between.blade.php +++ b/views/filter/between.blade.php @@ -1,6 +1,6 @@
    {{$label}} - + - - +
    \ No newline at end of file diff --git a/views/filter/betweenDatetime.blade.php b/views/filter/betweenDatetime.blade.php index b0703e5065..a31aaf8ec0 100644 --- a/views/filter/betweenDatetime.blade.php +++ b/views/filter/betweenDatetime.blade.php @@ -1,6 +1,6 @@
    {{$label}} - + - - +
    \ No newline at end of file From 2e9a2890fe6fbcb9bf6f35968b1faee29a893983 Mon Sep 17 00:00:00 2001 From: z-song Date: Thu, 2 Mar 2017 19:28:50 +0800 Subject: [PATCH 0551/2161] fix issue #501 --- src/Grid.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grid.php b/src/Grid.php index f913188b9e..e4deb4cc2a 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -819,7 +819,7 @@ protected function handleRelationColumn($method, $label) if ($relation instanceof HasMany || $relation instanceof BelongsToMany || $relation instanceof MorphToMany) { $this->model()->with($method); - return $this->addColumn($method, $label); + return $this->addColumn(snake_case($method), $label); } return false; From a334fbb6b43bf7d4c07f102423c0c0b45076715d Mon Sep 17 00:00:00 2001 From: Thomas Spiessens Date: Thu, 2 Mar 2017 12:17:24 +0100 Subject: [PATCH 0552/2161] Field->elementClass needs to be an array Calling $field->addElementClass('someClass') throws an error because $this->elementClass is not an array by default, so the merge fails --- src/Form/Field.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Form/Field.php b/src/Form/Field.php index c56a5deb8c..2cc20aa1c0 100644 --- a/src/Form/Field.php +++ b/src/Form/Field.php @@ -68,11 +68,11 @@ class Field implements Renderable protected $elementName = []; /** - * Form element name. + * Form element classes. * * @var string */ - protected $elementClass = ''; + protected $elementClass = []; /** * Variables of elements. From b342ecdb6cf5a495b8e8f961f11ebad508909191 Mon Sep 17 00:00:00 2001 From: liuyiyi Date: Thu, 2 Mar 2017 21:32:50 +0800 Subject: [PATCH 0553/2161] update docs [skip ci] --- docs/zh/model-form-fields.md | 8 ++++---- docs/zh/model-tree.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/zh/model-form-fields.md b/docs/zh/model-form-fields.md index 9ba2814942..291fd98045 100644 --- a/docs/zh/model-form-fields.md +++ b/docs/zh/model-form-fields.md @@ -431,19 +431,19 @@ $form->divide(); $form->html('你的html内容', $label = ''); ``` -### tags +### 标签 插入逗号(,)隔开的字符串`tags` ```php $form->tags('keywords'); ``` -### icon +### 图标 选择`font-awesome`图标 ```php $form->icon('icon'); ``` -### hasMany +### 一对多 一对多内嵌表格,用于处理一对多的关系,下面是个简单的例子: @@ -522,7 +522,7 @@ $form->display('created_at', 'Created At'); $form->display('updated_at', 'Updated At'); ``` -### embeds +### 内嵌 用于处理`mysql`的`JSON`类型字段数据或者`mongodb`的`object`类型数据,也可以将多个field的数据值以`JSON`字符串的形式存储在`mysql`的字符创类型字段中 diff --git a/docs/zh/model-tree.md b/docs/zh/model-tree.md index ca5a8f74b8..09b422f58f 100644 --- a/docs/zh/model-tree.md +++ b/docs/zh/model-tree.md @@ -60,7 +60,7 @@ class Category extends Model } } ``` -## Usage +## 使用 然后就是在页面中使用`model-tree`了: ```php Date: Thu, 2 Mar 2017 09:56:14 -0500 Subject: [PATCH 0554/2161] caches the table columns for later use when grid is created this uses fewer queries since the table definition does not have to be queried each time a column is defined --- src/Grid.php | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Grid.php b/src/Grid.php index e4deb4cc2a..24bf372489 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -40,6 +40,13 @@ class Grid */ protected $columns; + /** + * Collection of table columns. + * + * @var \Illuminate\Support\Collection + */ + protected $dbColumns; + /** * Collection of all data rows. * @@ -174,6 +181,7 @@ public function __construct(Eloquent $model, Closure $builder) $this->rows = new Collection(); $this->builder = $builder; + $this->setDbColumns(); $this->setupTools(); $this->setupFilter(); $this->setupExporter(); @@ -754,6 +762,18 @@ public function resource($path = null) //return app('router')->current()->getPath(); } + /** + * Get the table columns for grid. + * + * @return void + */ + protected function setDbColumns() + { + $connection = $this->model()->eloquent()->getConnectionName(); + + $this->dbColumns = collect(Schema::connection($connection)->getColumnListing($this->model()->getTable())); + } + /** * Handle table column for grid. * @@ -764,9 +784,7 @@ public function resource($path = null) */ protected function handleTableColumn($method, $label) { - $connection = $this->model()->eloquent()->getConnectionName(); - - if (Schema::connection($connection)->hasColumn($this->model()->getTable(), $method)) { + if ($this->dbColumns->has($method)) { return $this->addColumn($method, $label); } From 7664751ae91d99975e46c86b9bcfe8457f84cbaa Mon Sep 17 00:00:00 2001 From: Jon Phipps Date: Fri, 3 Mar 2017 13:48:52 -0500 Subject: [PATCH 0555/2161] remove csrf_token from the form if the form is in Builder::MODE_VIEW --- views/form.blade.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/views/form.blade.php b/views/form.blade.php index ae6cf9c3ed..31df383bbd 100644 --- a/views/form.blade.php +++ b/views/form.blade.php @@ -33,7 +33,9 @@