├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dashboard.png ├── menu.png ├── profile.png └── workflows │ └── php.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── Commands │ ├── InstallCommand.php │ └── PublishCommand.php ├── Config │ ├── Boilerplate.php │ └── Routes.php ├── Controllers │ ├── BaseController.php │ ├── DashboardController.php │ └── Users │ │ ├── MenuController.php │ │ ├── PermissionController.php │ │ ├── RoleController.php │ │ └── UserController.php ├── Database │ ├── Migrations │ │ └── 2020-02-03-081118_create_menu_table.php │ └── Seeds │ │ └── BoilerplateSeeder.php ├── Entities │ ├── Collection.php │ └── MenuEntity.php ├── Filters │ ├── PermissionFilter.php │ └── RoleFilter.php ├── Helpers │ └── menu_helper.php ├── Language │ ├── en │ │ └── boilerplate.php │ └── id │ │ └── boilerplate.php ├── Models │ ├── GroupMenuModel.php │ ├── GroupModel.php │ ├── MenuModel.php │ ├── PermissionModel.php │ └── UserModel.php └── Views │ ├── Authentication │ ├── emails │ │ ├── activation.php │ │ └── forgot.php │ ├── forgot.php │ ├── index.php │ ├── login.php │ ├── message_block.php │ ├── register.php │ └── reset.php │ ├── Menu │ ├── index.php │ └── update.php │ ├── Permission │ ├── create.php │ └── index.php │ ├── Role │ ├── create.php │ ├── edit.php │ └── index.php │ ├── User │ ├── create.php │ ├── index.php │ ├── profile.php │ └── update.php │ ├── dashboard.php │ ├── layout │ ├── contentheader.php │ ├── header.php │ ├── index.php │ └── mainsidebar.php │ └── load │ ├── datatables.php │ ├── duallistbox.php │ ├── iconpicker.php │ ├── nestable.php │ ├── select2.php │ └── sweetalert.php └── tests ├── Controllers └── ExampleTest.php ├── Support ├── AuthTestCase.php └── SessionTestCase.php └── UserTest.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | custom: https://saweria.co/agungsugiarto # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungsugiarto/boilerplate/7ae95390dfab215a66e40a3789ad742c3636f841/.github/dashboard.png -------------------------------------------------------------------------------- /.github/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungsugiarto/boilerplate/7ae95390dfab215a66e40a3789ad742c3636f841/.github/menu.png -------------------------------------------------------------------------------- /.github/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agungsugiarto/boilerplate/7ae95390dfab215a66e40a3789ad742c3636f841/.github/profile.png -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: "ci build" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - "master" 8 | 9 | jobs: 10 | build: 11 | name: PHP ${{ matrix.php-versions }} 12 | runs-on: ubuntu-latest 13 | if: "!contains(github.event.head_commit.message, '[ci skip]')" 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | php-versions: ['7.3', '7.4'] 18 | steps: 19 | - name: Setup PHP Action 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | extensions: intl, json, mbstring, xdebug, xml 23 | php-version: "${{ matrix.php-versions }}" 24 | coverage: xdebug 25 | - name: Checkout 26 | uses: actions/checkout@v2 27 | - name: "Validate composer.json and composer.lock" 28 | run: "composer validate" 29 | - name: "Install dependencies" 30 | run: "composer install --prefer-source --no-progress --no-suggest" 31 | - name: "Run test suite" 32 | run: "composer test" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #------------------------- 2 | # Operating Specific Junk Files 3 | #------------------------- 4 | 5 | # OS X 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # OS X Thumbnails 11 | ._* 12 | 13 | # Windows image file caches 14 | Thumbs.db 15 | ehthumbs.db 16 | Desktop.ini 17 | 18 | # Recycle Bin used on file shares 19 | $RECYCLE.BIN/ 20 | 21 | # Windows Installer files 22 | *.cab 23 | *.msi 24 | *.msm 25 | *.msp 26 | 27 | # Windows shortcuts 28 | *.lnk 29 | 30 | # Linux 31 | *~ 32 | 33 | # KDE directory preferences 34 | .directory 35 | 36 | # Linux trash folder which might appear on any partition or disk 37 | .Trash-* 38 | 39 | #------------------------- 40 | # Environment Files 41 | #------------------------- 42 | # These should never be under version control, 43 | # as it poses a security risk. 44 | .env 45 | .vagrant 46 | Vagrantfile 47 | 48 | #------------------------- 49 | # Temporary Files 50 | #------------------------- 51 | writable/cache/* 52 | !writable/cache/index.html 53 | 54 | writable/logs/* 55 | !writable/logs/index.html 56 | 57 | writable/session/* 58 | !writable/session/index.html 59 | 60 | writable/uploads/* 61 | !writable/uploads/index.html 62 | 63 | writable/debugbar/* 64 | 65 | php_errors.log 66 | 67 | #------------------------- 68 | # User Guide Temp Files 69 | #------------------------- 70 | user_guide_src/build/* 71 | user_guide_src/cilexer/build/* 72 | user_guide_src/cilexer/dist/* 73 | user_guide_src/cilexer/pycilexer.egg-info/* 74 | 75 | #------------------------- 76 | # Test Files 77 | #------------------------- 78 | tests/coverage* 79 | 80 | # Don't save phpunit under version control. 81 | phpunit 82 | 83 | #------------------------- 84 | # Composer 85 | #------------------------- 86 | vendor/ 87 | composer.lock 88 | 89 | #------------------------- 90 | # IDE / Development Files 91 | #------------------------- 92 | 93 | # Modules Testing 94 | _modules/* 95 | 96 | # phpenv local config 97 | .php-version 98 | 99 | # Jetbrains editors (PHPStorm, etc) 100 | .idea/ 101 | *.iml 102 | 103 | # Netbeans 104 | nbproject/ 105 | build/ 106 | nbbuild/ 107 | dist/ 108 | nbdist/ 109 | nbactions.xml 110 | nb-configuration.xml 111 | .nb-gradle/ 112 | 113 | # Sublime Text 114 | *.tmlanguage.cache 115 | *.tmPreferences.cache 116 | *.stTheme.cache 117 | *.sublime-workspace 118 | *.sublime-project 119 | .phpintel 120 | /api/ 121 | 122 | # Visual Studio Code 123 | .vscode/ 124 | 125 | /results/ 126 | /phpunit*.xml 127 | /.phpunit.*.cache 128 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-2020 Agung Sugiarto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 6 | 7 | 8 |

