diff --git a/README.md b/README.md new file mode 100644 index 0000000..60a054c --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Dotkernel Admin Documentation diff --git a/docs/book/index.md b/docs/book/index.md deleted file mode 100644 index 2eceefd..0000000 --- a/docs/book/index.md +++ /dev/null @@ -1 +0,0 @@ -# ../../README.md diff --git a/docs/book/index.md b/docs/book/index.md new file mode 120000 index 0000000..fe84005 --- /dev/null +++ b/docs/book/index.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/docs/book/v5/introduction/file-structure.md b/docs/book/v5/introduction/file-structure.md index 77abcd3..152ab97 100644 --- a/docs/book/v5/introduction/file-structure.md +++ b/docs/book/v5/introduction/file-structure.md @@ -108,7 +108,6 @@ Other classes the `src` folder may include are `InputFilter`, `EventListener`, ` The `src` folder in each Module folder normally also contains these files: * `ConfigProvider.php` - Configuration data for the module -* `OpenAPI.php` - Detailed descriptions for each endpoint in the OpenAPI format * `RoutesDelegator.php` - Module specific route registrations ### `templates` folder for modules diff --git a/docs/book/v6/introduction/file-structure.md b/docs/book/v6/introduction/file-structure.md index c428d07..3295ddc 100644 --- a/docs/book/v6/introduction/file-structure.md +++ b/docs/book/v6/introduction/file-structure.md @@ -110,7 +110,6 @@ Other classes the `src` folder may include are `Adapter`, `Factory`, `Form`, `De The `src` folder in each Module folder normally also contains these files: * `ConfigProvider.php` - Configuration data for the module -* `OpenAPI.php` - Detailed descriptions for each endpoint in the OpenAPI format * `RoutesDelegator.php` - Module specific route registrations ### `templates` folder for modules diff --git a/docs/book/v6/tutorials/create-book-module-via-dot-maker.md b/docs/book/v6/tutorials/create-book-module-via-dot-maker.md index 4678177..48aa605 100644 --- a/docs/book/v6/tutorials/create-book-module-via-dot-maker.md +++ b/docs/book/v6/tutorials/create-book-module-via-dot-maker.md @@ -1,7 +1,7 @@ # Implementing a book module in Dotkernel Admin using DotMaker The `dotkernel/dot-maker` library can be used to programmatically generate project files and directories. -It can be added to your API installation by following the [official documentation](https://docs.dotkernel.org/dot-maker/). +It can be added to your Dotkernel Admin installation by following the [official documentation](https://docs.dotkernel.org/dot-maker/). ## Folder and files structure @@ -72,10 +72,6 @@ you can have multiple components such as event listeners, wrappers, etc. * `src/Core/src/Book/src/Repository/BookRepository.php` – a repository is a class responsible for querying and retrieving entities from the database * `src/Core/src/Book/src/ConfigProvider.php` – is a class that provides configuration for Doctrine ORM -> Note that while this tutorial covers a standalone case, the `Core` module generated by default has the same structure as the one described in the -> [Dotkernel API "Book" module](https://docs.dotkernel.org/api-documentation/v6/tutorials/create-book-module-via-dot-maker/) -> allowing use as part of the [Dotkernel Headless Platform](https://docs.dotkernel.org/headless-documentation/) - ## File creation and contents After successfully installing `dot-maker`, it can be used to generate the Book module. @@ -90,7 +86,7 @@ Type `book` when prompted to enter the module name. Next you will be prompted to add the relevant components of a module, accepting `y(es)`, `n(o)` and `Enter` (defaults to `yes`): -> Note that `dot-maker` will automatically split the files into the described `Api` and `Core` structure without a further input needed. +> Note that `dot-maker` will automatically split the files into the described `Admin` and `Core` structure without a further input needed. * `Entity and repository` (Y): will generate the `Book.php` entity and the associated `BookRepository.php`. * `Service` and `service interface` (Y): will generate the `BookService` and the `BookServiceInterface`. diff --git a/docs/book/v7/how-to/authorization.md b/docs/book/v7/how-to/authorization.md new file mode 100644 index 0000000..5ebb0a8 --- /dev/null +++ b/docs/book/v7/how-to/authorization.md @@ -0,0 +1,43 @@ +# Authorization Guards + +The packages responsible for restricting access to certain parts of the application are [dot-rbac-guard](https://github.com/dotkernel/dot-rbac-guard) and [dot-rbac](https://github.com/dotkernel/dot-rbac). +These packages work together to create an infrastructure that is customizable and diversified to manage user access to the platform by specifying the type of role the user has. + +The `authorization.global.php` file provides multiple configurations specifying multiple roles as well as the types of permissions to which these roles have access. + +```php +//example of a flat RBAC model that specifies two types of roles as well as their permission + 'roles' => [ + 'admin' => [ + 'permissions' => [ + 'authenticated', + 'edit', + 'delete', + //etc.. + ] + ], + 'user' => [ + 'permissions' => [ + 'authenticated', + //etc.. + ] + ] + ] +``` + +The `authorization-guards.global.php` file defines which permissions are required to access specific route handlers. +These permissions must first be declared in the `authorization.global.php` (dot-rbac) configuration file. + +```php +// Example configuration granting access to route handlers based on permissions. + 'rules' => [ + 'admin::admin-login-form' => [], + 'admin::admin-login' => [], + 'admin::admin-create-form' => ['authenticated'], + 'admin::admin-create' => ['authenticated'], + 'admin::admin-delete-form' => ['authenticated'], + 'admin::admin-delete' => ['authenticated'], + 'admin::admin-edit-form' => ['authenticated'], + 'admin::admin-edit' => ['authenticated'], + ] +``` diff --git a/docs/book/v7/how-to/creating-fixtures.md b/docs/book/v7/how-to/creating-fixtures.md new file mode 100644 index 0000000..6e949e3 --- /dev/null +++ b/docs/book/v7/how-to/creating-fixtures.md @@ -0,0 +1,32 @@ +# Fixtures + +> Fixtures are used to seed the database with initial values and should only be executed ONCE each, after migrating the database. + +Seeding the database is done with the help of our custom package `dotkernel/dot-data-fixtures` built on top of `doctrine/data-fixtures`. +See below on how to use our CLI command for listing and executing Doctrine data fixtures. + +## Working with fixtures + +You can find an example of a fixtures class in `src/Core/src/App/src/Fixture/AdminLoader.php`. + +To list all the available fixtures by order of execution, run: + +```shell +php ./bin/doctrine fixtures:list +``` + +To execute all fixtures, run: + +```shell +php ./bin/doctrine fixtures:execute +``` + +To execute a specific fixture, use its class name, like in this example: + +```shell +php ./bin/doctrine fixtures:execute --class=AdminLoader +``` + +Fixtures can and should be ordered to ensure database consistency. +More on ordering fixtures can be found here: +https://www.doctrine-project.org/projects/doctrine-data-fixtures/en/latest/how-to/fixture-ordering.html#fixture-ordering diff --git a/docs/book/v7/how-to/creating-migrations.md b/docs/book/v7/how-to/creating-migrations.md new file mode 100644 index 0000000..12ddb09 --- /dev/null +++ b/docs/book/v7/how-to/creating-migrations.md @@ -0,0 +1,29 @@ +# Creating migrations + +Migrations are used to create and/or edit the database structure. +To generate a new migration file, use this command: + +```shell +php ./vendor/bin/doctrine-migrations migrations:generate +``` + +It creates a PHP file like this one `src/Core/src/App/src/Migration/Version20240627134952.php` that can then be edited in the IDE. +You can add new queries in: + +- `public function up` - these are executed when the migration is run. +- `public function down` - these are optional queries that undo the above changes. + +## Example + +This example creates a new column named `test`. +Add this in `public function up`: + +```shell +$this->addSql('ALTER TABLE admin ADD test VARCHAR(255) NOT NULL'); +``` + +And its opposite in `public function down`: + +```shell +$this->addSql('ALTER TABLE admin DROP test'); +``` diff --git a/docs/book/v7/how-to/csrf.md b/docs/book/v7/how-to/csrf.md new file mode 100644 index 0000000..02376b0 --- /dev/null +++ b/docs/book/v7/how-to/csrf.md @@ -0,0 +1,73 @@ +# CSRF protection in forms + +A Cross-Site Request Forgery (CSRF) attack is a type of security vulnerability that tricks a user into performing actions on a web application in which they are authenticated, without their knowledge or consent. + +Web applications can protect users against these types of attacks by implementing CSRF tokens in their forms, which are known only to the application that generated them and must be included when submitting forms. +With each visit, a new CSRF token is added to the form, so tokens are not reusable between forms. +Missing to provide a valid CSRF token will result in a form validation error. + +## Implement CSRF protection + +Implementing CSRF protection requires three steps: + +- create a new field using [laminas/laminas-form](https://github.com/laminas/laminas-form)'s [CSRF](https://github.com/laminas/laminas-form/blob/3.21.x/src/Element/Csrf.php) element +- validate new field using [laminas/laminas-session](https://github.com/laminas/laminas-session)'s [CSRF](https://github.com/laminas/laminas-session/blob/2.22.x/src/Validator/Csrf.php) validator +- render field using [laminas/laminas-form](https://github.com/laminas/laminas-form)'s [FormElement](https://github.com/laminas/laminas-form/blob/3.21.x/src/View/Helper/FormElement.php) helper + +### Create field + +Open the form's PHP class and append the following code to the method that initializes the fields (usually `init`): + +```php +$this->add( + (new \Laminas\Form\Element\Csrf('exampleCsrf')) + ->setOptions([ + 'csrf_options' => ['timeout' => 3600, 'session' => new Container()], + ]) + ->setAttribute('required', true) +); +``` + +where `exampleCsrf` should be a suggestive name that describes the purpose of the field (example: `forgotPasswordCsrf`). + +### Validate field + +Open the InputFilter that validates the form fields and append the following code to the method that initializes the fields (usually `init`): + +```php +$this->add(new \Admin\App\InputFilter\Input\CsrfInput('exampleCsrf')); +``` + +where `exampleCsrf` must match the CSRF field's name in the form. + +> Remember to modify both occurrences in this file. + +> Make sure that you validate the form using its `isValid` method in the handler/controller where it is submitted. + +### Render field + +Open the template that renders your form and add the following code somewhere between the form's opening and closing tags: + +```text +{{ formElement(form.get('exampleCsrf')) }} +``` + +## Test the implementation + +Access your form from the browser and view its source. You should see a new hidden field, called `exampleCsrf` (or however you named it). +After filling out the form, submitting it should work as before. + +To make sure that the new CSRF field works as expected, you can inspect the form using your browser's `Developer tools` and modify its value in any way. +Submitting a filled-out form should result in a validation error: + +> This field is required and cannot be empty. + +### Timeout + +Note the `timeout` option in your PHP form's `exampleCsrf` field, with its default value set to **3600**. +This represents the value in seconds for how long the token is valid. +Submitting a form that has been rendered for longer than this value will result in a validation error: + +> Invalid CSRF. + +You can modify the value of `timeout` in each form, but the default value should work in most cases. diff --git a/docs/book/v7/how-to/dependency-injection.md b/docs/book/v7/how-to/dependency-injection.md new file mode 100644 index 0000000..8ad4ee5 --- /dev/null +++ b/docs/book/v7/how-to/dependency-injection.md @@ -0,0 +1,55 @@ +# Dependency Injection + +Dependency injection is a design pattern used in software development to implement inversion of control. +In simpler terms, it's the act of providing dependencies for an object during instantiation. + +In PHP, dependency injection can be implemented in various ways, including through constructor injection, setter injection and property injection. + +Dotkernel Admin, through its [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package, focuses only on constructor injection. + +## Usage + +Dotkernel Admin comes out of the box with the [dot-dependency-injection](https://github.com/dotkernel/dot-dependency-injection) package, which provides all the functionality for injecting dependencies into any object you want. + +`dot-dependency-injection` determines the dependencies by looking at the `#[Inject]` attribute, added to the constructor of a class. +Each dependency is specified as a separate parameter of the `#[Inject]` attribute. + +For our example we will inject `RouterInterface` and `AuthenticationServiceInterface` dependencies into `GetAccountLogoutHandler`. + +```php +use Dot\DependencyInjection\Attribute\Inject; + +class GetAccountLogoutHandler implements RequestHandlerInterface +{ + #[Inject( + RouterInterface::class, + AuthenticationServiceInterface::class, + )] + public function __construct( + protected RouterInterface $router, + protected AuthenticationServiceInterface $authenticationService, + ) { + } +} +``` + +> If your class needs the value of a specific configuration key, you can specify the path using dot notation: `config.example` + +The next step is to register the class in the `ConfigProvider` under `factories` using `Dot\DependencyInjection\Factory\AttributedServiceFactory::class`. + +```php +public function getDependencies(): array +{ + return [ + 'factories' => [ + GetAccountLogoutHandler::class => AttributedServiceFactory::class, + ], + ]; +} +``` + +That's it. +When your object is instantiated from the container, it will automatically have its dependencies resolved. + +> Dependencies injection is available to any object within Dotkernel Admin. +> For example, you can inject dependencies in a service, a handler and so on, simply by registering them in the `ConfigProvider`. diff --git a/docs/book/v7/how-to/npm_commands.md b/docs/book/v7/how-to/npm_commands.md new file mode 100644 index 0000000..460a4b5 --- /dev/null +++ b/docs/book/v7/how-to/npm_commands.md @@ -0,0 +1,22 @@ +# NPM Commands + +To install dependencies into the `node_modules` directory run this command. + +```shell +npm install +``` + +> If `npm install` fails, this could be caused by user permissions of npm. +> The recommended way to install npm is through `Node Version Manager`. + +The watch command compiles the components, then monitors the files for changes and recompiles them. + +```shell +npm run watch +``` + +After all updates are done, this command compiles the assets locally, minifies them and makes them ready for production. + +```shell +npm run prod +``` diff --git a/docs/book/v7/installation/composer.md b/docs/book/v7/installation/composer.md new file mode 100644 index 0000000..80272f4 --- /dev/null +++ b/docs/book/v7/installation/composer.md @@ -0,0 +1,73 @@ +# Composer Installation of Packages + +Composer is required to install Dotkernel Admin. You can install Composer from the [official site](https://getcomposer.org/). + +> First, make sure that you have navigated your command prompt to the folder where you copied the files in the previous step. + +## Install dependencies + +Run this command in the command prompt. + +> Use the **CLI** to ensure interactivity for proper configuration. + +```shell +composer install +``` + +You should see this text below, along with a long list of packages to be installed instead of the `[...]`. +In this example there are 171 packages, though the number can change in future updates. +You will find the packages in the `vendor` folder. + +```shell +No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information. +Loading composer repositories with package information +Updating dependencies +Lock file operations: 171 installs, 0 updates, 0 removals +[...] +Writing lock file +Installing dependencies from lock file (including require-dev) +Package operations: 171 installs, 0 updates, 0 removals +[...] +``` + +The setup script prompts for some configuration settings, for example, the lines below: + +```text +Please select which config file you wish to inject 'Laminas\Validator\ConfigProvider' into: + [0] Do not inject + [1] config/config.php + Make your selection (default is 1): +``` + +Type `0` to select `[0] Do not inject`. + +> We choose `0` because Dotkernel includes its own ConfigProvider, which already contains the prompted configurations. +> If you choose `[1] config/config.php`, an extra `ConfigProvider` will be injected. + +The next question is: + +```text +Remember this option for other packages of the same type? (y/N) +``` + +Type `y` here, and hit `enter` to complete this stage. + +## Development mode + +If you're installing the project for development, make sure you have development mode enabled by running: + +```shell +composer development-enable +``` + +You can disable the development mode by running: + +```shell +composer development-disable +``` + +You can check if you have development mode enabled by running: + +```shell +composer development-status +``` diff --git a/docs/book/v7/installation/configuration-files.md b/docs/book/v7/installation/configuration-files.md new file mode 100644 index 0000000..6237e33 --- /dev/null +++ b/docs/book/v7/installation/configuration-files.md @@ -0,0 +1,19 @@ +# Configuration Files + +## Mail + +> If you intend to send emails from your Frontend, make sure to fill in SMTP connection params. +> This will be covered in the next section. + +> **optional**: to run/create tests, duplicate `config/autoload/local.test.php.dist` as `config/autoload/local.test.php` this creates a new in-memory database that your tests will run on. + +If you want your application to send mail, add valid credentials to the following keys in `config/autoload/mail.global.php` + +Under `message_options` key: + +- `from` - email address that will send emails (required) +- `from_name` - organization name for signing sent emails (optional) + +> **Please add at least one email address in order for a contact message to reach someone** + +Also feel free to add as many CCs as you require under the `dot_mail` => `default` => `message_options` => `cc` key. diff --git a/docs/book/v7/installation/doctrine-orm.md b/docs/book/v7/installation/doctrine-orm.md new file mode 100644 index 0000000..daf9290 --- /dev/null +++ b/docs/book/v7/installation/doctrine-orm.md @@ -0,0 +1,117 @@ +# Doctrine ORM + +This step saves the database connection credentials in an Admin configuration file. +We do not cover the creation steps of the database itself. + +## Setup database + +Create a new **MariaDB**/**PostgreSQL** database and set its collation to `utf8mb4_general_ci`. + +Make sure you fill out the database credentials in `config/autoload/local.php` under `$databases['mariadb']`. +Below is the item you need to focus on: + +```php +$databases = [ + /** + * You can add more database connections to this array. + * Only one active connection is allowed at a time. + * By default, the application uses the 'mariadb' connection. + * You can switch to another connection by activating it under doctrine->connection->orm_default-->params. + */ + 'mariadb' => [ + 'host' => 'localhost', + 'dbname' => 'dotkernel', + 'user' => '', + 'password' => '', + 'port' => 3306, + 'driver' => 'pdo_mysql', + 'collation' => 'utf8mb4_general_ci', + 'table_prefix' => '', + ], + 'postgresql' => [ + 'host' => 'localhost', + 'dbname' => 'dotkernel', + 'user' => '', + 'password' => '', + 'port' => 5432, + 'driver' => 'pdo_pgsql', + 'collation' => 'utf8mb4_general_ci', + 'table_prefix' => '', + ], +]; +``` + +> You can add more database connections to this array. +> Only one active connection is allowed at a time. +> By default, the application uses the 'mariadb' connection. +> You can switch to another connection by activating it under `doctrine` -> `connection` -> `orm_default` -> `params`. + +### Creating migrations + +Create a database migration by executing the following command: + +```shell +php ./vendor/bin/doctrine-migrations diff +``` + +The new migration file will be placed in `src/Core/src/App/src/Migration/`. + +### Running migrations + +Run the database migrations by executing the following command: + +```shell +php ./vendor/bin/doctrine-migrations migrate +``` + +> If you have already run the migrations, you may get the below message: + +```text +WARNING! You have x previously executed migrations in the database that are not registered migrations. + {migration list} +Are you sure you wish to continue? (y/n) +``` + +> In this case, you should double-check to make sure the new migrations are ok to run. + +When using an empty database, you will get this confirmation message: + +```text +WARNING! You are about to execute a migration in database "" that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) +``` + +Hit `Enter` to confirm the operation. +This will run all the migrations in chronological order. +Each migration will be logged in the `migrations` table to prevent running the same migration more than once, which is often not desirable. + +If everything ran correctly, you will get this confirmation. + +```text +[OK] Successfully migrated to version: Core\App\Migration\VersionYYYYMMDDHHMMSS +``` + +### Fixtures + +Run this command to populate the admin tables with the default values: + +```shell +php ./bin/doctrine fixtures:execute +``` + +You should see our galloping horse in the command line. + +```shell +Executing Core\App\Fixture\AdminRoleLoader +Executing Core\App\Fixture\OAuthClientLoader +Executing Core\App\Fixture\OAuthScopeLoader +Executing Core\App\Fixture\UserRoleLoader +Executing Core\App\Fixture\AdminLoader +Executing Core\App\Fixture\UserLoader +Fixtures have been loaded. + .'' + ._.-.___.' (`\ + //( ( `' + '/ )\ ).__. ) + ' <' `\ ._/'\ + ` \ \ +``` diff --git a/docs/book/v7/installation/getting-started.md b/docs/book/v7/installation/getting-started.md new file mode 100644 index 0000000..931f1d8 --- /dev/null +++ b/docs/book/v7/installation/getting-started.md @@ -0,0 +1,28 @@ +# Clone the project + +## Recommended development environment + +> If you are using Windows on your machine, you can use WSL2 as a development environment. +> Read more here: [PHP-Mariadb-on-WSL2](https://www.dotkernel.com/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin/) + +Using your terminal, navigate inside the directory you want to download the project files into. +Make sure that the directory is empty before proceeding to the download process. +Once there, run the following command: + +```shell +git clone https://github.com/dotkernel/admin.git . +``` + +If everything ran correctly, you can expect to see an output like this, though the numbers may differ. + +```shell +Cloning into '.'... +remote: Enumerating objects: 6538, done. +remote: Counting objects: 100% (1652/1652), done. +remote: Compressing objects: 100% (785/785), done. +remote: Total 6538 (delta 804), reused 1417 (delta 748), pack-reused 4886 (from 1) +Receiving objects: 100% (6538/6538), 11.84 MiB | 16.52 MiB/s, done. +Resolving deltas: 100% (3359/3359), done. +``` + +You can already open the project in your preferred IDE to double-check the files were copied correctly. diff --git a/docs/book/v7/installation/installation-intro.md b/docs/book/v7/installation/installation-intro.md new file mode 100644 index 0000000..b5ddcda --- /dev/null +++ b/docs/book/v7/installation/installation-intro.md @@ -0,0 +1,11 @@ +# Introduction + +In this tutorial, we will install Dotkernel Admin from scratch. +We will focus on these tasks: + +- Highlight third-party tools required for the installation. +- Provide all the relevant commands with expected responses. +- Configure the development environment. +- Run the project. + +By the end of this tutorial you will have a fully functional Dotkernel Admin on your selected environment and can begin coding. diff --git a/docs/book/v7/installation/manage-geolite2.md b/docs/book/v7/installation/manage-geolite2.md new file mode 100644 index 0000000..b033949 --- /dev/null +++ b/docs/book/v7/installation/manage-geolite2.md @@ -0,0 +1,31 @@ +# Manage the GeoLite2 databases + +You can download/update a specific GeoLite2 database, by running the following command where `{DATABASE}` can be `asn`, `city`, `country`: + +```shell +php ./bin/cli.php geoip:synchronize -d {DATABASE} +``` + +You can download/update all GeoLite2 databases at once by running the following command: + +```shell +php ./bin/cli.php geoip:synchronize +``` + +The output should be similar to the below, displaying per row: `database identifier`: `previous build datetime` -> `current build datetime`. + +```shell +asn: n/a -> 2024-11-01 02:29:44 +city: n/a -> 2024-11-01 02:29:31 +country: n/a -> 2024-11-01 02:25:09 +``` + +> `n/a` will be replaced by the older version of the GeoLite2 databases when you run the command again. + +Get help for this command by running: + +```shell +php ./bin/cli.php help geoip:synchronize +``` + +> If you set up the synchronizer command as a cronjob, you can add the `-q|--quiet` option, and it will output data only if an error has occurred. diff --git a/docs/book/v7/installation/test-the-installation.md b/docs/book/v7/installation/test-the-installation.md new file mode 100644 index 0000000..7175169 --- /dev/null +++ b/docs/book/v7/installation/test-the-installation.md @@ -0,0 +1,43 @@ +# Running the application + +> **Do not enable dev mode in production** + +We recommend running your applications in WSL: + +- Make sure you have [WSL2](https://docs.dotkernel.org/development/v2/setup/system-requirements/) installed on your system. +- Currently, we provide a distro implementation for [AlmaLinux9](https://github.com/dotkernel/development/blob/main/wsl/README.md). +- Install the application in a virtualhost as recommended by the chosen distro. +- Set `$baseUrl` in **config/autoload/local.php** to the address of the virtualhost. +- Run the application by opening the virtualhost address in your browser. + +You should see the `Dotkernel Admin` login page. + +> If you are getting exceptions or errors regarding some missing services, try running the following command: + +```shell +sudo php ./bin/clear-config-cache.php +``` + +> If `config-cache.php` is present that config will be loaded regardless of the `ConfigAggregator::ENABLE_CACHE` in `config/autoload/mezzio.global.php` + +If you ran the fixtures, you will have an admin user in the database with the following credentials: + +- **User**: `admin` +- **Password**: `dotadmin` + +> **Production only**: Make sure you modify the default admin credentials. + +> **Development only**: `session.cookie_secure` does not work locally so make sure you modify your `local.php`, as per the following: + +```php +# other code + +return [ + # other configurations... + 'session_config' => [ + 'cookie_secure' => false, + ], +]; +``` + +> Do not change this in `local.php.dist` as well because this value should remain `true` on production. diff --git a/docs/book/v7/introduction/file-structure.md b/docs/book/v7/introduction/file-structure.md new file mode 100644 index 0000000..7f78872 --- /dev/null +++ b/docs/book/v7/introduction/file-structure.md @@ -0,0 +1,140 @@ +# File structure + +Dotkernel Admin follows the [PSR-4](https://www.php-fig.org/psr/psr-4/) standards. + +It is considered good practice to standardize the file structure of projects. + +When using Dotkernel Admin, the following structure is installed by default: + +![Dotkernel Admin File Structure!](https://docs.dotkernel.org/img/admin/v7/file-structure-dk-admin.jpg) + +## Special purpose folders + +* `.github` - Contains GitHub workflow files +* `.laminas-ci` - Contains laminas-ci workflow files + +## `bin` folder + +This folder contents are: + +* `clear-config-cache.php` - Removes the config cache file `data/cache/config-cache.php`; available only when development mode is enabled +* `cli.php` - Used to build console applications based on [laminas-cli](https://github.com/laminas/laminas-cli) +* `doctrine` - Used by the doctrine fixtures to populate the database tables + +## `config` folder + +This folder contains all application-related config files: + +* `cli-config.php` - Command line interface configuration used by migrations, fixtures, crons +* `config.php` - Registers ConfigProviders for installing packages +* `container.php` - Main service container that provides access to all registered services +* `development.config.php.dist` - Activates debug mode; gets symlinked as `development.config.php` when enabling development mode +* `migrations.php` - Configuration for database migration, like migration file location and table to save the migration log +* `pipeline.php` - Contains a list of middlewares, in the order of their execution +* `twig-cs-fixer.php` - Configuration file for Twig code style checker/fixer + +### `config/autoload` folder + +This folder contains all service-related local and global config files: + +* `app.global.php` - Configures basic app variables +* `authentication.global.php` - Defines the Admin identity +* `authorization.global.php` - Configures permissions for user roles +* `authorization-guards.global.php` - Configures access per route for user roles +* `cli.global.php` - Configures cli +* `cors.global.php` - Configures Cross-Origin Resource Sharing, like call origin, headers, cookies +* `dependencies.global.php` - Config file to set global dependencies that should be accessible by all modules +* `development.local.php.dist` - Gets symlinked as `development.local.php` when enabling development mode; activates error handlers +* `error-handling.global.php` - Configures and activates error logs +* `local.php.dist` - Local config file where you can overwrite application name and URL +* `local.test.php.dist` - Local configuration for functional tests +* `mail.global.php` - Mail configuration; e.g. sendmail vs smtp, message configuration, mail logging +* `mezzio.global.php` - Mezzio core config file +* `navigation.global.php` - Configures the top menu +* `session.global.php` - Configures the session +* `templates.global.php` - dotkernel/dot-twigrenderer config file + +## `data` folder + +This folder is a storage for project data files and service caches. +It contains these folders: + +* `cache` - Twig and Doctrine cache files +* `geoip` - Holds the GeoLite2 databases +* `lock` - Contains lock files generated by [`dotkernel/dot-cli`](https://docs.dotkernel.org/dot-cli/v3/lock-files/) + +> AVOID storing sensitive data on the repository! + +## `log` folder + +This folder stores daily log files. +When you access the application from the browser, (if not already created) a new log file gets created in the format specified in the `config/autoload/error-handling.global.php` config file under the `stream` array key. + +## `public` folder + +This folder contains all publicly available assets and serves as the entry point of the application: + +* `css` and `js` - Contains the CSS and js file(s) generated by the webpack (npm) from the assets folder +* `fonts` and `images` - Contain the font and image file(s) copied by the webpack (npm) from the assets folder +* `uploads` - a folder that normally contains admin avatar images +* `.htaccess` - server configuration file used by Apache web server; it enables the URL rewrite functionality +* `index.php` - the application's main entry point +* `robots.txt.dist` - a sample robots.txt file that allows/denies bot access to certain areas of your application; activate it by duplicating the file as `robots.txt` and comment out the lines that don't match your environment + +## `src` folder + +This folder contains a separate folder for each Module. + +These are the modules included by default: + +* `Admin` - Contains functionality for managing users with `admin` role; note these are users save in the `admin` database table +* `App` - Contains core functionality, from authentication, to rendering, to error reporting +* `Core` – Contains the shared logic and base infrastructure used across multiple modules +* `Dashboard` – Contains the structure and rendering logic of the main admin dashboard, including layout, widgets, and default admin landing page +* `Page` - Contains reusable UI components and layout elements such as dropdowns, modal popups, error displays, and tooltips for the admin interface +* `Setting` - Contains functionality for saving and reading display settings +* `User` - Contains functionality for managing users; note these are users save in the `user` database table + +### Module contents + +Each Module folder, in turn, should contain the following folders, unless they are empty: + +* `src/Handler` - Action classes +* `src/InputFilter` - Validation rules for inputs +* `src/Service` - Service classes + +The above example is just some of the folders a project may include, but they should give you an idea about the recommended structure. +Other classes the `src` folder may include are `Adapter`, `Factory`, `Form`, `Delegator` etc. + +The `src` folder in each Module folder normally also contains these files: + +* `ConfigProvider.php` - Configuration data for the module +* `RoutesDelegator.php` - Module specific route registrations + +### `templates` folder for modules + +This folder contains the template files, used, for example, to help render e-mail templates. + +> `twig` is used as Templating Engine. +> All template files have the extension `.html.twig`. + +### Core module + +The Core module is a common codebase set up to be used by the applications you added to your project. + +These are the submodules included by default: + +* `Admin` - Contains logic for the admin submodule features +* `App` - Contains shared application-level services and infrastructure utilities +* `Security` - Contains authentication, authorization, and related security mechanisms +* `Setting` - Contains configuration and application settings management +* `User` - Contains user entities, repositories, and services handling user data and logic + +Each submodule folder should contain: + +* `src/Entity` - Used by database entities +* `src/Repository` - Entity repository folder +* `ConfigProvider.php` - Configuration data for the module + +The above example is just some of the folders a project may include, but they should give you an idea about the recommended structure. +Other classes the `src` folder may include are `DBAL`, `Enum`, `Command`, `Factory` etc. diff --git a/docs/book/v7/introduction/introduction.md b/docs/book/v7/introduction/introduction.md new file mode 100644 index 0000000..e3f69c9 --- /dev/null +++ b/docs/book/v7/introduction/introduction.md @@ -0,0 +1,9 @@ +# Introduction + +Dotkernel Admin is an application (skeleton) intended for quickly setting up an administration site for your platform. +It's a fast and reliable way to manage records in your database with a simple table-based approach, and also to build reports and graphs to monitor your platform. +The many graphical components at your disposal ensure an intuitive user experience. + +> Check out our [demo](https://admin7.dotkernel.net/). +> +> Submit user `admin` and password `dotadmin` to authenticate yourself. diff --git a/docs/book/v7/introduction/packages.md b/docs/book/v7/introduction/packages.md new file mode 100644 index 0000000..e0f341f --- /dev/null +++ b/docs/book/v7/introduction/packages.md @@ -0,0 +1,27 @@ +# Packages + +* `dotkernel/dot-cache`:`^4.3` - Provides caching based on `symfony/cache` +* `dotkernel/dot-cli`:`^3.9` - Build console applications based on `laminas-cli` +* `dotkernel/dot-data-fixtures`:`^1.4` - Provides a CLI interface for listing & executing doctrine data fixtures +* `dotkernel/dot-dependency-injection`:`^1.2` - Dependency injection component using class attributes +* `dotkernel/dot-errorhandler`:`^4.0` - Logging Error Handler for Middleware Applications +* `dotkernel/dot-flashmessenger`:`^3.6` - Provides session messages between redirects +* `dotkernel/dot-geoip`:`^3.9` - Retrieve information about an IP address based on `maxmind/GeoIP2-php` +* `dotkernel/dot-helpers`:`^3.8` - Helper/Utility classes based on `mezzio/mezzio-helpers` +* `dotkernel/dot-mail`:`^5.1` - Mail component based on Symfony Mailer +* `dotkernel/dot-navigation`:`^3.5` - Allows you to easily define and parse menus inside templates, configuration based approach +* `dotkernel/dot-rbac-guard`:`^3.6` - Defines authorization guards that authorize users for accessing certain parts of an application based on various criteria +* `dotkernel/dot-router`:`^1.0` - Dotkernel component to build complex routes, based on `mezzio/mezzio-fastroute` +* `dotkernel/dot-session`:`^5.7` - Dotkernel session component extending and customizing `laminas/laminas-session` +* `dotkernel/dot-twigrenderer`:`^3.6` - Dotkernel component providing twig extensions and customizations +* `friendsofphp/proxy-manager-lts`:`^1.0` - Fork of `ocramius/proxy-manager` +* `laminas/laminas-component-installer`:`^3.5` - Composer plugin for injecting modules and configuration providers into application configuration +* `laminas/laminas-config-aggregator`:`^1.18` - Lightweight library for collecting and merging configuration from different sources +* `mezzio/mezzio`:`^3.21` - PSR-15 Middleware Microframework +* `mezzio-authentication-oauth2`:`^2.11` - Middleware for Mezzio and PSR-7 applications providing an OAuth2 server for authentication +* `mezzio/mezzio-authorization-rbac`:`^1.9` - Mezzio authorization RBAC adapter for `laminas/laminas-permissions-rbac` +* `mezzio/mezzio-cors`:`^1.14` - CORS component for Mezzio and other PSR-15 middleware runners +* `mezzio/mezzio-fastroute`:`^3.13` - FastRoute integration for Mezzio +* `ramsey/uuid`:`^4.5` - Library for generating and working with universally unique identifiers (UUIDs) +* `ramsey/uuid-doctrine`:`^2.1` - Use `ramsey/uuid` as a Doctrine field type +* `roave/psr-container-doctrine`:`^5.2` || `^6.0` - Doctrine Factories for PSR-11 Containers diff --git a/docs/book/v7/introduction/server-requirements.md b/docs/book/v7/introduction/server-requirements.md new file mode 100644 index 0000000..0304cfb --- /dev/null +++ b/docs/book/v7/introduction/server-requirements.md @@ -0,0 +1,46 @@ +# Server Requirements + +For production, we highly recommend a *nix based system. + +## Webserver + +### Apache >= 2.2 + +* mod_rewrite +* .htaccess support `(AllowOverride All)` + +> The repository includes a default `.htaccess` file in the `public` folder. + +### Nginx + +You need to convert the provided Apache related `.htaccess` file into Nginx configuration instructions. + +## PHP >= 8.2 + +Both mod_php and FCGI (FPM) are supported. + +## Required Settings and Modules & Extensions + +* memory_limit >= 128M +* upload_max_filesize and post_max_size >= 100 M (depending on your data) +* mbstring +* CLI SAPI (for Cron Jobs) +* Composer (added to $PATH) + +## RDBMS + +* Tested with MariaDB 10.7, 10.11 LTS, 11.4 LTS, and 11.8 LTS +* Tested with PostgreSQL 13 and above + +> MySQL is not supported because of missing UUID support. + +## Recommended extensions + +* `opcache` +* `pdo_mysql`, `pdo_pgsql` or `mysqli` (if using MariaDB or PostgreSQL as RDBMS) +* `dom` - if working with markup files structure (HTML, XML, etc.) +* `simplexml` - working with XML files +* `gd`, `exif` - if working with images +* `zlib`, `zip`, `bz2` - if compressing files +* `curl` (required if APIs are used) +* `sqlite3` - for tests diff --git a/docs/book/v7/security/basic-security.md b/docs/book/v7/security/basic-security.md new file mode 100644 index 0000000..d0b8813 --- /dev/null +++ b/docs/book/v7/security/basic-security.md @@ -0,0 +1,71 @@ +# Basic Security + +Dotkernel Admin provides all necessary tools to implement safe applications; however, you will need to manually make use of some of them. +This section will go over the provided tools and any steps you need to follow to use them successfully, as well as a few general considerations. + +## Form Input Validation + +To create safe forms, Dotkernel Admin makes use of [laminas/laminas-form](https://github.com/laminas/laminas-form) and [laminas/laminas-inputfilter](https://github.com/laminas/laminas-inputfilter). +All shipped forms have their inputs validated, and it is strongly recommended any custom forms added also make use of input filters to validate user input. + +## Cross-Site Request Forgery Protection + +Dotkernel Admin provides protection against CSRF attacks by using CSRF token creation and validation, available for all forms. + +All forms provided make use of CSRF token validation, but you must ensure to implement this step for any new forms you create. + +> This step is described in the [Setup CSRF](../how-to/csrf.md) tutorial. + +## Role-Based Access Control + +This project makes use of [dot-rbac-guard](https://github.com/dotkernel/dot-rbac-guard) and [dot-rbac](https://github.com/dotkernel/dot-rbac) to handle access control. + +The default modules have already been configured, but any custom functionality will require additional configuration to make sure it is protected. +Update the configuration files of both these packages whenever you add new routes or roles. + +> This step is described in the [Configure Authorizations](../how-to/authorization.md) tutorial. + +## Session and Cookie Settings + +Make sure your session cookie settings are properly set up for usage in production by reviewing the `config/autoload/session.global.php` file. +Pay extra attention to the following keys to make sure your desired values are set: + +- `session_config.cookie_httponly` +- `session_config.cookie_samesite` +- `session_config.cookie_secure` + +## Demo Credentials + +`Admin` ships with a demo admin account, with public identity and password. +**Make sure to change or remove this demo account when going live.** + +## PHP Dependencies + +Dotkernel Admin uses `composer` to handle PHP dependencies. +In time, make sure to review any common vulnerabilities and exposures for your dependencies. + +> You may also keep an eye on the Dotkernel Admin changelog for any updates relevant to your project. + +## JavaScript Dependencies + +This project uses `npm` to handle JavaScript dependencies. +Keep an eye on any vulnerabilities whenever using `npm` to install or update packages. + +> You may use the `npm audit` command to check for vulnerabilities in the current `node_modules`. + +## General Considerations + +- `*.global.php` and `*.php.dist` configuration files are visible to the VCS, make sure **not** to include sensitive data in commits. + - `*.local.php` configuration files are ignored by the VCS by default and are the recommended place for sensitive data such as API keys. +- Review `config/autoload/cors.global.php` to ensure your application is accessible by your preferred origins. +- Make sure the `development mode` is correctly set - **do not** enable `development mode` in a production environment. + - You can use the following command to check the current status: + +```shell +composer development-status +``` + +- Dotkernel Admin ships with a [Laminas Continuous Integration](https://github.com/laminas/laminas-continuous-integration-action) GitHub Action, +if you are using a public repository, consider keeping it in your custom applications to ensure code quality. + +> Read more about using [Laminas Continuous Integration](https://getlaminas.org/blog/2024-08-05-using-laminas-continuous-integration.html). diff --git a/docs/book/v7/tutorials/create-book-module-via-dot-maker.md b/docs/book/v7/tutorials/create-book-module-via-dot-maker.md new file mode 100644 index 0000000..457dab0 --- /dev/null +++ b/docs/book/v7/tutorials/create-book-module-via-dot-maker.md @@ -0,0 +1,968 @@ +# Implementing a book module in Dotkernel Admin using DotMaker + +The `dotkernel/dot-maker` library can be used to programmatically generate project files and directories. +It can be added to your Admin installation by following the [official documentation](https://docs.dotkernel.org/dot-maker/). + +## Folder and files structure + +The below files structure is what we will have at the end of this tutorial and is just an example; you can have multiple components such as event listeners, wrappers, etc. + +```markdown +. +└── src/ + ├── Book/ + │ ├── src/ + │ │ ├── Handler/ + │ │ │ ├── GetCreateBookFormHandler.php + │ │ │ ├── GetDeleteBookFormHandler.php + │ │ │ ├── GetEditBookFormHandler.php + │ │ │ ├── GetListBookHandler.php + │ │ │ ├── PostCreateBookHandler.php + │ │ │ ├── PostDeleteBookHandler.php + │ │ │ └── PostEditBookHandler.php + │ │ ├── InputFilter/ + │ │ │ ├── Input/ + │ │ │ │ └── ConfirmDeleteBookInput.php + │ │ │ ├── CreateBookInputFilter.php + │ │ │ ├── DeleteBookInputFilter.php + │ │ │ └── EditBookInputFilter.php + │ │ ├── Service/ + │ │ │ ├── BookService.php + │ │ │ └── BookServiceInterface.php + │ │ ├── ConfigProvider.php + │ │ └── RoutesDelegator.php + │ └── templates/ + │ └── book/ + │ ├── create-book-form.html.twig + │ ├── delete-book-form.html.twig + │ ├── edit-book-form.html.twig + │ └── list-book.html.twig + └── Core/ + └── src/ + └── Book/ + └── src/ + ├──Entity/ + │ └──Book.php + ├──Repository/ + │ └──BookRepository.php + └── ConfigProvider.php +``` + +* `src/Book/src/Handler/GetCreateBookFormHandler.php` – handler that reflects the GET action for the `CreateBookForm` class +* `src/Book/src/Handler/GetDeleteBookFormHandler.php` – handler that reflects the GET action for the `DeleteBookForm` class +* `src/Book/src/Handler/GetEditBookFormHandler.php` – handler that reflects the GET action for the `EditBookForm` class +* `src/Book/src/Handler/GetListBookHandler.php` – handler that reflects the GET action for a configurable list of `Book` entities +* `src/Book/src/Handler/PostCreateBookHandler.php` – handler that reflects the POST action for creating a `Book` entity +* `src/Book/src/Handler/PostDeleteBookHandler.php` – handler that reflects the POST action for deleting a `Book` entity +* `src/Book/src/Handler/PostEditBookHandler.php` – handler that reflects the POST action for editing a `Book` entity +* `src/Book/src/InputFilter/Input/*` – input filters and validator configurations +* `src/Book/src/InputFilter/CreateBookInputFilter.php` – input filters and validators +* `src/Book/src/InputFilter/EditBookInputFilter.php` – input filters and validators +* `src/Book/src/InputFilter/DeleteBookInputFilter.php` – input filters and validators +* `src/Book/src/Service/BookService.php` – is a class or component responsible for performing a specific task or providing functionality to other parts of the application +* `src/Book/src/Service/BookServiceInterface.php` – interface that reflects the publicly available methods in `BookService` +* `src/Book/src/ConfigProvider.php` – is a class that provides configuration for various aspects of the framework or application +* `src/Book/src/RoutesDelegator.php` – a routes delegator is a delegator factory responsible for configuring routing middleware based on routing configuration provided by the application +* `src/Book/templates/book/create-book-form.html.twig` – a Twig template for generating the view for the `CreateBookForm` class +* `src/Book/templates/book/delete-book-form.html.twig` – a Twig template for generating the view for the `DeleteBookForm` class +* `src/Book/templates/book/edit-book-form.html.twig` – a Twig template for generating the view for the `EditBookForm` class +* `src/Book/templates/book/list-book.html.twig` – a Twig template for generating the view for the list of `Book` entities +* `src/Core/src/Book/src/Entity/Book.php` – an entity refers to a PHP class that represents a persistent object or data structure +* `src/Core/src/Book/src/Repository/BookRepository.php` – a repository is a class responsible for querying and retrieving entities from the database +* `src/Core/src/Book/src/ConfigProvider.php` – is a class that provides configuration for Doctrine ORM + +## File creation and contents + +After successfully installing `dot-maker`, it can be used to generate the Book module. +Invoke `dot-maker` by executing `./vendor/bin/dot-maker` or via the optional script described in the documentation - `composer make`. +This will list all component types that can be created - for this tutorial, enter `module`: + +```shell +./vendor/bin/dot-maker module +``` + +Type `book` when prompted to enter the module name. + +Next you will be prompted to add the relevant components of a module, accepting `y(es)`, `n(o)` and `Enter` (defaults to `yes`): + +> Note that `dot-maker` will automatically split the files into the described `Admin` and `Core` structure without a further input needed. + +* `Entity and repository` (Y): will generate the `Book.php` entity and the associated `BookRepository.php`. +* `Service` and `service interface` (Y): will generate the `BookService` and the `BookServiceInterface`. +* `Command`, followed by `middleware`(N): not necessary for the module described in this tutorial. +* `Handler` (Y): this option is needed, and will further prompt you for the required actions. + * `Allow listing Books?` (Y): this will generate the `GetListBookHandler.php` class and the `list-book.html.twig`. + * `Allow viewing Books?` (N): not necessary for the module described in this tutorial. + * `Allow creating Books?` (Y): will generate all files used for creating `Book` entities, as follows: + * The form used for creation `CreateBookForm` as well as the input filter it uses `CreateBookInputFilter` + * The handler that fetches the form `GetCreateBookFormHandler` + * The handler for the POST action `PostCreateBookHandler` + * The template file used for the form `create-book-form.html.twig` + * `Allow deleting Books?` (Y): similar to the previous step, this step will generate multiple files: + * The form used for creation `DeleteBookForm`, the input filter it uses `DeleteBookInputFilter` as well as a singular Input class it uses - `ConfirmDeleteBookInput` + * The handler that fetches the form `GetDeleteBookFormHandler` + * The handler for the POST action `PostDeleteBookHandler` + * The template file used for the form `delete-book-form.html.twig` + * `Allow editing Books?` (Y): as the previous two cases, multiple files are generated on this step as well: + * The form used for creation `EditBookForm` and the input filter it uses `EditBookInputFilter` + * The handler that fetches the form `GetEditBookFormHandler` + * The handler for the POST action `PostEditBookHandler` + * The template file used for the form `edit-book-form.html.twig` +* Following this step, `dot-maker` will automatically generate the `ConfigProvider.php` classes for both the `Admin` and `Core` namespaces, +as well as the `RoutesDelegator` class containing all the relevant routes. + +You will then be instructed to: + +* Register the `ConfigProvider` classes by adding `Admin\Book\ConfigProvider::class` and `Core\Book\ConfigProvider::class` to `config/config.php` +* Register the new `Book` namespace by adding `"Admin\\Book\\": "src/Book/src/"` and `"Core\\Book\\": "src/Core/src/Book/src/"` to `composer.json` under the `autoload.psr-4` key. + * After registering the namespace, run the following command to regenerate the autoloaded files, as notified by `dot-maker`: + +```shell +composer dump +``` + +* `dot-maker` will by default prompt you to generate the migrations for the new entity, but for this tutorial we will run this after updating the generated entity. + +The next step is filling in the required logic for the proposed flow of this module. +While `dot-maker` does also include common logic in the relevant files, the tutorial adds custom functionality. +As such, the following section will go over the files that require changes. + +* `src/Book/src/Handler/GetListBookHandler.php` + +The overall class structure is fully generated, but for this tutorial you will need to send the `indentifier` key to the template, as shown below: + +```php +return new HtmlResponse( + $this->template->render('book::book-list', [ + 'pagination' => $this->bookService->getBooks($request->getQueryParams()), + 'identifier' => SettingIdentifierEnum::IdentifierTableUserListSelectedColumns->value, + ]) +); +``` + +* `src/Core/src/App/src/Message.php` + +The generated `PostCreateBookHandler`, `PostEditBookHandler` and `PostDeleteBookHandler` classes will by default make use of the `Message::BOOK_CREATED`, `Message::BOOK_UPDATED` and `Message::BOOK_DELETED` constants which you will have to manually add: + +```php +public const BOOK_CREATED = 'Book created successfully.'; +public const BOOK_UPDATED = 'Book updated successfully.'; +public const BOOK_DELETED = 'Book deleted successfully.'; +``` + +* `src/Core/src/Book/src/Entity/Book.php` + +To keep things simple in this tutorial, our book will have three properties: `name`, `author` and `releaseDate`. +Add the three properties and their getters and setters, while making sure to update the generated constructor method. + +```php +setName($name); + $this->setAuthor($author); + $this->setReleaseDate($releaseDate); + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getAuthor(): string + { + return $this->author; + } + + public function setAuthor(string $author): self + { + $this->author = $author; + + return $this; + } + + public function getReleaseDate(): DateTimeImmutable + { + return $this->releaseDate; + } + + public function setReleaseDate(DateTimeImmutable $releaseDate): self + { + $this->releaseDate = $releaseDate; + + return $this; + } + + public function getArrayCopy(): array + { + return [ + 'uuid' => $this->getUuid()->toString(), + 'name' => $this->getName(), + 'author' => $this->getAuthor(), + 'releaseDate' => $this->getReleaseDate(), + ]; + } +} + +``` + +The `BookService` class will require minor modifications for the `getBooks()` and `saveBook()` methods, to add the custom properties added in the previous step. +The class should look like the following after updating the methods. + +* `src/Book/src/Service/BookService.php` + +```php +bookRepository; + } + + public function deleteBook( + Book $book, + ): void { + $this->bookRepository->deleteResource($book); + } + + /** + * @param array $params + */ + public function getBooks( + array $params, + ): array { + $filters = $params['filters'] ?? []; + $params = Paginator::getParams($params, 'book.created'); + + $sortableColumns = [ + 'book.name', + 'book.author', + 'book.releaseDate', + 'book.created', + 'book.updated', + ]; + if (! in_array($params['sort'], $sortableColumns, true)) { + $params['sort'] = 'book.created'; + } + + $paginator = new DoctrinePaginator($this->bookRepository->getBooks($params, $filters)->getQuery()); + + return Paginator::wrapper($paginator, $params, $filters); + } + + /** + * @param array $data + * @throws \DateMalformedStringException + */ + public function saveBook( + array $data, + ?Book $book = null, + ): Book { + if (! $book instanceof Book) { + $book = new Book( + $data['name'], + $data['author'], + new DateTimeImmutable($data['releaseDate']) + ); + } else { + if (array_key_exists('name', $data) && $data['name'] !== null) { + $book->setName($data['name']); + } + + if (array_key_exists('author', $data) && $data['author'] !== null) { + $book->setAuthor($data['author']); + } + + if (array_key_exists('releaseDate', $data) && $data['releaseDate'] !== null) { + $book->setReleaseDate(new DateTimeImmutable($data['releaseDate'])); + } + } + + $this->bookRepository->saveResource($book); + + return $book; + } + + /** + * @throws NotFoundException + */ + public function findBook( + string $uuid, + ): Book { + $book = $this->bookRepository->find($uuid); + if (! $book instanceof Book) { + throw new NotFoundException(Message::resourceNotFound('Book')); + } + + return $book; + } +} + +``` + +When creating a book, we will need some validators, so we will create a form and the input filter that will be used to validate the data received in the request. + +* `src/Book/src/Form/CreateBookForm.php` + +The default `Csrf` and `Submit` Inputs will be automatically added to the `CreateBookForm.php` class that `dot-maker` will create for you. +For this tutorial, you will have to add the custom inputs, by copying the following code in the `init` function of `CreateBookForm`: + +```php +$this->add( + (new Text('name')) + ->setLabel('Name') + ->setAttribute('required', true) +)->add( + (new Text('author')) + ->setLabel('Author') + ->setAttribute('required', true) +)->add( + (new Date('releaseDate')) + ->setLabel('Release Date') + ->setAttribute('required', true) +); +``` + +* `src/Book/src/Form/EditBookForm.php` + +A similar sequence is used for the `init` function of `EditBookForm`, with the `required` attributes removed, as leaving the inputs empty is allowed for keeping the original data: + +```php +$this->add( + (new Text('name')) + ->setLabel('Name') +)->add( + (new Text('author')) + ->setLabel('Author') +)->add( + (new Date('releaseDate')) + ->setLabel('Release Date') +); +``` + +By creating a `module` with `dot-maker`, separate inputs will not be created. +However, you can still generate them as using these steps: + +* Run the following to start adding `Input` classes: + +```shell +./vendor/bin/dot-maker input +``` + +* When prompted, enter the names `Author`, `Name` and `ReleaseDate` one by one to generate the classes. +* The resulting `AuthorInput.php`, `NameInput.php` and `ReleaseDateInput.php` classes require no further changes for the tutorial use case. + +The module creation process has generated the parent input filters `CreateBookInputFilter.php` and `EditBookInputFilter.php` containing only the default `CsrfInput`. +Now we add all the inputs together in the parent input filters' `init` functions, as below: + +* `src/Book/src/InputFilter/CreateBookInputFilter.php` and ``src/Book/src/InputFilter/EditBookInputFilter.php`` + +```php +$this->add(new NameInput('name')) + ->add(new AuthorInput('author')) + ->add(new ReleaseDateInput('releaseDate')); +``` + +We create separate `Input` files to demonstrate their reusability and obtain a clean `InputFilter`s, but you could have all the inputs created directly in the `InputFilter` like this: + +> Note that `dot-maker` will not generate inputs in the `init` method, so the following are to be added by hand before the default `CsrfInput`, **if** going for this approach. + +`CreateBookInputFilter` + +```php +$nameInput = new Input('name'); +$nameInput->setRequired(true); + +$nameInput->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + +$nameInput->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true); + +$this->add($nameInput); + +$authorInput = new Input('author'); +$authorInput->setRequired(true); + +$authorInput->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + +$authorInput->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true); + +$this->add($authorInput); + +$releaseDateInput = new Input('releaseDate'); +$releaseDateInput->setRequired(true); + +$releaseDateInput->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + +$releaseDateInput->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true); + +$this->add($releaseDateInput); +``` + +`EditBookInputFilter` + +```php +$nameInput = new Input('name'); +$nameInput->setRequired(false); + +$nameInput->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + +$nameInput->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true); + +$this->add($nameInput); + +$authorInput = new Input('author'); +$authorInput->setRequired(false); + +$authorInput->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + +$authorInput->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true); + +$this->add($authorInput); + +$releaseDateInput = new Input('releaseDate'); +$releaseDateInput->setRequired(false); + +$releaseDateInput->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + +$releaseDateInput->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true); + +$this->add($releaseDateInput); +``` + +* `src/App/assets/js/components/_book.js` + +As the listing pages make use of JavaScript, you will need to manually create your module specific `_book.js` file and register it in `webpack.config.js` for building. + +You may copy this sample `_book.js` file to the `src/App/assets/js/components/` directory: + +```js +$(document).ready(() => { + const request = async(url, options = {}) => { + try { + const response = await fetch(url, options); + const body = await response.text(); + if (! response.ok) { + throw { + data: body, + } + } + return body; + } catch (error) { + throw { + data: error.data, + } + } + } + + $("#add-book-modal").on('show.bs.modal', function () { + const modal = $(this); + request(modal.data('add-url'), { + method: 'GET' + }).then(data => { + modal.find('.modal-dialog').html(data); + }).catch(error => { + console.error(error); + location.reload(); + }); + }).on('hidden.bs.modal', function () { + const modal = $(this); + modal.find('.modal-dialog').find('.modal-body').html('Loading...'); + }); + + $("#edit-book-modal").on('show.bs.modal', function () { + const selectedElement = $('.ui-checkbox:checked'); + if (selectedElement.length !== 1) { + return; + } + + const modal = $(this); + request(selectedElement.data('edit-url'), { + method: 'GET' + }).then(data => { + modal.find('.modal-dialog').html(data); + }).catch(error => { + console.error(error); + location.reload(); + }); + }).on('hidden.bs.modal', function () { + const modal = $(this); + modal.find('.modal-dialog').find('.modal-body').html('Loading...'); + }); + + $("#delete-book-modal").on('show.bs.modal', function () { + const selectedElement = $('.ui-checkbox:checked'); + if (selectedElement.length !== 1) { + return; + } + + const modal = $(this); + request(selectedElement.data('delete-url'), { + method: 'GET' + }).then(data => { + modal.find('.modal-dialog').html(data); + }).catch(error => { + console.error(error); + location.reload(); + }); + }).on('hidden.bs.modal', function () { + const modal = $(this); + modal.find('.modal-dialog').find('.modal-body').html('Loading...'); + }); + + $(document).on("submit", "#book-form", (event) => { + event.preventDefault(); + + const form = event.target; + if (! form.checkValidity()) { + event.stopPropagation(); + form.classList.add('was-validated'); + return; + } + + const modal = $(form.closest('.modal')); + request(form.getAttribute('action'), { + method: 'POST', + body: new FormData(form), + }).then(() => { + location.reload(); + }).catch(error => { + modal.find('.modal-dialog').html(error.data); + }); + }); + + $(document).on("submit", "#delete-book-form", (event) => { + event.preventDefault(); + + const form = event.target; + if (! form.checkValidity()) { + event.stopPropagation(); + form.classList.add('was-validated'); + return; + } + + const modal = $(form.closest('.modal')); + request(form.getAttribute('action'), { + method: 'POST', + body: new FormData(form), + }).then(() => { + location.reload(); + }).catch(error => { + modal.find('.modal-dialog').html(error.data); + }); + }); +}); +``` + +Next you have to register the file in the `entries` array of `webpack.config.js` by adding the following key: + +```js +book: [ + './App/assets/js/components/_book.js' +] +``` + +To make use of the newly added scripts, make sure to build your assets by running the command: + +```shell +npm run prod +``` + +* `src/Book/templates/book/*` + +The next step is creating the page structures in the `.twig` files `dot-maker` automatically generated for you. + +For this tutorial you may copy the following default page layout in the `list-book.html.twig`: + +```html +{% from '@partial/macros.html.twig' import sortableColumn %} + +{% extends '@layout/default.html.twig' %} + +{% block title %}Manage books{% endblock %} + +{% block content %} +
+