9 | 10 | CodeIgniter 4 Application Boilerplate 11 | ===================================== 12 | This package for CodeIgniter 4 serves as a basic platform for quickly creating a back-office application. It includes profile creation and management, user management, roles, permissions and a dynamically-generated menu. 13 | 14 | Feature 15 | ------- 16 | * Configurable backend theme [AdminLTE 3](https://adminlte.io/docs/3.0/) 17 | * CSS framework [Bootstrap 4](https://getbootstrap.com/) 18 | * Icons by [Font Awesome 5](https://fontawesome.com/) 19 | * Role-based permissions (RBAC) provided by [Myth/Auth](https://github.com/lonnieezell/myth-auth) 20 | * Dynamically-Generated Menu 21 | * Localized English / Indonesian 22 | 23 | This project is still early in its development... please feel free to contribute! 24 | ------------------------------------------------------------ 25 | Screenshoot | Demo On [Heroku](https://boilerplate-codeigniter4.herokuapp.com/) 26 | ------------------------------------------------------------------------------- 27 | ![Dashboard](.github/dashboard.png?raw=true) 28 | 29 | Installation 30 | ------------ 31 | 32 | **1.** Get The Module 33 | 34 | ```bash 35 | composer require agungsugiarto/boilerplate 36 | ``` 37 | 38 | **2.** Set CI_ENVIRONMENT, baseURL, index page, and database config in your `.env` file based on your existing database (If you don't have a `.env` file, you can copy first from `env` file: `cp env .env` first). If the database does not exist, create the database first. 39 | 40 | ```bash 41 | # .env file 42 | CI_ENVIRONMENT = development 43 | 44 | app.baseURL = 'http://localhost:8080' 45 | app.indexPage = '' 46 | 47 | database.default.hostname = localhost 48 | database.default.database = boilerplate 49 | database.default.username = root 50 | database.default.password = 51 | database.default.DBDriver = MySQLi 52 | ``` 53 | **3.** Run publish auth 54 | ```bash 55 | php spark auth:publish 56 | 57 | Publish Migration? [y, n]: y 58 | created: Database/Migrations/2017-11-20-223112_create_auth_tables.php 59 | Remember to run `spark migrate -all` to migrate the database. 60 | Publish Models? [y, n]: n 61 | Publish Entities? [y, n]: n 62 | Publish Controller? [y, n]: n 63 | Publish Views? [y, n]: n 64 | Publish Filters? [y, n]: n 65 | Publish Config file? [y, n]: y 66 | created: Config/Auth.php 67 | Publish Language file? [y, n]: n 68 | ``` 69 | 70 | > NOTE: Everything about how to configure auth you can find add [Myth/Auth](https://github.com/lonnieezell/myth-auth). 71 | 72 | 73 | Is it ready yet? Not so fast!! ;-) After publishing `Config/Auth.php` you need to change 74 | `public $views` with these lines below: 75 | ```php 76 | public $views = [ 77 | 'login' => 'agungsugiarto\boilerplate\Views\Authentication\login', 78 | 'register' => 'agungsugiarto\boilerplate\Views\Authentication\register', 79 | 'forgot' => 'agungsugiarto\boilerplate\Views\Authentication\forgot', 80 | 'reset' => 'agungsugiarto\boilerplate\Views\Authentication\reset', 81 | 'emailForgot' => 'agungsugiarto\boilerplate\Views\Authentication\emails\forgot', 82 | 'emailActivation' => 'agungsugiarto\boilerplate\Views\Authentication\emails\activation', 83 | ]; 84 | ``` 85 | 86 | Open `app\Config\Filters.php`, find `$aliases` and add these lines below: 87 | ```php 88 | public $aliases = [ 89 | 'login' => \Myth\Auth\Filters\LoginFilter::class, 90 | 'role' => \agungsugiarto\boilerplate\Filters\RoleFilter::class, 91 | 'permission' => \agungsugiarto\boilerplate\Filters\PermissionFilter::class, 92 | ]; 93 | ``` 94 | 95 | **4.** Run publish, migrate and seed boilerplate 96 | 97 | ```bash 98 | php spark boilerplate:install 99 | ``` 100 | 101 | **5.** Run development server: 102 | 103 | ```bash 104 | php spark serve 105 | ``` 106 | 107 | **6.** Open in browser http://localhost:8080/admin 108 | ```bash 109 | Default user and password 110 | +----+--------+-------------+ 111 | | No | User | Password | 112 | +----+--------+-------------+ 113 | | 1 | admin | super-admin | 114 | | 2 | user | super-user | 115 | +----+--------+-------------+ 116 | ``` 117 | 118 | Settings 119 | -------- 120 | 121 | Config Boilerplate 122 | 123 | You can configure default dashboard controller and backend theme in `app\Config\Boilerplate.php`, 124 | 125 | ```php 126 | class Boilerplate extends BaseConfig 127 | { 128 | public $appName = 'Boilerplate'; 129 | 130 | public $dashboard = [ 131 | 'namespace' => 'agungsugiarto\boilerplate\Controllers', 132 | 'controller' => 'DashboardController::index', 133 | 'filter' => 'permission:back-office', 134 | ]; 135 | // App/Config/Boilerplate.php 136 | ``` 137 | 138 | Usage 139 | ----- 140 | You can find how it works with the read code routes, controller and views etc. Finnally... Happy Coding! 141 | 142 | Changelog 143 | -------- 144 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 145 | 146 | Contributing 147 | ------------ 148 | Contributions are very welcome. 149 | 150 | License 151 | ------- 152 | 153 | This package is free software distributed under the terms of the [MIT license](LICENSE.md). 154 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agungsugiarto/boilerplate", 3 | "description": "CodeIgniter4 Boilerplate based on AdminLTE 3 with user management, roles, permissions, ...", 4 | "keywords": [ 5 | "codeigniter4", 6 | "authentication", 7 | "authorization", 8 | "boilerplate" 9 | ], 10 | "type": "library", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Agung Sugiarto", 15 | "email": "me.agungsugiarto@gmail.com" 16 | } 17 | ], 18 | "require": { 19 | "php": "^7.3 || ^8.0", 20 | "myth/auth": "^1.0", 21 | "codeigniter4/framework": "^4.1", 22 | "codeigniter4/translations" : "^4.0" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "^9.1", 26 | "fakerphp/faker": "^1.13" 27 | }, 28 | "minimum-stability": "dev", 29 | "prefer-stable": true, 30 | "autoload": { 31 | "psr-4": { 32 | "agungsugiarto\\boilerplate\\": "src" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Tests\\": "tests/" 38 | } 39 | }, 40 | "scripts": { 41 | "post-update-cmd": [ 42 | "@composer dump-autoload" 43 | ], 44 | "test": "phpunit --colors=always -vvv" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests 16 | 17 | 18 | 19 | 20 | 21 | ./src 22 | 23 | ./src/Database 24 | ./src/Views 25 | ./src/Config 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/Commands/InstallCommand.php: -------------------------------------------------------------------------------- 1 | call('boilerplate:publish'); 67 | // migrate all first 68 | $this->call('migrate'); 69 | // then seed data 70 | $seeder = Database::seeder(); 71 | $seeder->call('agungsugiarto\boilerplate\Database\Seeds\BoilerplateSeeder'); 72 | } catch (\Exception $e) { 73 | $this->showError($e); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Commands/PublishCommand.php: -------------------------------------------------------------------------------- 1 | determineSourcePath(); 74 | $this->publishConfig(); 75 | $this->publishMigration(); 76 | } 77 | 78 | protected function publishConfig() 79 | { 80 | $path = "{$this->sourcePath}/Config/Boilerplate.php"; 81 | 82 | $content = file_get_contents($path); 83 | $content = str_replace('namespace agungsugiarto\boilerplate\Config', 'namespace Config', $content); 84 | 85 | $this->writeFile('Config/Boilerplate.php', $content); 86 | } 87 | 88 | protected function publishMigration() 89 | { 90 | $map = directory_map($this->sourcePath.'/Database/Migrations'); 91 | 92 | foreach ($map as $file) { 93 | $content = file_get_contents("{$this->sourcePath}/Database/Migrations/{$file}"); 94 | $content = str_replace('namespace agungsugiarto\boilerplate\Database\Migrations', 'namespace '.APP_NAMESPACE.'\Database\Migrations', $content); 95 | 96 | $this->writeFile("Database/Migrations/{$file}", $content); 97 | } 98 | } 99 | 100 | //-------------------------------------------------------------------- 101 | // Utilities 102 | //-------------------------------------------------------------------- 103 | 104 | /** 105 | * Replaces the Myth\Auth namespace in the published 106 | * file with the applications current namespace. 107 | * 108 | * @param string $contents 109 | * @param string $originalNamespace 110 | * @param string $newNamespace 111 | * 112 | * @return string 113 | */ 114 | protected function replaceNamespace(string $contents, string $originalNamespace, string $newNamespace): string 115 | { 116 | $appNamespace = APP_NAMESPACE; 117 | $originalNamespace = "namespace {$originalNamespace}"; 118 | $newNamespace = "namespace {$appNamespace}\\{$newNamespace}"; 119 | 120 | return str_replace($originalNamespace, $newNamespace, $contents); 121 | } 122 | 123 | /** 124 | * Determines the current source path from which all other files are located. 125 | */ 126 | protected function determineSourcePath() 127 | { 128 | $this->sourcePath = realpath(__DIR__.'/../'); 129 | 130 | if ($this->sourcePath == '/' || empty($this->sourcePath)) { 131 | CLI::error('Unable to determine the correct source directory. Bailing.'); 132 | exit(); 133 | } 134 | } 135 | 136 | /** 137 | * Write a file, catching any exceptions and showing a 138 | * nicely formatted error. 139 | * 140 | * @param string $path 141 | * @param string $content 142 | */ 143 | protected function writeFile(string $path, string $content) 144 | { 145 | $config = new Autoload(); 146 | $appPath = $config->psr4[APP_NAMESPACE]; 147 | 148 | $directory = dirname($appPath.$path); 149 | 150 | if (!is_dir($directory)) { 151 | mkdir($directory, 0777, true); 152 | } 153 | 154 | try { 155 | write_file($appPath.$path, $content); 156 | } catch (\Exception $e) { 157 | $this->showError($e); 158 | exit(); 159 | } 160 | 161 | $path = str_replace($appPath, '', $path); 162 | 163 | CLI::write(CLI::color(' created: ', 'green').$path); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/Config/Boilerplate.php: -------------------------------------------------------------------------------- 1 | 'agungsugiarto\boilerplate\Controllers', 24 | 'controller' => 'DashboardController::index', 25 | 'filter' => 'permission:back-office', 26 | ]; 27 | 28 | //-------------------------------------------------------------------------- 29 | // Config cdn for language datatables 30 | // pelase see https://cdn.datatables.net/plug-ins/1.10.20/i18n/ 31 | //-------------------------------------------------------------------------- 32 | 33 | public $i18n = 'Indonesian'; 34 | 35 | //-------------------------------------------------------------------------- 36 | // Theme boilerplate 37 | // 38 | // BG: blue, indigo, purple, pink, red, orange, yellow, green, teal, cyan, 39 | // gray, gray-dark, black 40 | // Type: dark, light 41 | // Shadow: 0-4 42 | // 43 | //-------------------------------------------------------------------------- 44 | 45 | public $theme = [ 46 | 'body-sm' => false, 47 | 'navbar' => [ 48 | 'bg' => 'white', 49 | 'type' => 'light', 50 | 'border' => true, 51 | 'user' => [ 52 | 'visible' => true, 53 | 'shadow' => 0, 54 | ], 55 | ], 56 | 'sidebar' => [ 57 | 'type' => 'dark', 58 | 'shadow' => 4, 59 | 'border' => false, 60 | 'compact' => true, 61 | 'links' => [ 62 | 'bg' => 'blue', 63 | 'shadow' => 1, 64 | ], 65 | 'brand' => [ 66 | 'bg' => 'gray-dark', 67 | 'logo' => [ 68 | 'icon' => 'favicon.ico', // path to image | this example icon on public root folder. 69 | 'text' => 'Boilerplate', 70 | 'shadow' => 2, 71 | ], 72 | ], 73 | 'user' => [ 74 | 'visible' => true, 75 | 'shadow' => 2, 76 | ], 77 | ], 78 | 'footer' => [ 79 | 'fixed' => false, 80 | 'vendorname' => 'Your Awesome Vendor', 81 | 'vendorlink' => 'https://your-awesome.com', 82 | ], 83 | ]; 84 | } 85 | -------------------------------------------------------------------------------- /src/Config/Routes.php: -------------------------------------------------------------------------------- 1 | group('admin', function ($routes) { 4 | 5 | /** 6 | * Admin routes. 7 | **/ 8 | $routes->group('/', [ 9 | 'filter' => config('Boilerplate')->dashboard['filter'], 10 | 'namespace' => config('Boilerplate')->dashboard['namespace'], 11 | ], function ($routes) { 12 | $routes->get('/', config('Boilerplate')->dashboard['controller']); 13 | }); 14 | 15 | /** 16 | * User routes. 17 | **/ 18 | $routes->group('user', [ 19 | 'filter' => 'permission:back-office', 20 | 'namespace' => 'agungsugiarto\boilerplate\Controllers\Users', 21 | ], function ($routes) { 22 | $routes->match(['get', 'post'], 'profile', 'UserController::profile', ['as' => 'user-profile']); 23 | $routes->resource('manage', [ 24 | 'filter' => 'permission:manage-user', 25 | 'namespace' => 'agungsugiarto\boilerplate\Controllers\Users', 26 | 'controller' => 'UserController', 27 | 'except' => 'show', 28 | ]); 29 | }); 30 | 31 | /** 32 | * Permission routes. 33 | */ 34 | $routes->resource('permission', [ 35 | 'filter' => 'permission:role-permission', 36 | 'namespace' => 'agungsugiarto\boilerplate\Controllers\Users', 37 | 'controller' => 'PermissionController', 38 | 'except' => 'show,new', 39 | ]); 40 | 41 | /** 42 | * Role routes. 43 | */ 44 | $routes->resource('role', [ 45 | 'filter' => 'permission:role-permission', 46 | 'namespace' => 'agungsugiarto\boilerplate\Controllers\Users', 47 | 'controller' => 'RoleController', 48 | ]); 49 | 50 | /** 51 | * Menu routes. 52 | */ 53 | $routes->resource('menu', [ 54 | 'filter' => 'permission:menu-permission', 55 | 'namespace' => 'agungsugiarto\boilerplate\Controllers\Users', 56 | 'controller' => 'MenuController', 57 | 'except' => 'new,show', 58 | ]); 59 | 60 | $routes->put('menu-update', 'MenuController::new', [ 61 | 'filter' => 'permission:menu-permission', 62 | 'namespace' => 'agungsugiarto\boilerplate\Controllers\Users', 63 | 'except' => 'show', 64 | 'as' => 'menu-update', 65 | ]); 66 | }); 67 | -------------------------------------------------------------------------------- /src/Controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | auth = Services::authentication(); 53 | $this->authorize = Services::authorization(); 54 | $this->db = Database::connect(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Controllers/DashboardController.php: -------------------------------------------------------------------------------- 1 | 'Dashboard', 14 | ]; 15 | 16 | return view('agungsugiarto\boilerplate\Views\dashboard', $data); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Controllers/Users/MenuController.php: -------------------------------------------------------------------------------- 1 | menu = new MenuModel(); 24 | $this->groupsMenu = new GroupMenuModel(); 25 | } 26 | 27 | /** 28 | * Return an array of resource objects, themselves in array format. 29 | * 30 | * @return \CodeIgniter\View\View | \CodeIgniter\API\ResponseTrait 31 | */ 32 | public function index() 33 | { 34 | if ($this->request->isAJAX()) { 35 | return $this->respond(['data' => nestable()]); 36 | } 37 | 38 | return view('agungsugiarto\boilerplate\Views\Menu\index', [ 39 | 'title' => lang('boilerplate.menu.title'), 40 | 'subtitle' => lang('boilerplate.menu.subtitle'), 41 | 'roles' => $this->authorize->groups(), 42 | 'menus' => $this->menu->orderBy('sequence', 'asc')->findAll(), 43 | ]); 44 | } 45 | 46 | /** 47 | * Update to sort menu. 48 | * 49 | * @return CodeIgniter\API\ResponseTrait 50 | */ 51 | public function new() 52 | { 53 | $data = $this->request->getJSON(); 54 | $menu = new MenuEntity(); 55 | 56 | $this->db->transBegin(); 57 | 58 | try { 59 | $i = 1; 60 | foreach ($data as $item) { 61 | if (isset($item->parent_id)) { 62 | $menu->parent_id = $item->parent_id; 63 | $menu->sequence = $i++; 64 | } else { 65 | $menu->parent_id = 0; 66 | $menu->sequence = $i++; 67 | } 68 | 69 | $this->menu->update($item->id, $menu); 70 | } 71 | 72 | $this->db->transCommit(); 73 | } catch (\Exception $e) { 74 | $this->db->transRollback(); 75 | 76 | return $this->fail(lang('boilerplate.menu.msg.msg_fail_order')); 77 | } 78 | 79 | return $this->respondUpdated([], lang('boilerplate.menu.msg.msg_update')); 80 | } 81 | 82 | /** 83 | * Create a new resource object, from "posted" parameters. 84 | * 85 | * @return \CodeIgniter\API\ResponseTrait 86 | */ 87 | public function create() 88 | { 89 | $validationRules = [ 90 | 'parent_id' => 'required|numeric', 91 | 'active' => 'required|numeric', 92 | 'icon' => 'required|min_length[5]|max_length[55]', 93 | 'route' => 'required|max_length[255]', 94 | 'title' => 'required|min_length[2]|max_length[255]', 95 | 'groups_menu' => 'required', 96 | ]; 97 | 98 | if (!$this->validate($validationRules)) { 99 | return redirect()->back()->withInput()->with('error', $this->validator->getErrors()); 100 | } 101 | 102 | $this->db->transBegin(); 103 | 104 | try { 105 | $menu = new MenuEntity(); 106 | $menu->parent_id = $this->request->getPost('parent_id'); 107 | $menu->active = $this->request->getPost('active'); 108 | $menu->title = $this->request->getPost('title'); 109 | $menu->icon = $this->request->getPost('icon'); 110 | $menu->route = $this->request->getPost('route'); 111 | $menu->sequence = $menu->sequence() + 1; 112 | 113 | $id = $this->menu->insert($menu); 114 | 115 | foreach ($this->request->getPost('groups_menu') as $groups) { 116 | $this->groupsMenu->insert([ 117 | 'group_id' => $groups, 118 | 'menu_id' => $id, 119 | ]); 120 | } 121 | 122 | $this->db->transCommit(); 123 | } catch (\Exception $e) { 124 | $this->db->transRollback(); 125 | 126 | return redirect()->back()->with('sweet-error', $e->getMessage()); 127 | } 128 | 129 | return redirect()->back()->with('sweet-success', lang('boilerplate.menu.msg.msg_insert')); 130 | } 131 | 132 | /** 133 | * Add or update a model resource, from "posted" properties. 134 | * 135 | * @param int id 136 | * 137 | * @return \CodeIgniter\API\ResponseTrait 138 | */ 139 | public function update($id) 140 | { 141 | $validationRules = [ 142 | 'parent_id' => 'required|numeric', 143 | 'active' => 'required|numeric', 144 | 'icon' => 'required|min_length[5]|max_length[55]', 145 | 'route' => 'required|max_length[255]', 146 | 'title' => 'required|min_length[2]|max_length[255]', 147 | 'groups_menu' => 'required', 148 | ]; 149 | 150 | if (!$this->validate($validationRules)) { 151 | return $this->fail($this->validator->getErrors()); 152 | } 153 | 154 | $data = $this->request->getRawInput(); 155 | 156 | $this->db->transBegin(); 157 | 158 | try { 159 | $menu = $this->menu->update($id, [ 160 | 'parent_id' => $data['parent_id'], 161 | 'active' => $data['active'], 162 | 'title' => $data['title'], 163 | 'icon' => $data['icon'], 164 | 'route' => $data['route'], 165 | ]); 166 | 167 | // first remove all groups_menu by id 168 | $this->db->table('groups_menu')->where('menu_id', $id)->delete(); 169 | 170 | foreach ($data['groups_menu'] as $groups) { 171 | // insert with new 172 | $this->groupsMenu->insert([ 173 | 'group_id' => $groups, 174 | 'menu_id' => $id, 175 | ]); 176 | } 177 | 178 | $this->db->transCommit(); 179 | } catch (\Exception $e) { 180 | $this->db->transRollback(); 181 | 182 | return $this->fail($e->getMessage()); 183 | } 184 | 185 | return $this->respondUpdated($menu, lang('boilerplate.menu.msg.msg_update')); 186 | } 187 | 188 | /** 189 | * Return the editable properties of a resource object. 190 | * 191 | * @param int id 192 | * 193 | * @return \CodeIgniter\API\ResponseTrait 194 | */ 195 | public function edit($id) 196 | { 197 | $found = $this->menu->getMenuById($id); 198 | 199 | if ($this->request->isAJAX()) { 200 | if (!$found) { 201 | return $this->failNotFound(lang('boilerplate.menu.msg.msg_get_fail')); 202 | } 203 | 204 | return $this->respond([ 205 | 'data' => $found, 206 | 'menu' => $this->menu->getMenu(), 207 | 'roles' => $this->menu->getRole(), 208 | ]); 209 | } 210 | } 211 | 212 | /** 213 | * Delete the designated resource object from the model. 214 | * 215 | * @param int id 216 | * 217 | * @return \CodeIgniter\API\ResponseTrait 218 | */ 219 | public function delete($id) 220 | { 221 | if (!$this->menu->delete($id)) { 222 | return $this->failNotFound(lang('boilerplate.menu.msg.msg_get_fail')); 223 | } 224 | 225 | return $this->respondDeleted(['id' => $id], lang('boilerplate.menu.msg.msg_delete')); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/Controllers/Users/PermissionController.php: -------------------------------------------------------------------------------- 1 | permission = new PermissionModel(); 28 | } 29 | 30 | /** 31 | * Return an array of resource objects, themselves in array format. 32 | * 33 | * @return View | \CodeIgniter\API\ResponseTrait 34 | */ 35 | public function index() 36 | { 37 | if ($this->request->isAJAX()) { 38 | $start = $this->request->getGet('start'); 39 | $length = $this->request->getGet('length'); 40 | $search = $this->request->getGet('search[value]'); 41 | $order = PermissionModel::ORDERABLE[$this->request->getGet('order[0][column]')]; 42 | $dir = $this->request->getGet('order[0][dir]'); 43 | 44 | return $this->respond(Collection::datatable( 45 | $this->permission->getResource($search)->orderBy($order, $dir)->limit($length, $start)->get()->getResultObject(), 46 | $this->permission->getResource()->countAllResults(), 47 | $this->permission->getResource($search)->countAllResults() 48 | )); 49 | } 50 | 51 | return view('agungsugiarto\boilerplate\Views\Permission\index', [ 52 | 'title' => lang('boilerplate.permission.title'), 53 | 'subtitle' => lang('boilerplate.permission.subtitle'), 54 | ]); 55 | } 56 | 57 | /** 58 | * Create a new resource object, from "posted" parameters. 59 | * 60 | * @return array an array 61 | */ 62 | public function create() 63 | { 64 | if (!$data = $this->permission->save($this->request->getPost())) { 65 | return $this->fail($this->permission->errors()); 66 | } 67 | 68 | return $this->respondCreated($data, lang('boilerplate.permission.msg.msg_insert')); 69 | } 70 | 71 | /** 72 | * Return the editable properties of a resource object. 73 | * 74 | * @param int $id 75 | * 76 | * @return array an array 77 | */ 78 | public function edit($id = null) 79 | { 80 | if (!$found = $this->permission->find($id)) { 81 | return $this->failNotFound(lang('boilerplate.permission.msg.msg_get_fail', [$id])); 82 | } 83 | 84 | return $this->respond(['data' => $found], 200, lang('boilerplate.permission.msg.msg_get', [$id])); 85 | } 86 | 87 | /** 88 | * Add or update a model resource, from "posted" properties. 89 | * 90 | * @param int $id 91 | * 92 | * @return array an array 93 | */ 94 | public function update($id = null) 95 | { 96 | if (!$result = $this->permission->update($id, $this->request->getRawInput())) { 97 | return $this->fail($this->permission->errors()); 98 | } 99 | 100 | return $this->respondUpdated($result, lang('boilerplate.permission.msg.msg_update', [$id])); 101 | } 102 | 103 | /** 104 | * Delete the designated resource object from the model. 105 | * 106 | * @param int $id 107 | * 108 | * @return array an array 109 | */ 110 | public function delete($id = null) 111 | { 112 | if (!$this->permission->delete($id)) { 113 | return $this->failNotFound(lang('boilerplate.permission.msg.msg_get_fail', [$id])); 114 | } 115 | 116 | return $this->respondDeleted(['id' => $id], lang('boilerplate.permission.msg.msg_delete', [$id])); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Controllers/Users/RoleController.php: -------------------------------------------------------------------------------- 1 | group = new GroupModel(); 23 | } 24 | 25 | /** 26 | * Return an array of resource objects, themselves in array format. 27 | * 28 | * @return array an array 29 | */ 30 | public function index() 31 | { 32 | if ($this->request->isAJAX()) { 33 | $start = $this->request->getGet('start'); 34 | $length = $this->request->getGet('length'); 35 | $search = $this->request->getGet('search[value]'); 36 | $order = GroupModel::ORDERABLE[$this->request->getGet('order[0][column]')]; 37 | $dir = $this->request->getGet('order[0][dir]'); 38 | 39 | return $this->respond(Collection::datatable( 40 | $this->group->getResource($search)->orderBy($order, $dir)->limit($length, $start)->get()->getResultObject(), 41 | $this->group->getResource()->countAllResults(), 42 | $this->group->getResource($search)->countAllResults() 43 | )); 44 | } 45 | 46 | return view('agungsugiarto\boilerplate\Views\Role\index', [ 47 | 'title' => lang('boilerplate.role.title'), 48 | 'subtitle' => lang('boilerplate.role.subtitle'), 49 | 'data' => $this->authorize->permissions(), 50 | ]); 51 | } 52 | 53 | /** 54 | * Return a new resource object, with default properties. 55 | * 56 | * @return array an array 57 | */ 58 | public function new() 59 | { 60 | $data = [ 61 | 'title' => lang('boilerplate.role.title'), 62 | 'subtitle' => lang('boilerplate.role.add'), 63 | 'data' => $this->authorize->permissions(), 64 | ]; 65 | 66 | return view('agungsugiarto\boilerplate\Views\Role\create', $data); 67 | } 68 | 69 | /** 70 | * Create a new resource object, from "posted" parameters. 71 | * 72 | * @return array an array 73 | */ 74 | public function create() 75 | { 76 | $validationRules = [ 77 | 'name' => 'required|min_length[5]|max_length[255]|is_unique[auth_groups.name]', 78 | 'description' => 'required|max_length[255]', 79 | 'permission' => 'required', 80 | ]; 81 | 82 | $name = $this->request->getPost('name'); 83 | $description = $this->request->getPost('description'); 84 | $permission = $this->request->getPost('permission'); 85 | 86 | if (!$this->validate($validationRules)) { 87 | return redirect()->back()->withInput()->with('error', $this->validator->getErrors()); 88 | } 89 | 90 | $this->db->transBegin(); 91 | 92 | try { 93 | $id = $this->authorize->createGroup(url_title($name), $description); 94 | 95 | foreach ($permission as $value) { 96 | $this->authorize->addPermissionToGroup($value, $id); 97 | } 98 | 99 | $this->db->transCommit(); 100 | } catch (\Exception $e) { 101 | $this->db->transRollback(); 102 | 103 | return redirect()->back()->with('sweet-error', $e->getMessage()); 104 | } 105 | 106 | return redirect()->back()->with('sweet-success', lang('boilerplate.role.msg.msg_insert')); 107 | } 108 | 109 | /** 110 | * Return the editable properties of a resource object. 111 | * 112 | * @param int $id 113 | * 114 | * @return array an array 115 | */ 116 | public function edit($id = null) 117 | { 118 | if (is_null($this->authorize->group($id))) { 119 | return redirect()->back()->with('sweet-error', lang('boilerplate.role.msg.msg_get_fail', [$id])); 120 | } 121 | 122 | $data = [ 123 | 'title' => lang('boilerplate.role.title'), 124 | 'subtitle' => lang('boilerplate.role.edit'), 125 | 'role' => $this->authorize->group($id), 126 | 'permissions' => $this->authorize->permissions(), 127 | 'permission' => $this->authorize->groupPermissions($id), 128 | ]; 129 | 130 | return view('agungsugiarto\boilerplate\Views\Role\edit', $data); 131 | } 132 | 133 | /** 134 | * Add or update a model resource, from "posted" properties. 135 | * 136 | * @param int $id 137 | * 138 | * @return array an array 139 | */ 140 | public function update($id = null) 141 | { 142 | $validationRules = [ 143 | 'name' => 'required|min_length[5]|max_length[255]', 144 | 'description' => 'required|max_length[255]', 145 | 'permission' => 'required', 146 | ]; 147 | 148 | $name = $this->request->getPost('name'); 149 | $description = $this->request->getPost('description'); 150 | $permission = $this->request->getPost('permission'); 151 | 152 | if (!$this->validate($validationRules)) { 153 | return redirect()->back()->withInput()->with('error', $this->validator->getErrors()); 154 | } 155 | 156 | $this->db->transBegin(); 157 | 158 | try { 159 | // update group 160 | $this->authorize->updateGroup($id, url_title($name), $description); 161 | 162 | // remove first all groups permissions 163 | $this->db->table('auth_groups_permissions')->where('group_id', $id)->delete(); 164 | 165 | foreach ($permission as $value) { 166 | // insert with new permission to group 167 | $this->authorize->addPermissionToGroup($value, $id); 168 | } 169 | 170 | $this->db->transCommit(); 171 | } catch (\Exception $e) { 172 | $this->db->transRollback(); 173 | 174 | return redirect()->back()->with('sweet-error', $e->getMessage()); 175 | } 176 | 177 | return redirect()->back()->with('sweet-success', lang('boilerplate.role.msg.msg_update', [$id])); 178 | } 179 | 180 | /** 181 | * Delete the designated resource object from the model. 182 | * 183 | * @param int $id 184 | * 185 | * @return array an array 186 | */ 187 | public function delete($id = null) 188 | { 189 | if (!$found = $this->authorize->deleteGroup($id)) { 190 | return $this->failNotFound(lang('boilerplate.role.msg.msg_get_fail', [$id])); 191 | } 192 | 193 | return $this->respondDeleted($found, lang('boilerplate.role.msg.msg_delete', [$id])); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/Controllers/Users/UserController.php: -------------------------------------------------------------------------------- 1 | users = new UserModel(); 26 | } 27 | 28 | /** 29 | * Return an array of resource objects, themselves in array format. 30 | * 31 | * @return mixed 32 | */ 33 | public function index() 34 | { 35 | if ($this->request->isAJAX()) { 36 | $start = $this->request->getGet('start'); 37 | $length = $this->request->getGet('length'); 38 | $search = $this->request->getGet('search[value]'); 39 | $order = UserModel::ORDERABLE[$this->request->getGet('order[0][column]')]; 40 | $dir = $this->request->getGet('order[0][dir]'); 41 | 42 | return $this->respond(Collection::datatable( 43 | $this->users->getResource($search)->orderBy($order, $dir)->limit($length, $start)->get()->getResultObject(), 44 | $this->users->getResource()->countAllResults(), 45 | $this->users->getResource($search)->countAllResults() 46 | )); 47 | } 48 | 49 | return view('agungsugiarto\boilerplate\Views\User\index', [ 50 | 'title' => lang('boilerplate.user.title'), 51 | 'subtitle' => lang('boilerplate.user.subtitle'), 52 | ]); 53 | } 54 | 55 | /** 56 | * Show profile user or update. 57 | * 58 | * @return mixed 59 | */ 60 | public function profile() 61 | { 62 | if ($this->request->getMethod() === 'post') { 63 | $id = user()->id; 64 | $validationRules = [ 65 | 'email' => "required|valid_email|is_unique[users.email,id,$id]", 66 | 'username' => "required|alpha_numeric_space|min_length[3]|is_unique[users.username,id,$id]", 67 | 'password' => 'if_exist', 68 | 'pass_confirm' => 'matches[password]', 69 | ]; 70 | 71 | if (!$this->validate($validationRules)) { 72 | return redirect()->back()->withInput()->with('error', $this->validator->getErrors()); 73 | } 74 | 75 | $user = new User(); 76 | 77 | if ($this->request->getPost('password')) { 78 | $user->password = $this->request->getPost('password'); 79 | } 80 | 81 | $user->email = $this->request->getPost('email'); 82 | $user->username = $this->request->getPost('username'); 83 | 84 | if ($this->users->skipValidation(true)->update(user()->id, $user)) { 85 | return redirect()->back()->with('sweet-success', lang('boilerplate.user.msg.msg_update')); 86 | } 87 | 88 | return redirect()->back()->withInput()->with('sweet-error', lang('boilerplate.user.msg.msg_get_fail')); 89 | } 90 | 91 | return view('agungsugiarto\boilerplate\Views\User\profile', [ 92 | 'title' => lang('boilerplate.user.fields.profile'), 93 | ]); 94 | } 95 | 96 | /** 97 | * Create a new resource object, from "posted" parameters. 98 | * 99 | * @return mixed 100 | */ 101 | public function new() 102 | { 103 | return view('agungsugiarto\boilerplate\Views\User\create', [ 104 | 'title' => lang('boilerplate.user.title'), 105 | 'subtitle' => lang('boilerplate.user.add'), 106 | 'permissions' => $this->authorize->permissions(), 107 | 'roles' => $this->authorize->groups(), 108 | ]); 109 | } 110 | 111 | /** 112 | * Create a new resource object, from "posted" parameters. 113 | * 114 | * @return mixed 115 | */ 116 | public function create() 117 | { 118 | $validationRules = [ 119 | 'active' => 'required', 120 | 'username' => 'required|alpha_numeric_space|min_length[3]|is_unique[users.username]', 121 | 'email' => 'required|valid_email|is_unique[users.email]', 122 | 'password' => 'required|strong_password', 123 | 'pass_confirm' => 'required|matches[password]', 124 | 'permission' => 'required', 125 | 'role' => 'required', 126 | ]; 127 | 128 | $permissions = $this->request->getPost('permission'); 129 | $roles = $this->request->getPost('role'); 130 | 131 | if (!$this->validate($validationRules)) { 132 | return redirect()->back()->withInput()->with('error', $this->validator->getErrors()); 133 | } 134 | 135 | $this->db->transBegin(); 136 | 137 | try { 138 | $id = $this->users->insert(new User([ 139 | 'active' => $this->request->getPost('active'), 140 | 'email' => $this->request->getPost('email'), 141 | 'username' => $this->request->getPost('username'), 142 | 'password' => $this->request->getPost('password'), 143 | ])); 144 | 145 | foreach ($permissions as $permission) { 146 | $this->authorize->addPermissionToUser($permission, $id); 147 | } 148 | 149 | foreach ($roles as $role) { 150 | $this->authorize->addUserToGroup($id, $role); 151 | } 152 | 153 | $this->db->transCommit(); 154 | } catch (\Exception $e) { 155 | $this->db->transRollback(); 156 | 157 | return redirect()->back()->with('sweet-error', $e->getMessage()); 158 | } 159 | 160 | return redirect()->back()->with('sweet-success', lang('boileplate.user.msg.msg_insert')); 161 | } 162 | 163 | /** 164 | * Return the editable properties of a resource object. 165 | * 166 | * @param int id 167 | * 168 | * @return mixed 169 | */ 170 | public function edit($id) 171 | { 172 | $data = [ 173 | 'title' => lang('boilerplate.user.title'), 174 | 'subtitle' => lang('boilerplate.user.edit'), 175 | 'permissions' => $this->authorize->permissions(), 176 | 'permission' => (new PermissionModel())->getPermissionsForUser($id), 177 | 'roles' => $this->authorize->groups(), 178 | 'role' => (new GroupModel())->getGroupsForUser($id), 179 | 'user' => $this->users->asArray()->find($id), 180 | ]; 181 | 182 | return view('agungsugiarto\boilerplate\Views\User\update', $data); 183 | } 184 | 185 | /** 186 | * Add or update a model resource, from "posted" properties. 187 | * 188 | * @param int id 189 | * 190 | * @return mixed 191 | */ 192 | public function update($id) 193 | { 194 | $validationRules = [ 195 | 'active' => 'required', 196 | 'username' => "required|alpha_numeric_space|min_length[3]|is_unique[users.username,id,$id]", 197 | 'email' => "required|valid_email|is_unique[users.email,id,$id]", 198 | 'password' => 'if_exist', 199 | 'pass_confirm' => 'matches[password]', 200 | 'permission' => 'if_exist', 201 | 'role' => 'if_exist', 202 | ]; 203 | 204 | if (!$this->validate($validationRules)) { 205 | return redirect()->back()->withInput()->with('error', $this->validator->getErrors()); 206 | } 207 | 208 | $this->db->transBegin(); 209 | 210 | try { 211 | $user = new User(); 212 | 213 | if ($this->request->getPost('password')) { 214 | $user->password = $this->request->getPost('password'); 215 | } 216 | 217 | $user->active = $this->request->getPost('active'); 218 | $user->email = $this->request->getPost('email'); 219 | $user->username = $this->request->getPost('username'); 220 | 221 | $this->users->skipValidation(true)->update($id, $user); 222 | 223 | // delete first permission from user 224 | $this->db->table('auth_users_permissions')->where('user_id', $id)->delete(); 225 | 226 | foreach ($this->request->getPost('permission') as $permission) { 227 | // insert with new permission 228 | $this->authorize->addPermissionToUser($permission, $id); 229 | } 230 | 231 | // delete first groups from user 232 | $this->db->table('auth_groups_users')->where('user_id', $id)->delete(); 233 | 234 | foreach ($this->request->getPost('role') as $role) { 235 | // insert with new role 236 | $this->authorize->addUserToGroup($id, $role); 237 | } 238 | 239 | $this->db->transCommit(); 240 | } catch (\Exception $e) { 241 | $this->db->transRollback(); 242 | 243 | return redirect()->back()->with('sweet-error', $e->getMessage()); 244 | } 245 | 246 | return redirect()->back()->with('sweet-success', lang('boilerplate.user.msg.msg_update')); 247 | } 248 | 249 | /** 250 | * Delete the designated resource object from the model. 251 | * 252 | * @param int id 253 | * 254 | * @return mixed 255 | */ 256 | public function delete($id) 257 | { 258 | if (!$found = $this->users->delete($id)) { 259 | return $this->failNotFound(lang('boilerplate.user.msg.msg_get_fail')); 260 | } 261 | 262 | return $this->respondDeleted($found, lang('boilerplate.user.msg.msg_delete')); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/Database/Migrations/2020-02-03-081118_create_menu_table.php: -------------------------------------------------------------------------------- 1 | forge->addField([ 16 | 'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], 17 | 'parent_id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'null' => true], 18 | 'active' => ['type' => 'tinyint', 'constraint' => '1', 'default' => 1], 19 | 'title' => ['type' => 'varchar', 'constraint' => 255, 'null' => true], 20 | 'icon' => ['type' => 'varchar', 'constraint' => 55, 'null' => true], 21 | 'route' => ['type' => 'varchar', 'constraint' => 255, 'null' => true], 22 | 'sequence' => ['type' => 'int', 'constraint' => 11, 'null' => true], 23 | 'created_at' => ['type' => 'datetime', 'null' => true], 24 | 'updated_at' => ['type' => 'datetime', 'null' => true], 25 | ]); 26 | 27 | $this->forge->addKey('id', true); 28 | $this->forge->createTable('menu', true); 29 | 30 | // TODO: group menu item 31 | $this->forge->addField([ 32 | 'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], 33 | 'group_id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'default' => 0], 34 | 'menu_id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'default' => 0], 35 | ]); 36 | 37 | $this->forge->addKey(['id', 'group_id', 'menu_id']); 38 | $this->forge->addForeignKey('menu_id', 'menu', 'id', false, 'CASCADE'); 39 | $this->forge->addForeignKey('group_id', 'auth_groups', 'id', false, 'CASCADE'); 40 | $this->forge->createTable('groups_menu', true); 41 | } 42 | 43 | //-------------------------------------------------------------------- 44 | 45 | public function down() 46 | { 47 | $this->forge->dropTable('groups_menu'); 48 | $this->forge->dropTable('menu'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Database/Seeds/BoilerplateSeeder.php: -------------------------------------------------------------------------------- 1 | authorize = Services::authorization(); 33 | $this->db = \Config\Database::connect(); 34 | $this->users = new UserModel(); 35 | } 36 | 37 | public function run() 38 | { 39 | // User 40 | $this->users->save(new User([ 41 | 'email' => 'admin@admin.com', 42 | 'username' => 'admin', 43 | 'password' => 'super-admin', 44 | 'active' => '1', 45 | ])); 46 | 47 | $this->users->save(new User([ 48 | 'email' => 'user@user.com', 49 | 'username' => 'user', 50 | 'password' => 'super-user', 51 | 'active' => '1', 52 | ])); 53 | 54 | // Role 55 | $this->authorize->createGroup('admin', 'Administrators. The top of the food chain.'); 56 | $this->authorize->createGroup('member', 'Member everyday member.'); 57 | 58 | // Permission 59 | $this->authorize->createPermission('back-office', 'User can access to the administration panel.'); 60 | $this->authorize->createPermission('manage-user', 'User can create, delete or modify the users.'); 61 | $this->authorize->createPermission('role-permission', 'User can edit and define permissions for a role.'); 62 | $this->authorize->createPermission('menu-permission', 'User cand create, delete or modify the menu.'); 63 | 64 | // Assign Permission to role 65 | $this->authorize->addPermissionToGroup('back-office', 'admin'); 66 | $this->authorize->addPermissionToGroup('manage-user', 'admin'); 67 | $this->authorize->addPermissionToGroup('role-permission', 'admin'); 68 | $this->authorize->addPermissionToGroup('menu-permission', 'admin'); 69 | $this->authorize->addPermissionToGroup('back-office', 'member'); 70 | 71 | // Assign Role to user 72 | $this->authorize->addUserToGroup(1, 'admin'); 73 | $this->authorize->addUserToGroup(1, 'member'); 74 | $this->authorize->addUserToGroup(2, 'member'); 75 | 76 | // Assign Permission to user 77 | $this->authorize->addPermissionToUser('back-office', 1); 78 | $this->authorize->addPermissionToUser('manage-user', 1); 79 | $this->authorize->addPermissionToUser('role-permission', 1); 80 | $this->authorize->addPermissionToUser('menu-permission', 1); 81 | $this->authorize->addPermissionToUser('back-office', 2); 82 | 83 | $this->db->table('menu')->insertBatch([ 84 | [ 85 | 'parent_id' => '0', 86 | 'title' => 'Dashboard', 87 | 'icon' => 'fas fa-tachometer-alt', 88 | 'route' => 'admin', 89 | 'sequence' => '1', 90 | 'created_at' => date('Y-m-d H:i:s'), 91 | 'updated_at' => date('Y-m-d H:i:s'), 92 | ], 93 | [ 94 | 'parent_id' => '0', 95 | 'title' => 'User Management', 96 | 'icon' => 'fas fa-user', 97 | 'route' => '#', 98 | 'sequence' => '2', 99 | 'created_at' => date('Y-m-d H:i:s'), 100 | 'updated_at' => date('Y-m-d H:i:s'), 101 | ], 102 | [ 103 | 'parent_id' => '2', 104 | 'title' => 'User Profile', 105 | 'icon' => 'fas fa-user-edit', 106 | 'route' => 'admin/user/profile', 107 | 'sequence' => '3', 108 | 'created_at' => date('Y-m-d H:i:s'), 109 | 'updated_at' => date('Y-m-d H:i:s'), 110 | ], 111 | [ 112 | 'parent_id' => '2', 113 | 'title' => 'Users', 114 | 'icon' => 'fas fa-users', 115 | 'route' => 'admin/user/manage', 116 | 'sequence' => '4', 117 | 'created_at' => date('Y-m-d H:i:s'), 118 | 'updated_at' => date('Y-m-d H:i:s'), 119 | ], 120 | [ 121 | 'parent_id' => '2', 122 | 'title' => 'Permissions', 123 | 'icon' => 'fas fa-user-lock', 124 | 'route' => 'admin/permission', 125 | 'sequence' => '5', 126 | 'created_at' => date('Y-m-d H:i:s'), 127 | 'updated_at' => date('Y-m-d H:i:s'), 128 | ], 129 | [ 130 | 'parent_id' => '2', 131 | 'title' => 'Roles', 132 | 'icon' => 'fas fa-users-cog', 133 | 'route' => 'admin/role', 134 | 'sequence' => '6', 135 | 'created_at' => date('Y-m-d H:i:s'), 136 | 'updated_at' => date('Y-m-d H:i:s'), 137 | ], 138 | [ 139 | 'parent_id' => '2', 140 | 'title' => 'Menu', 141 | 'icon' => 'fas fa-stream', 142 | 'route' => 'admin/menu', 143 | 'sequence' => '7', 144 | 'created_at' => date('Y-m-d H:i:s'), 145 | 'updated_at' => date('Y-m-d H:i:s'), 146 | ], 147 | ]); 148 | 149 | $this->db->table('groups_menu')->insertBatch([ 150 | [ 151 | 'group_id' => 1, 152 | 'menu_id' => 1, 153 | ], 154 | [ 155 | 'group_id' => 1, 156 | 'menu_id' => 2, 157 | ], 158 | [ 159 | 'group_id' => 1, 160 | 'menu_id' => 3, 161 | ], 162 | [ 163 | 'group_id' => 1, 164 | 'menu_id' => 4, 165 | ], 166 | [ 167 | 'group_id' => 1, 168 | 'menu_id' => 5, 169 | ], 170 | [ 171 | 'group_id' => 1, 172 | 'menu_id' => 6, 173 | ], 174 | [ 175 | 'group_id' => 1, 176 | 'menu_id' => 7, 177 | ], 178 | [ 179 | 'group_id' => 2, 180 | 'menu_id' => 1, 181 | ], 182 | [ 183 | 'group_id' => 2, 184 | 'menu_id' => 2, 185 | ], 186 | [ 187 | 'group_id' => 2, 188 | 'menu_id' => 3, 189 | ], 190 | ]); 191 | } 192 | 193 | public function down() 194 | { 195 | // 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/Entities/Collection.php: -------------------------------------------------------------------------------- 1 | service('request')->getGet('draw'), 20 | 'recordsTotal' => $recordsTotal, 21 | 'recordsFiltered' => $recordsFiltered, 22 | 'data' => $data, 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Entities/MenuEntity.php: -------------------------------------------------------------------------------- 1 | 'boolean', 24 | ]; 25 | 26 | /** 27 | * Activate menu. 28 | * 29 | * @return $this 30 | */ 31 | public function activate() 32 | { 33 | $this->attributes['active'] = 1; 34 | 35 | return $this; 36 | } 37 | 38 | /** 39 | * Unactivate menu. 40 | * 41 | * @return $this 42 | */ 43 | public function deactivate() 44 | { 45 | $this->attributes['active'] = 0; 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * Checks to see if a menu is active. 52 | * 53 | * @return bool 54 | */ 55 | public function isActivated(): bool 56 | { 57 | return isset($this->attributes['active']) && $this->attributes['active'] == true; 58 | } 59 | 60 | /** 61 | * Check to see max value from fields sequence. 62 | * 63 | * @return int 64 | */ 65 | public function sequence(): int 66 | { 67 | return (new MenuModel())->selectMax('sequence')->get()->getRow()->sequence; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Filters/PermissionFilter.php: -------------------------------------------------------------------------------- 1 | check()) { 45 | session()->set('redirect_url', current_url()); 46 | 47 | return redirect('login'); 48 | } 49 | 50 | $authorize = Services::authorization(); 51 | $result = true; 52 | 53 | // Check each requested permission 54 | foreach ($params as $permission) { 55 | $result = $result && $authorize->hasPermission($permission, $authenticate->id()); 56 | } 57 | 58 | if (!$result) { 59 | if ($authenticate->silent()) { 60 | $redirectURL = session('redirect_url') ?? '/'; 61 | unset($_SESSION['redirect_url']); 62 | 63 | return redirect()->to($redirectURL)->with('error', lang('Auth.notEnoughPrivilege')); 64 | } else { 65 | throw PageNotFoundException::forPageNotFound(lang('Auth.notEnoughPrivilege')); 66 | } 67 | } 68 | } 69 | 70 | //-------------------------------------------------------------------- 71 | 72 | /** 73 | * Allows After filters to inspect and modify the response 74 | * object as needed. This method does not allow any way 75 | * to stop execution of other after filters, short of 76 | * throwing an Exception or Error. 77 | * 78 | * @param \CodeIgniter\HTTP\RequestInterface $request 79 | * @param \CodeIgniter\HTTP\ResponseInterface $response 80 | * 81 | * @return void 82 | */ 83 | public function after(RequestInterface $request, ResponseInterface $response, $arguements = null) 84 | { 85 | } 86 | 87 | //-------------------------------------------------------------------- 88 | } 89 | -------------------------------------------------------------------------------- /src/Filters/RoleFilter.php: -------------------------------------------------------------------------------- 1 | check()) { 45 | session()->set('redirect_url', current_url()); 46 | 47 | return redirect('login'); 48 | } 49 | 50 | $authorize = Services::authorization(); 51 | 52 | // Check each requested permission 53 | foreach ($params as $group) { 54 | if ($authorize->inGroup($group, $authenticate->id())) { 55 | return; 56 | } 57 | } 58 | 59 | if ($authenticate->silent()) { 60 | $redirectURL = session('redirect_url') ?? '/'; 61 | unset($_SESSION['redirect_url']); 62 | 63 | return redirect()->to($redirectURL)->with('error', lang('Auth.notEnoughPrivilege')); 64 | } else { 65 | throw PageNotFoundException::forPageNotFound(lang('Auth.notEnoughPrivilege')); 66 | } 67 | } 68 | 69 | //-------------------------------------------------------------------- 70 | 71 | /** 72 | * Allows After filters to inspect and modify the response 73 | * object as needed. This method does not allow any way 74 | * to stop execution of other after filters, short of 75 | * throwing an Exception or Error. 76 | * 77 | * @param \CodeIgniter\HTTP\RequestInterface $request 78 | * @param \CodeIgniter\HTTP\ResponseInterface $response 79 | * 80 | * @return void 81 | */ 82 | public function after(RequestInterface $request, ResponseInterface $response, $arguements = null) 83 | { 84 | } 85 | 86 | //-------------------------------------------------------------------- 87 | } 88 | -------------------------------------------------------------------------------- /src/Helpers/menu_helper.php: -------------------------------------------------------------------------------- 1 | parent_id == $parent_id) { 27 | $child = parse($item, $value->id); 28 | $value->children = $child ?: $child; 29 | $data[] = $value; 30 | } 31 | } 32 | // cache()->delete('menu'); 33 | return $data; 34 | } 35 | 36 | // TODO: cache the result 37 | // if (! $found = cache('menu')) { 38 | // $data = parse((new MenuModel())->where('active', 1)->orderBy('sequence', 'asc')->get()->getResultObject(), 0); 39 | // cache()->save('menu', $data, 300); 40 | // } 41 | // return $found; 42 | return parse((new GroupMenuModel())->menuHasRole(), 0); 43 | } 44 | } 45 | 46 | if (!function_exists('nestable')) { 47 | /** 48 | * Helpers for build menu. 49 | * 50 | * @return array 51 | */ 52 | function nestable() 53 | { 54 | /** 55 | * Function parse. 56 | * 57 | * @param item array 58 | * @param parent_id int 59 | * 60 | * @return array 61 | */ 62 | function nest($item, $parent_id) 63 | { 64 | $data = []; 65 | foreach ($item as $value) { 66 | if ($value->parent_id == $parent_id) { 67 | $child = nest($item, $value->id); 68 | $value->children = $child ? $child : ''; 69 | $data[] = $value; 70 | } 71 | } 72 | // cache()->delete('menu'); 73 | return $data; 74 | } 75 | 76 | // TODO: cache the result 77 | // if (! $found = cache('menu')) { 78 | // $data = parse((new MenuModel())->orderBy('sequence', 'asc')->findAll(), 0); 79 | // cache()->save('menu', $data, 300); 80 | // } 81 | // return $found; 82 | return nest((new MenuModel())->orderBy('sequence', 'asc')->get()->getResultObject(), 0); 83 | } 84 | } 85 | 86 | /** 87 | * The ugly for generate some html. 88 | * 89 | * return string hrml 90 | */ 91 | if (!function_exists('build')) { 92 | function build() 93 | { 94 | $html = ''; 95 | foreach (menu() as $parent) { 96 | $open = current_url() == base_url($parent->route) || in_array(uri_string(), array_column($parent->children, 'route')) ? 'menu-open' : ''; 97 | $active = current_url() == base_url($parent->route) || in_array(uri_string(), array_column($parent->children, 'route')) ? 'active' : ''; 98 | $link = base_url($parent->route); 99 | 100 | $html .= "