Manage books

+
+
+
+
+ + + + +
+ + + +
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + {% for book in pagination.items %} + + + + + + + + + {% endfor %} + + + {% if pagination.isOutOfBounds %} + + {% endif %} +
+
+
+
+ {{ include('@partial/pagination.html.twig', {pagination: pagination, path: 'book::list-book'}, false) }} +
+
+
+ + + + + + +
+{% endblock %} + +{% block javascript %} +{{ parent() }} + + + +{% endblock %} +``` + +To add books, a modal must be generated based on the `CreateBookForm.php` class. +You may copy the following structure in `create-book-form.html.twig`: + +```html +{% from '@partial/macros.html.twig' import inputElement, submitElement %} + + +``` + +For the "edit" action, use the following modal in the `edit-book-form.html.twig`: + +```html +{% from '@partial/macros.html.twig' import inputElement, submitElement %} + + +``` + +Add the following structure to the `delete-book-form.html.twig` file: + +```html + +``` + +* `/config/autoload/navigation.global.php` + +Lastly, link the new module to the admin side-menu by adding the following array to `navigation.global.php`, under the `dot_navigation.containers.main_menu.options.items` key: + +```php +[ + 'options' => [ + 'label' => 'Book', + 'route' => [ + 'route_name' => 'book::list-book', + ], + 'icon' => 'c-blue-500 fa fa-book', + ], +], +``` + +## Migrations + +All changes are done, so at this point the migration file can be generated to create the associated table for the `Book` entity. + +> You can check the mapping files by running: + +```shell +php ./bin/doctrine orm:validate-schema +``` + +> Generate the migration files by running: + +```shell +php ./vendor/bin/doctrine-migrations diff +``` + +This will check for differences between your entities and database structure and create migration files if necessary, in `src/Core/src/App/src/Migration`. + +To execute the migrations, run: + +```shell +php ./vendor/bin/doctrine-migrations migrate +``` + +## Update the authorization file + +We need to configure access to the newly created endpoints. +Open `config/autoload/authorization-guards.global.php` and append the below routes to the `guards.options.rules` key: + +```php +'book::create-book-form' => ['authenticated'], +'book::create-book' => ['authenticated'], +'book::list-book' => ['authenticated'], +``` + +> Make sure you read and understand the `rbac` [documentation](https://docs.dotkernel.org/dot-rbac-guard/v4/configuration/). + +## Checking routes + +The module should now be accessible via the `Book` section of the `Admin` main menu, linking to the newly created `/list-book` route. + +New book entities can be added via the new "Create book" modal accessible from the `+` button on the management page. + +Once selected with the checkbox, existing entries can be edited via the `-` button , or deleted via the "trash" icon. diff --git a/docs/book/v7/upgrading/UPGRADE-7.0.md b/docs/book/v7/upgrading/UPGRADE-7.0.md new file mode 100644 index 0000000..ecc174a --- /dev/null +++ b/docs/book/v7/upgrading/UPGRADE-7.0.md @@ -0,0 +1,8 @@ +# Upgrading from 6.x to 7.0 + +> You can find a complete list in [Changelog](https://github.com/dotkernel/admin/blob/7.0/CHANGELOG.md) + +* Bumped dependencies https://github.com/dotkernel/admin/pull/401 +* Core Sync and update codebase https://github.com/dotkernel/admin/pull/403 +* Updated readme, oss https://github.com/dotkernel/admin/pull/397 +* Core sync https://github.com/dotkernel/admin/pull/398 diff --git a/docs/book/v7/upgrading/upgrading.md b/docs/book/v7/upgrading/upgrading.md new file mode 100644 index 0000000..23901af --- /dev/null +++ b/docs/book/v7/upgrading/upgrading.md @@ -0,0 +1,19 @@ +# Upgrades + +Dotkernel Admin does not provide an automatic upgrade path. +Instead, the recommended procedure is to manually implement each modification listed in [releases](https://github.com/dotkernel/admin/releases). +Additionally, release info can also be accessed as an [RSS](https://github.com/dotkernel/admin/releases.atom) feed. + +## Upgrade procedure + +Once you clone Dotkernel Admin, you will find a [CHANGELOG.md](https://github.com/dotkernel/admin/blob/7.0/CHANGELOG.md) file in the root of the project. +This file contains a list of already implemented features in reverse chronological order. +You can use this file to track the version of Dotkernel Admin. + +For each new release you need to implement the modifications from its pull requests in your project. +It is recommended to copy the release info into your project's CHANGELOG.md file. +This allows you to track your Admin's version and keep your project up to date with future releases. + +## Version to version upgrading + +Starting from [version 6.2](UPGRADE-7.0.md) the upgrading procedure is detailed version to version. diff --git a/mkdocs.yml b/mkdocs.yml index 3a1db44..1efa0d0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,12 +2,41 @@ docs_dir: docs/book site_dir: docs/html extra: project: Admin - current_version: v6 + current_version: v7 versions: - - v5 + - v7 - v6 + - v5 nav: - Home: index.md + - v7: + - Introduction: v7/introduction/introduction.md + - Overview: + - "Server Requirements": v7/introduction/server-requirements.md + - "File Structure": v7/introduction/file-structure.md + - "Packages": v7/introduction/packages.md + - Installation: + - "Introduction": v7/installation/installation-intro.md + - "Getting Started": v7/installation/getting-started.md + - "Composer": v7/installation/composer.md + - "Configuration Files": v7/installation/configuration-files.md + - "Doctrine ORM": v7/installation/doctrine-orm.md + - "Manage GeoLite2": v7/installation/manage-geolite2.md + - "Test the Installation": v7/installation/test-the-installation.md + - Upgrading: + - "Upgrade procedure": v7/upgrading/upgrading.md + - "Upgrading 6.x to 7.0": v7/upgrading/UPGRADE-7.0.md + - How to: + - "Create Database Migrations": v7/how-to/creating-migrations.md + - "Create Database Fixtures": v7/how-to/creating-fixtures.md + - "Configure Authorizations": v7/how-to/authorization.md + - "Use NPM Commands": v7/how-to/npm_commands.md + - "Inject Dependencies": v7/how-to/dependency-injection.md + - "Set Up CSRF": v7/how-to/csrf.md + - Security: + - "Basic Security": v7/security/basic-security.md + - Tutorials: + - "Creating a book module using DotMaker": v7/tutorials/create-book-module-via-dot-maker.md - v6: - Introduction: v6/introduction/introduction.md - Overview: