From 66f91bce27a4156deff1a9f368b360b75193fc6f Mon Sep 17 00:00:00 2001 From: olaru Date: Tue, 21 Jul 2026 11:46:50 +0300 Subject: [PATCH 1/4] Re-check H1,H2,H3 order --- src/App/templates/app/index.html.twig | 2 +- src/App/templates/error/404.html.twig | 3 +- src/App/templates/error/error.html.twig | 3 +- src/App/templates/partial/meta.html.twig | 2 +- .../templates/page/author-resource.html.twig | 2 +- ...e-for-a-mezzio-based-application.html.twig | 2 +- ...rds-setup-in-php-ide-zend-studio.html.twig | 2 +- ...egotiation-in-dotkernel-rest-api.html.twig | 6 +- .../dotkernel-api-1-0-0-released.html.twig | 8 +- ...el-api-client-side-authorization.html.twig | 4 +- ...el-api-server-side-authorization.html.twig | 8 +- ...nel-api-versus-laminas-api-tools.html.twig | 2 +- ...orting-endpoint-in-dotkernel-api.html.twig | 10 +- ...ement-mailchimp-in-dotkernel-api.html.twig | 2 +- ...-implementation-in-dotkernel-api.html.twig | 8 +- ...mplementation-to-zend-expressive.html.twig | 12 +- ...upport-in-your-dotkernel-project.html.twig | 4 +- ...n-dotkernel-using-zend-framework.html.twig | 4 +- ...y-in-laminas-mezzio-applications.html.twig | 2 +- ...obile-devices-in-dotkernel-1-6-0.html.twig | 2 +- ...ctrine-cache-using-symfony-cache.html.twig | 14 +-- ...enum-implementation-in-dotkernel.html.twig | 8 +- .../dotkernel-1-5-0-released.html.twig | 14 +-- .../dotkernel-1-8-0-lts-released.html.twig | 22 ++-- ...hoice-for-your-presentation-site.html.twig | 10 +- ...erved-variable-names-for-caching.html.twig | 12 +- .../dotkernel-template-engine.html.twig | 4 +- .../dotkernel-version-1-0-in-action.html.twig | 2 +- ...in-zend-expressive-and-dotkernel.html.twig | 2 +- ...of-zend-framework-1-pear-channel.html.twig | 2 +- ...-features-in-zend-framework-1-12.html.twig | 6 +- ...-with-symfony-mailer-in-dot-mail.html.twig | 2 +- ...ernel-with-composer-dependencies.html.twig | 12 +- .../using-utf8-charset-in-dotkernel.html.twig | 6 +- ...sole-implementation-in-dotkernel.html.twig | 4 +- .../dotkernel3/dotkernel-admin-v4.html.twig | 14 +-- ...tkernel-admin-version-3-launched.html.twig | 4 +- ...-api-architecture-and-components.html.twig | 24 ++-- ...rnel-frontend-version-3-launched.html.twig | 4 +- ...rnel3-stable-release-version-1-0.html.twig | 10 +- ...platform-the-whats-hows-and-whys.html.twig | 2 +- ...-time-password-totp-in-dotkernel.html.twig | 2 +- ...p-in-dotkernel-using-mezzio-cors.html.twig | 8 +- ...e-migrations-and-how-to-use-them.html.twig | 12 +- ...ne-cache-in-mezzio-and-dotkernel.html.twig | 4 +- ...in-an-generic-laminas-mezzio-app.html.twig | 2 +- ...s-in-dotkernel-api-using-dot-cli.html.twig | 2 +- ...ith-dot-cli-based-on-laminas-cli.html.twig | 8 +- ...documentation-in-dotkernel-api-3.html.twig | 16 +-- ...e-urlgenerator-work-in-fastroute.html.twig | 4 +- ...is-cross-origin-token-redemption.html.twig | 6 +- ...p-html-css-javascript-editor-ide.html.twig | 4 +- ...-lgpl-in-practice-dotkernel-case.html.twig | 8 +- ...iant-handlers-in-dotkernel-light.html.twig | 2 +- ...-doctrine-data-fixtures-vs-phinx.html.twig | 6 +- ...ithmetic-why-is-int-0-7-0-1-10-7.html.twig | 8 +- .../mezzio-app-development-in-wsl2.html.twig | 10 +- ...t-development-staging-production.html.twig | 6 +- ...-php-7-express-in-zend-studio-13.html.twig | 10 +- ...lling-pear-packages-with-php-7-2.html.twig | 6 +- ...ntl-php-extension-problem-solved.html.twig | 4 +- src/Blog/templates/page/blog.html.twig | 2 +- src/Blog/templates/page/categories.html.twig | 2 +- .../page/category-resource.html.twig | 2 +- src/Blog/templates/page/contact.html.twig | 6 +- ...dotkernel-packages-oss-lifecycle.html.twig | 106 +++++++++--------- 66 files changed, 255 insertions(+), 257 deletions(-) diff --git a/src/App/templates/app/index.html.twig b/src/App/templates/app/index.html.twig index 7555c080..c4167642 100644 --- a/src/App/templates/app/index.html.twig +++ b/src/App/templates/app/index.html.twig @@ -312,7 +312,7 @@ {% for post in posts %} -

{{ post.title }}

+

{{ post.title }}

{{ post.excerpt }}

Read more →
diff --git a/src/App/templates/error/404.html.twig b/src/App/templates/error/404.html.twig index 6711a55c..2c106934 100644 --- a/src/App/templates/error/404.html.twig +++ b/src/App/templates/error/404.html.twig @@ -6,8 +6,7 @@ {% block content %}
-

Oops!

-

This is awkward.

+ Oops! This is awkward.

404

You are looking for something that doesn't exist or may have moved. Check out one of the links on this page diff --git a/src/App/templates/error/error.html.twig b/src/App/templates/error/error.html.twig index 6b649930..f5bb68e2 100644 --- a/src/App/templates/error/error.html.twig +++ b/src/App/templates/error/error.html.twig @@ -6,8 +6,7 @@ {% block content %}

-

Oops!

-

This is awkward.

+ Oops! This is awkward.

{{ status }}

{{ reason }}

diff --git a/src/App/templates/partial/meta.html.twig b/src/App/templates/partial/meta.html.twig index c1f780cf..ad96c49f 100644 --- a/src/App/templates/partial/meta.html.twig +++ b/src/App/templates/partial/meta.html.twig @@ -9,7 +9,7 @@ - + diff --git a/src/Blog/templates/page/author-resource.html.twig b/src/Blog/templates/page/author-resource.html.twig index 943f16f2..5fdf2994 100644 --- a/src/Blog/templates/page/author-resource.html.twig +++ b/src/Blog/templates/page/author-resource.html.twig @@ -42,7 +42,7 @@ {{ post.category.name }} -

{{ post.title }}

+

{{ post.title }}

{{ post.excerpt|length > 200 ? post.excerpt|slice(0, 200)|trim ~ '…' : post.excerpt }}

{{ post.postDate|date('M d, Y') }} Read more → diff --git a/src/Blog/templates/page/blog-resource/architecture/request-lifecycle-for-a-mezzio-based-application.html.twig b/src/Blog/templates/page/blog-resource/architecture/request-lifecycle-for-a-mezzio-based-application.html.twig index 4bca20b3..5f10f21e 100644 --- a/src/Blog/templates/page/blog-resource/architecture/request-lifecycle-for-a-mezzio-based-application.html.twig +++ b/src/Blog/templates/page/blog-resource/architecture/request-lifecycle-for-a-mezzio-based-application.html.twig @@ -30,7 +30,7 @@
-

Seamlessly Interconnected Middleware for Enterprise-Level Solutions

+

Seamlessly Interconnected Middleware for Enterprise-Level Solutions

The request lifecycle is the sequence of steps that happen from the moment a user makes an HTTP request until the server sends back a response.

diff --git a/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig b/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig index 8d33c0d3..0ad09cf2 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig @@ -62,7 +62,7 @@ cache
Name: bugtraq:url
 Propriety: http://www.dotkernel.net/view.php?id=%BUGID%
For above Proprieties , apply only to project folder, NOT recursive -

Final step( below instructions are good only for svn:keywords )

+

Final step( below instructions are good only for svn:keywords )

  Check the Apply property recursively to: diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig index b084bb9d..06c42391 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig @@ -30,7 +30,7 @@
-

+

Content negotiation is an important aspect of RESTful APIs to make it possible for diverse systems to work seamlessly together. It's based on enabling clients and servers to agree on the format and language of data they exchange.

@@ -75,7 +75,7 @@
  • or resource URI patterns.
  • -

    HTTP Request Headers

    +

    HTTP Request Headers

    The HTTP request headers Content-Type and Accept are used to determine the data format that will be sent in the request and the response. There are several types to choose from. Here are some examples:

    @@ -99,7 +99,7 @@ Accept: application/json

    If the Accept header is not present, the server gets to decide the format of the response.

    -

    Content Negotiation using URL Patterns

    +

    Content Negotiation using URL Patterns

    Below are a couple of ways to communicate a preferred data format.

    diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig index 45d0194a..00e82b87 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig @@ -33,12 +33,12 @@

    Dotkernel API has come a long way since this post was created. Check out the newest version of Dotkernel API to stay up to date with the latest functional and security features.

    -

    DotKernel API 1.0.0 was just released!

    +

    DotKernel API 1.0.0 was just released!

     

    -
    What is DotKernel API?
    +

    What is DotKernel API?

    It is a Zend Expressive 3 application aiming to help developers quickly and efficiently develop an API.

     

    -
    How does it work?
    +

    How does it work?

    Under the hood it uses the following libraries:

     

    -
    What does it offer?
    +

    What does it offer?

    Out-of-the-box, DotKernel API provides the following features:

    • Secure authentication via OAuth2
    • diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig index ff4980c9..799b1b29 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig @@ -32,7 +32,7 @@
      This article covers the basic authorization of a Client application which use a backend built using DotKernel API -

      Authorization Request

      +

      Authorization Request

      Client application users send a POST request to the backend containing the following JSON object:
       {
      @@ -45,7 +45,7 @@ Client application users send a POST request to the backend con
       }
       
      -

      Authorization Response

      +

      Authorization Response

      If the credentials are correct, the API will return a JSON object containing the authentication data:
       {
      diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig
      index 96657188..8044adaf 100644
      --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig
      +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig
      @@ -31,7 +31,7 @@
                   
      This article covers the basic authorization of a Server Side application  built using DotKernel API -

      Protecting an endpoint

      +

      Protecting an endpoint

      • no-auth: the resource can be accessed without the need of authentication/authorization
      • authentication: the resource can be accessed only by authenticated users
      • @@ -67,20 +67,20 @@ Under the key roles you can define role inheritance. In the ab Of course, this setup is just a model, you should not use it in live projects because guests will end up having the same rights as admins. Under the key permissions you can define which routes are accessible to a role. In the above example, a member has access to the routes named avatar, users and user. -

        1. No-auth endpoints:

        +

        1. No-auth endpoints:

        These endpoints can be accessed without authentication/authorization. Examples could be: login, register, contact etc... Creating a route for such an endpoint will use only the handler(s) responsible for returning the content:
         $app->get('/users', UserHandler::class, 'users');
         
        -

        2. Endpoints requiring Authentication:

        +

        2. Endpoints requiring Authentication:

        These endpoints can be accessed only if a valid Bearer token is present in the request headers. Else, the API will return a **401 Unauthorized** response. Creating a route for such an endpoint will have a structure similar to the following example:
         $app->get('/users', , 'users');
         
        -

        3. Endpoints requiring Authorization:

        +

        3. Endpoints requiring Authorization:

        These endpoints can be accessed only if a valid Bearer token is present in the request headers. Else, the API will return a **403 Forbidden** response. Creating a route for such an endpoint will have a structure similar to the following example:
        diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig
        index 14970f0c..2d252c25 100644
        --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig
        +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig
        @@ -37,7 +37,7 @@
         
         
        API Tools (formerly Apigility)Dotkernel API
        URLapi-toolsDotkernel API
        First Release20122018
        PHP Version<= 8.2PHP Version
        ArchitectureMVC, Event DrivenMiddleware
        OSS LifecycleArchivedOSS Lifecycle
        StyleREST, RPCREST
        VersioningYesDeprecations (API Evolution) *
        DocumentationSwagger (Automated)Postman (Manual), OpenAPI 3.0 (Swagger)
        Content-NegotiationCustomCustom
        LicenseBSD-3MIT
        Default DB Layerlaminas-dbdoctrine-orm 3.x
        AuthorizationACLRBAC-guard
        AuthenticationHTTP Basic/Digest OAuth2.0OAuth2.0
        CI/CDYesYes
        Unit TestsYesYes
        Code (Endpoint) GeneratorYesdot-maker
        PSRPSR-7PSR-7, PSR-15
        -

        Note

        +

        Note

        • Versioning is replaced by Deprecations, using evolution strategy
        diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.html.twig index c366a864..ccae9728 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.html.twig @@ -30,13 +30,13 @@
        -

        +

        Dotkernel API has received a lot of love from our developers, with regular updates to the platform for years. We use Dotkernel API in our projects, so any bugs and issues are addressed as soon as they are found. Still, it's not unlikely that some hidden issues remain in fringe use cases that we simply haven't explored. The occurrence of bugs increases when the API is used in a complex frontend project.

        Fatal errors are easily found in the API logs, but it's another matter altogether to deal with incorrect data processing that doesn't generate errors in the frontend that interfaces with the API. The error reporting endpoint was designed to allow the frontend developers of your API to report any bugs they encounter in a secure way that is fully under your control.

        -

        Example case usage

        +

        Example case usage

        • Frontend developed in Angular.
        • @@ -47,7 +47,7 @@

          Error reporting is done by sending a POST request to the /error-report endpoint, together with a token in the header. In the sections below we will detail how to configure error reporting in your API and how the endpoint is used by the frontend developers.

          -

          Generating a token and adding it to your API config

          +

          Generating a token and adding it to your API config

          First you need to generate a token for your request. This is done by using the below command.

          @@ -62,7 +62,7 @@ ] ]
        -

        Validation mechanism

        +

        Validation mechanism

        Behind the scenes, the API validates your configuration and lets you know if any config items prevent the submission of the error report. Below are the requirements for an application to be able to send error messages to Dotkernel API.

        @@ -97,7 +97,7 @@

        If your request passes all the checks, the message is saved in the log file specified in ErrorReportServiceInterface::class . path.

        -
        Tips and tricks
        +

        Tips and tricks

        If there are multiple applications that report errors to your API, you can assign a different error reporting token for each. The tokens support key-value pairs where:

        diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig index 4056bbc7..513d51ee 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig @@ -30,7 +30,7 @@
        -
        This article will walk you through the process of implementing MailChimp into your instance of DotKernel API using drewm/mailchimp-api
        +

        This article will walk you through the process of implementing MailChimp into your instance of DotKernel API using drewm/mailchimp-api

          Step 1: Add the library to your application using the following command: diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig index 2d7ea548..26925671 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig @@ -77,7 +77,7 @@

        Each module (Admin, App, User) in DotKernel API contains a file named OpenAPI.php. In this file you must document all of the endpoints from RoutesDelegator.php. The entries in OpenAPI.php have several descriptive items, the most important being method, request and response. These are used to generate a documentation file from the command line. The static documentation file is rendered using Swagger UI or Redoc in a user-friendly way. You can read more about this starting here and its subsequent pages.

        -

        Describing OpenAPI components

        +

        Describing OpenAPI components

        All OpenAPI components require a handful of components that are universally valid for a given project. These are below:

        @@ -113,11 +113,11 @@

        Wherever it's appropriate, schemas should be used to ensure consistency. The optional 'tags' item can be used to group operations together. Read more here.

        -

        Generating the documentation

        +

        Generating the documentation

        The documentation is generated using zircote/swagger-php. It uses the descriptions you added in the OpenAPI.php files to build the documentation file. The documentation contents can be listed in the command line or saved to a file in yaml of json format. You can read more here.

        -

        Alternatives for rendering the documentation

        +

        Alternatives for rendering the documentation

        Once you have the documentation generated, it can be rendered in two ways:

        @@ -126,7 +126,7 @@
      • Redoc lists the documentation in read-only mode, detailing example requests and responses.
      -

      Handling authentication for Swagger UI

      +

      Handling authentication for Swagger UI

      Most endpoints for your API should be protected, so to access them you are required to generate an authentication token (AuthToken). The token is related to the user type, so make sure to check the privileges required for the endpoint you are testing. After you submit the token, you can test the endpoints as an authenticated user. Clicking on the 'Try it out' button will activate the required parameter input fields and the textarea for the request body. The 'Execute' button will send the request and return the response. along with its HTTP status code. You can read more details here.

      diff --git a/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig index 040ce808..7c63b86e 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig @@ -32,7 +32,7 @@

      This article is a guide on how to add a CORS implementation on an existing DotKernel3 project.

      -

      The issue

      +

      The issue

      If you're facing this message:

      "Access to XMLHttpRequest at 'url' has been blocked by cors policy. No 'Access-Control-Allow-Origin header is present on the requested resource."

      It means the server didn't sent the header that lets you access its data through a local client (eg.: browser).

      @@ -47,13 +47,13 @@

      A simple implementation would be using Tuupola's Cors Middleware package.

      This article was inspired by: akrabat.com/implementing-tuupola-cors-in-expressive

      -

      Adding the package to your project

      +

      Adding the package to your project

      Run the following command in your project:

      composer require tuupola/cors-middleware

      At the time writing this article the current package version is: 0.9.4.

      Follow the next steps to get your Zend Expressive or DotKernel3 project CORS friendly.

      -

      Create the CORS config file

      +

      Create the CORS config file

      Create a cors.global.php file in the config/autoload directory.

      return [
           'cors' => [
      @@ -68,7 +68,7 @@
       ];

      We'll come back at this file to register the CORS middleware.

      -

      Creating a factory for the middleware

      +

      Creating a factory for the middleware

      The factory should look like the one below.

      The code below extracts de config from the cors key if provided or initializes an empty array and instantiates the Tuupola CORS middleware.

      <?php
      @@ -86,7 +86,7 @@ class CorsMiddlewareFactory
           }
       }
      -

      Registering the CORS middleware

      +

      Registering the CORS middleware

      Back at cors.global.php we will register the cors middleware so our custom implemented factory will be used to create the middleware. (basic config example below)

      <?php
       
      @@ -109,7 +109,7 @@ return [
           ]
       ];
      -

      Final step: Adding (registering) the CorsMiddleware in the pipeline

      +

      Final step: Adding (registering) the CorsMiddleware in the pipeline

      In this last step we only need to add the CorsMiddleware in config/pipelines.php

      // don't forget the use statement
       use Tuupola\Middleware\CorsMiddleware;
      diff --git a/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig
      index 73eacc78..70df6234 100644
      --- a/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig
      +++ b/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig
      @@ -37,7 +37,7 @@ This article will cover the needed steps to add composer support to your DotKern
       Assuming that you know how to use composer (if not you should consider reading this article) we wil move on to your DotKernel project "composification".
       
        
      -

      First things first

      +

      First things first

      The DotKernel project must have a composer.json file so that composer can work. Our composer.json file should look like this: @@ -73,7 +73,7 @@ If the vendor folder does not exist composer will create a vendor folde Composer will create an autoload file, which will be use to load our dependencies/packages.   -

      Adding Composer Support to DotKernel

      +

      Adding Composer Support to DotKernel

      The autoload file created by composer will be used to load our packages.
      >$composerAutoLoaderPath = realpath(APPLICATION_PATH.'/vendor/autoload.php');
       require_once($composerAutoLoaderPath);
      diff --git a/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig index fdb70bfd..722153a8 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig @@ -40,11 +40,11 @@ Because of that , we implemented in upcoming 1.8 version of DotKernel a cache la We are implementing 2 different cache factories to choose from: apc (or apcu for newest PHP installations) and file. -

      1. Configuring the cache

      +

      1. Configuring the cache

      The configuration can be set from /configs/application.ini, you can choose if you use the caching system, how long your cache stays valid, the cache namespace, and the storage provider (File or APC). I would disable the cache in development mode if I were you. For more info about the configuration and help configuring the cache see: Configuring the Cache in DotKernel. -

      2. Using the Cache

      +

      2. Using the Cache

      The cache is automatically loaded in the initialization and stored in the Registry. Loading the caching engine is not needed because it is already loaded on kernel initialization (see Dot_Kernel::initialize($startTime)), but if you would like to use caching for other purposes (where you are not initializing the kernel), the loading syntax is the following: diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig index e2fbfbea..c092064b 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig @@ -149,7 +149,7 @@ class UserHandler implements RequestHandlerInterface }

      -

      And, that's not all.

      +

      And, that's not all.

      If you use doctrine and repository pattern and you don't want to get your repository from EntityManager and want to inject it into your service, this package covers that too. The principle is the same, and for more insight about this, you can check the package documentation at dot-dependency-injection.

      diff --git a/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig index 334eb14d..ff49d5a2 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig @@ -64,7 +64,7 @@ resources.useragent.wurflcloud.lib_dir = APPLICATION_PATH "/library/WurflCloud/" Because of these changes we removed the old Dot_UserAgent_Wurfl class and added the new one Dot_UserAgent_WurflCloud wich is using the Wurfl Cloud API adapter. -

      Example of Dot_UserAgent usage in DotKernel:

      +

      Example of Dot_UserAgent usage in DotKernel:

      Get Wurfl configuration
      >$wurflConf = $registry->configuration->resources->useragent->wurflcloud;
      diff --git a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig index 802349a4..5087b9e1 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig @@ -44,7 +44,7 @@

      In this article our focus will be on enabling the dot-cache component and effectively implementing caching in DotKernel Admin.

      -
      Installation
      +

      Installation

      Run the following command in your project directory:

      composer require dotkernel/dot-cache

      After installing, add the DotCacheConfigProvider::class class to your configuration aggregate (config/config.php).

      Before we continue with the configuration process we need to know a few things about how and where the data is stored.

      The dotkernel/dot-cache component is a wrapper that sits on top of symfony/cache. It currently supports two adapters and can store data in two distinct locations:

      @@ -55,7 +55,7 @@

      1. Storing data in-memory is the fastest and sometimes the cheapest caching mechanism, but it also comes with down-sides. Storing everything in the RAM memory is not the best idea when your application is running on a low memory system. In this case you should consider using the filesystem mechanism.

      2. The second caching mechanism involves storing data into files on the local disk, known as the filesystem option. While this option may be slightly slower than the first one, it provides a more persistent storage solution.

      Feel free to explore and use other adapters from symfony/cache by checking the official documentation.

      -
      Configuration
      +

      Configuration

      In configautoloaddoctrine.global.php, in the doctrine.configuration.orm_default key add the following entry:

      'result_cache'       => 'filesystem',
       'metadata_cache'     => 'filesystem',
      @@ -76,17 +76,17 @@
       
    • query_cache
    • hydration_cache
    -
    Result cache
    +

    Result cache

    The result cache can be used to store the results of your queries, enabling Doctrine to avoid querying the database or hydrating the data again after the initial retrieval.

     

    -
    Metadata cache
    +

    Metadata cache

    Parsing your class metadata on every request is inefficient. Instead, it's advisable to cache this information using one of the available cache adapters.

     

    -
    Query cache
    +

    Query cache

    In a production environment, it's strongly recommended to cache the resulted DQL query into its SQL equivalent. Since the query doesn't change unless the DQL query itself changes, it's unnecessary to parse it multiple times.

    -
    Hydration cache
    +

    Hydration cache

    Doctrine hydration cache is a feature that stores the results of data hydration, which is the process of converting raw database data into usable objects or arrays. By caching these results, it avoids repeating the hydration process for repeated queries, improving performance. -
    How to use
    +

    How to use

    To enable caching for entities, need to add the  # attribute like in the following example:

    #
    #
    #
    class Admin extends AbstractEntity implements AdminInterface
    {
    }

    For further details about the cache mode please refer to the official documentation.

    diff --git a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig index ef5e1d05..a65ed93d 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig @@ -38,7 +38,7 @@

    The update introduces the detection of enumType and options.values from a property with type: Types::ENUM. This PR discusses the update and links to several older relevant issues.

    -

    Old setup

    +

    Old setup

    #
     class Card
    @@ -53,7 +53,7 @@ class Card
         public Suit $suit;
     }
    -

    New setup

    +

    New setup

    #
     class Card
    @@ -72,7 +72,7 @@ class Card
     
     

    Dotkernel's approach

    -

    Old setup

    +

    Old setup

    Dotkernel uses flags for columns like User->Status, but we resorted to the simpler string type. The obvious disadvantage is that you can't definitively enforce a set of values for a given column. Sure, the PHP can be set up to only use the agrred upon set of values, but the database is independent from it. If you edit a value manually in the database, any string is accepted.

    @@ -103,7 +103,7 @@ public function setStatus(string $status): self $this->status = $status; }
    -

    New setup

    +

    New setup

    Thanks to the update of doctrine/orm to version 3.2.0, Dotkernel can now have a proper link between the PHP code and database values. Now the link between the PHP code and the database is explicit and enforced.

    diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig index 41e7cadb..76c5ead0 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig @@ -33,19 +33,19 @@ After a longer wait than usual, DotKernel 1.5.0 was just released. Due to the large amount of changes and the long time spent in development, we chose to skip 1.4 and go straight to 1.5.0. Here are a few of the many changes to DotKernel in the latest release: -

    -

    Highlights of 1.5.0

    -

    Switched from Dojo to jQuery

    +

    +

    Highlights of 1.5.0

    +

    Switched from Dojo to jQuery

    Starting with 1.5.0 we've switched from using Dojo to jQuery. This doesn't mean you can't still use Dojo in your own projects, but only jQuery will be used and maintained in the DotKernel distribution. -

    New designs

    +

    New designs

    We've redesigned the admin site, with new themes, and a dropdown menu, as well as a new and simpler design for the front-end. -

    Model inheritance

    +

    Model inheritance

    Up until now, there was a lot of code duplication in models. For example, in the user model, you might have a getUserById function in the admin as well as the frontend. When you've got more models and more modules, your project can start having a lot of copy-pasted code. To prevent this, we've introduced a Dot_Model class, and a way to define global models that are inherited in the admin and frontend. This way, you can have User class in the admin that only has methods specific to the admin module, a User class in the frontend that only has code specific for the frontend, and they both inherit the Dot_Model_User class which will have all the common code. -

    Dashed controllers

    +

    Dashed controllers

    We've changed the way the controller name is parsed, so that you can have controller with multiple words, split with dashes, without breaking the coding standard (for example, www.example.com/search-article will call SearchArticleController.php) -

    Zend Registry reorganization

    +

    Zend Registry reorganization

    We've changed the structure of the registry, for more about this, please check this blog post.   diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig index 8db2ab00..de8252b9 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig @@ -31,30 +31,30 @@
    DotKernel 1.8.0 (LTS) was just released. -

    What is LTS? -

    +

    What is LTS? +

    Long-term support (LTS) is a type of special versions or editions of software designed to be supported for a longer than normal period. It is particularly applicable to open-source software projects. It contains many bug fixes, some refactoring and a few minor features. Find more details read this article.   Here are a few of the many changes to DotKernel in the latest release: -

    -

    Highlights of 1.8.0 (LTS)

    +

    +

    Highlights of 1.8.0 (LTS)

      -

    Plugin Architecture

    +

    Plugin Architecture

    Starting with 1.8.0 we will start using Plugins to make the DotKernel extending easier. We'll keep you up to date about how you create and use a plugin. -

    New design

    +

    New design

    We've redesigned the admin module and the frontend module is now mobile-friendly, but you can still use the mobile module. -

    Loads Faster

    +

    Loads Faster

    The DotKernel framework just got a big boost because it supports APC & File Caching within the framework, all the XML's and config files are cached in order to maximize response speed for more information about caching and how to cache your data see this article. -

    Easier Request Handling

    +

    Easier Request Handling

    We've added a new class, Dot_Request, which lets you have control over the request data before you use it, for example the variables $_SERVER, $_GET and $_POST are only used within controllers. -

    Features added

    +

    Features added

    • API with Rate Limit - we've added a simple API with a single key authentification and a simple implementation of a rate limit (see /configs/application.ini - section params.api)
    • Cache System - Built on the Zend_Cache backends, provides caching within DotKernel, but also in library, more details about this and how it works can be found here.
    -

    Other changes

    +

    Other changes

    • Removed WURFL integration, DotKernel can detect wether you use a mobile device separately now, the WURFL Library can be added as a Plugin from now on
    • Removed multiple SMTP Transporter, it can be added as a plugin
    • @@ -62,7 +62,7 @@ We've added a new class, Dot_Request, which lets you have control over the reque
    • Admin fail logins are no longer sent to the first admin, they are sent to all developers found in devEmails within settings table in the database
    • Alert System - Alerts can be sent to all the developers to notify them if something goes wrong, for more information about alerts read this article.
    -

    Bug Fixes

    +

    Bug Fixes

    • 0000289: seo.xml will cause error on same varname for two modules instead of overwriting
    • 0000249: email sent twice
    • diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.html.twig index 70efedff..f0585c2b 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.html.twig @@ -30,7 +30,7 @@
      -

      +

      Dotkernel Light is a good starting point for a project if you want to have full control over the functionality it contains. It easily grows into something more complex with the integration of packages based on your requirements.

      @@ -74,7 +74,7 @@

      In this article we explore how to use Dotkernel Light for a simple presentation site. We will mention what files to focus on to teach you how to add more pages of content to your site and how to manage their assets.

      -

      Adding new pages

      +

      Adding new pages

      The first step is to add the new pages in src/Page/src/Controller/PageController.php. This means adding an Action function for each page, as seen below.

      @@ -126,7 +126,7 @@

      Optional items

      -

      Twitter and OpenGraph cards

      +

      Twitter and OpenGraph cards

      If you want to promote the pages on other platforms, a helpful item is the header section in the src/App/templates/layout/default.html.twig file. This is where the Twitter (X) and OpenGraph cards should be placed.

      @@ -157,7 +157,7 @@ <meta property="og:image" content="{{ url('home') }}images/app/My-image.png"/> <meta property="og:description" content="Basic description"/> -

      Top menu

      +

      Top menu

      This menu is displayed on all of the pages, in the header. To edit it, go to src/App/templates/layout/default.html.twig and update the items under id="navbarHeader". You can use the below as an example.

      @@ -175,7 +175,7 @@

      You can also replace the nav-item class for the li elements with button-border for a link that looks more like a button.

      -

      Footer

      +

      Footer

      To edit the footer on all of the pages, search for <footer class="app-footer"> in the src/App/templates/layout/default.html.twig template. We won't include an example here, since the content is usually basic HTML and CSS with twig elements already covered in this article.

      diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-reserved-variable-names-for-caching.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-reserved-variable-names-for-caching.html.twig index 6b66e1e6..99e0720b 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-reserved-variable-names-for-caching.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-reserved-variable-names-for-caching.html.twig @@ -33,21 +33,21 @@ This article is related to: Caching in DotKernel with Zend Framework Cache The variables that DotKernel cache are below: -

      Router

      +

      Router

      Router is the object that load routes (modules, controllers, actions) settings from router.xml file. More info about router: http://www.dotkernel.com/docs/router-xml/ The router is cached as router. -

      Auth / ACL Role -

      +

      Auth / ACL Role +

      The object which authorizes user methods (aka Dot_Auth), used in all DotKernel Applications, Dot_Auth uses /configs/acl/role.xml to define the users rights. The role.xml file is cached as acl_role. -

      Menu

      +

      Menu

      The menu.xml from current module(what is a module?) More about menu.xml : http://www.dotkernel.com/docs/menu-xml/ The menu is cached as admin_menu, frontend_menu. -

      Options

      +

      Options

      The options are the ones found in /configs/dots/ and have the following naming format: option_MODULE_CONTROLLER. MODULE is the current module and CONTROLLER is the current controller or "seo", so the options cache entries will look like: @@ -56,7 +56,7 @@ MODULE is the current module and CONTROLLER is the current controller or "seo",
    • option_frontend_Page
    • option_admin_seo (yes, seo is lowercase)
    -

    Browser & OS

    +

    Browser & OS

    The browser.xml and os.xml are used to identify the Browser and OS name, icon, and type. Theese XML files are located in /configs/useragent/. Browser & OS are cached as browser_xml and os_xml. diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-template-engine.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-template-engine.html.twig index 09ff94a4..523be2d1 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-template-engine.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-template-engine.html.twig @@ -30,9 +30,9 @@
    -

    +

    -DotKernel Template Engine is an implementation of PHPLib Template engine for PHP5.

    +DotKernel Template Engine is an implementation of PHPLib Template engine for PHP5. It has an amazing ability to separate the application code from the presentation layer. Separates the manipulation of data (in the database as well as in PHP) from its final format, whether that format is HTML, XML or a formatted e-mail. The big advantage is that is allowing us to change the look and feel of a site quickly without having to delve immediately into a lot of PHP variable assignments and print statements.
    diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-version-1-0-in-action.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-version-1-0-in-action.html.twig index 92e14851..98e44f3d 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-version-1-0-in-action.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-version-1-0-in-action.html.twig @@ -38,7 +38,7 @@ It has eliminated the complexity of Zend Framework by using a different approach of how the web request are handled.
    -

    From Zend Framework, DotKernel is using only the necessary classes:

    +

    From Zend Framework, DotKernel is using only the necessary classes:

    • Zend_Config - While Zend Framework itself is configuration-less, it's often necessary to have some way to specify configurable options. Zend_Config provides multiple backends for configuration storage, and a simple, intuitive, object-oriented interface for accessing it. We store configuration as simple PHP arrays, which are then wrapped by Zend_Config.
    • Zend_Db and Zend_Db_Table - Zend_Db_Table is a classic implementation of both the Table Data Gateway and Row Data Gateway design patterns, allowing for easy and  intuitive access to database tables and rows, as well as an entry point for custom business logic surrounding our data.
    • diff --git a/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig index cd9de2c0..ffa49eb3 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig @@ -249,7 +249,7 @@ $logger->notice('5 NOTICE'); $logger->info('6 INF'); $logger->debug('7 debug'); $logger->log(Logger::NOTICE, 'NOTICE from log()'); -
      Sources:
      +

      Sources:

      • https://zendframework.github.io/zend-log/
      • https://zendframework.github.io/zend-log/writers/
      • diff --git a/src/Blog/templates/page/blog-resource/dotkernel/migration-of-zend-framework-1-pear-channel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/migration-of-zend-framework-1-pear-channel.html.twig index 5fddd0b6..75238fc8 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/migration-of-zend-framework-1-pear-channel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/migration-of-zend-framework-1-pear-channel.html.twig @@ -37,7 +37,7 @@ Zend Framework 1 is still used by a lot of  projects in Production, it's st We were unable to migrate the project on github.com since the size of the repository is more then 1 GB. We resorted to building a special server that will host only the PEAR channel for Zend Framework 1 and we are commited to keep it live and running for the long term. Instructions on how to use the new channel can be found below . -

        PEAR Channel Migration Guide

        +

        PEAR Channel Migration Guide

        Use the PEAR installer: 1. Remove the installed pear package diff --git a/src/Blog/templates/page/blog-resource/dotkernel/new-features-in-zend-framework-1-12.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/new-features-in-zend-framework-1-12.html.twig index ae50bbda..26f2df3b 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/new-features-in-zend-framework-1-12.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/new-features-in-zend-framework-1-12.html.twig @@ -31,7 +31,7 @@
        According to Matthew Weier O'Phinney announcement, Zend Framework team is pleased to announce the immediate availability of the first release candidate of the Zend Framework 1.12 series, 1.12.0RC1 -

         New Features

        +

         New Features

        • Zend_Loader changes
        • @@ -65,12 +65,12 @@ Zend_Mobile_Push was contributed by Mike Willbanks.
          Zend_Gdata_Analytics is an extension to Zend_Gdata to allow interaction with Google's Analytics Data Export API. This extension does not encompass any major changes in the overall operation of Zend_Gdata components. Zend_Gdata_Analytics was contributed by Daniel Hartmann.
          -

          Removed features

          +

          Removed features

          • Zend_Http_UserAgent_Features_Adapter_WurflApi  ( announced few months ago here )
          Due to the changes in licensing of WURFL, we have removed the WurflApi adapter. We will be providing the WurflApi adapter to ScientiaMobile so that users of WURFL will still have that option.
          -

          Bug Fixes

          +

          Bug Fixes

          In addition over 200 reported issues in the tracker have been fixed. We'd like to particularly thank Adam Lundrigan, Frank Brückner and Martin Hujer for their efforts in making this happen. Thanks also to the many people who ran the ZF1 unit tests and reported their results! For a complete list go here: http://framework.zend.com/issues/secure/IssueNavigator.jspa?requestId=12877
          diff --git a/src/Blog/templates/page/blog-resource/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.html.twig index 2e5f4bb4..7a0ed880 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.html.twig @@ -30,7 +30,7 @@
          -

          +

          What prompted the change

          diff --git a/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig index f926d208..524450b9 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig @@ -37,16 +37,16 @@ There is also an article explaining how composer can be added to DotKernel Zend Framework 1 as the Non-Namespaced dependency and Zend Framework 2 as the Namespaced dependency. -

          The Composer Dependencies

          +

          The Composer Dependencies

          ZendFramework 1 Barcode module can only be loaded with the ZF1 itself:
          >composer require 'zendframework/zendframework1'
          The ZendFramework 2 Barcode module can be separately loaded:
          >composer require 'zendframework/zend-barcode'
            -

          Important note

          +

          Important note

          These dependencies can be used anywhere after the Dot_Kernel::initialize() function was called
            -

          Using Non-Namespaced Dependencies (Zend Framework 1)

          +

          Using Non-Namespaced Dependencies (Zend Framework 1)

          The class is loaded PSR-0 style, meaning the class name looks like VendorName_PackageName_ClassName
          >// Only the text to draw is required
           $barcodeOptions = array('text' =>; 'ZEND-FRAMEWORK');
          @@ -60,7 +60,7 @@ Zend_Barcode::factory(
           'code39', 'image', $barcodeOptions, $rendererOptions
           )->render();
            -

          Using Namespaced Dependencies (Zend Framework 2)

          +

          Using Namespaced Dependencies (Zend Framework 2)

          The class is loaded PSR-4 style, meaning the class name looks like \VendorName\PackageName\ClassName
          >use Zend\Barcode\Barcode;
           
          @@ -75,11 +75,11 @@ $rendererOptions = array();
           Barcode::factory(
               'code39', 'image', $barcodeOptions, $rendererOptions
           )->render();
          -

          The result

          +

          The result

          Both the examples will render the following barcode if nothing goes wrong: Resulting barcode. Source: http://framework.zend.com/images/manual/zend.barcode.introduction.example-1.png Resulting barcode.
          Source: bit.ly/1XbZZ81 -

          Tip

          +

          Tip

          In this case the first example will work for both namespaced an non-namespaced dependencies if we add the the following as the first line at the first example:
          >use Zend\Barcode\Barcode as Zend_Barcode;
          Now we can use any of the following to access ZF2's Barcode Module: diff --git a/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig index ce47a0ee..93baccbf 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig @@ -33,13 +33,13 @@ In order to use UTF8 encoding in your DotKernel based system, is needed to make some changes in both database structure and in the application.ini file.
          Those changes are commited into DotKernel 1.6.0 dev codebase, which will be released in next days.
          -

          Database

          +

          Database

          set for all tables and columns proper collation , either utf8_general_ci or utf8_bin -

          Application.ini

          +

          Application.ini

          add the line
          >database.params.charset = utf8
          to your application.ini file, in the area. -

          Differences between utf8_general_ci and utf8_bin

          +

          Differences between utf8_general_ci and utf8_bin

          utf8_bin: compare strings by the binary value of each character in the string utf8_general_ci: compare strings using general language rules and using case-insensitive comparisons diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig index 9ab776d0..e660adf6 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig @@ -35,7 +35,7 @@ Starting with 1.5, DotKernel has a Console bootstrap to easily run PHP scripts f The bootstrap can be found in Console/index.php and it has one controller in Controller.php with two example actions. -

          Usage

          +

          Usage

          Currently there are two command line arguments:
          • -a - the name of the action that will be executed
          • @@ -44,7 +44,7 @@ Currently there are two command line arguments: Any other arguments set when calling the script will be available in the controller in the $registry->arguments array. The controller bundled with DotKernel has two example actions, "count-users" which demonstrates how to interact with models, and "send-newsletter" which reads the newsletter from the command line. -

            Examples

            +

            Examples

            >/var/www/vhosts/example.com/httpdocs/Cron/index.php -e staging -a count-users
            >/var/www/vhosts/example.com/httpdocs/Cron/index.php -e staging -a send-newsletter "test newsletter"
          diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig index 1cf61946..03bc4a33 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig @@ -52,11 +52,11 @@

          Dotkernel Admin V4 Features

          -

          PHP 8.1

          +

          PHP 8.1

          Dotkernel Admin V4 fully supports PHP 8.1 with a minimum requirement of PHP 7.4.

          -

          Configurability

          +

          Configurability

          From managing middleware order to simply adding an API key to you application the config directory is the way to go.

          @@ -102,7 +102,7 @@ ] ] -

          Authorization Guards

          +

          Authorization Guards

          The packages responsible for restricting access to certain parts of the application are dot-rbac-guard and 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.

          @@ -155,7 +155,7 @@ ] ]
          -

          CLI

          +

          CLI

          For registering new command first make sure your command class extends Symfony\Component\Console\Command\Command, then you can enable the command by registering it in config/autoload/cli.global.php.

          @@ -169,7 +169,7 @@

          Note: You can take as example Dot\Cli\Command\DemoCommand

          -

          Routing

          +

          Routing

          Providing each Module with a RoutesDelegator.php file for managing existing routes inside that specific module and an easy way of adding new ones providing the route path, Middleware that the route will use, an array of accepted methods and the route name.

          @@ -185,7 +185,7 @@

          Note: The optional attributes on the route path are marked between [] like 

          -

          Frontend

          +

          Frontend

          As for the frontend toolkit we chose to use Bootstrap 4.5.0 in combination with Fontawesome 5.0.6 for a minimalist but efficient design.

          @@ -221,7 +221,7 @@

          Note: You`ll find in the #tableToolbar buttons that toggle a modal for adding/editing/deleting admins.

          -

          +

        {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig index deb9061e..880a49fd 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig @@ -31,7 +31,7 @@

        -

        Dotkernel Admin PHP Application version 3 was launched.

        +

        Dotkernel Admin PHP Application version 3 was launched.


        @@ -47,7 +47,7 @@

        Live demo: admin.dotkernel.net

        -
        Changelog:
        +

        Changelog:

        Branch 3.0 is the current and default branch , and :

        diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig index 2e8488ea..29d3b1b6 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig @@ -215,7 +215,7 @@

        -

        Doctrine 2 ORM

        +

        Doctrine 2 ORM

        For the persistence in a relational database management system we chose Doctrine ORM (object-relational mapper).

        @@ -224,7 +224,7 @@

        The benefit of Doctrine for the programmer is the ability to focus on the object-oriented business logic and worry about persistence only as a secondary priority.

        -

        Documentation

        +

        Documentation

        Our documentation is Postman based. We use the following files in which we store information about every available endpoint ready to be tested:

        @@ -254,31 +254,31 @@

        -

        Hypertext Application Language

        +

        Hypertext Application Language

        For our API payloads (a value object for describing the API resource, its relational links and any embedded/child resources related to it) we chose mezzio-hal.

        -

        CORS

        +

        CORS

        By using MezzioCorsMiddlewareCorsMiddleware, the CORS preflight will be recognized and the middleware will start to detect the proper CORS configuration. The Router is used to detect every allowed request method by executing a route match with all possible request methods. Therefore, for every preflight request, there is at least one Router request.

        -

        OAuth 2.0

        +

        OAuth 2.0

        OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user accounts on your Dotkernel API. We are using mezzio/mezzio-authentication-oauth2 which provides OAuth 2.0 authentication for Mezzio and PSR-7/PSR-15 applications by using league/oauth2-server package.

        -

        Email

        +

        Email

        It is not unlikely for an API to send emails depending on the use case. Here is another area where Dotkernel API shines. Using DotMailServiceMailService provided by dotkernel/dot-mail you can easily send custom email templates.

        -

        Configuration

        +

        Configuration

        From authorization at request route level to API keys for your application, you can find every configuration variable in the config directory.

        @@ -293,7 +293,7 @@

        You can further customize your api within the autoload directory where each configuration category has its own file.

        -

        Routing

        +

        Routing

        Each module has a RoutesDelegator.php file for managing existing routes inside that specific module. It also allows an quick way of adding new routes by providing the route path, Middlewares that the route will use and the route name.

        @@ -302,13 +302,13 @@

        You can allocate permissions per route name in order to restrict access for a user role to a specific route in config/autoload/authorization.global.php.

        -

        Commands

        +

        Commands

        For registering new commands first make sure your command class extends SymfonyComponentConsoleCommandCommand. Then you can enable it by registering it in config/autoload/cli.global.php.

        -

        File locker

        +

        File locker

        Here you will also find our brand new file locker configuration so you can easily turn it on or off ( by default: 'enabled' => true )

        @@ -317,7 +317,7 @@

        Note: The File Locker System will create a command-{command-default-name}.lock file which will not let another instance of the same command to run until the previous one has finished.

        -

        PSR Standards

        +

        PSR Standards

          @@ -371,7 +371,7 @@

          -

          Tests

          +

          Tests

          One of the best ways to ensure the quality of your product is to create and run functional and unit tests. You can find factory-made tests in the tests/AppTest/ folder, and you can also register your own.

          diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig index 3cc74b59..384dce08 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig @@ -30,7 +30,7 @@
          -

          Dotkernel Frontend PHP Application version 3 was launched.

          +

          Dotkernel Frontend PHP Application version 3 was launched.

          @@ -42,7 +42,7 @@

          Live demo: v3.dotkernel.net

          -
          Changelog:
          +

          Changelog:

          Branch 3.0 is the current and default branch , and :

          diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig index 826ab505..7e4fe464 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig @@ -45,21 +45,21 @@ admin -> this guide.   -

          What's new

          +

          What's new

          • Runs on Zend Stratigility / Zend Expressive 3.0
          • Middleware is now PSR-15 compliant
          • PHP >=7.1 Support
            -

          Changes

          +

          Changes

          • No functional changes were made in the core code
          • If your middleware code is based on the dotkernel/dot-controller package no middleware migration is needed
          • If your middleware code is based on the http-interop/http-middleware migration must be made to implement interfaces in psr/http-server-middleware
            -

          Packages Updated

          +

          Packages Updated

          • dotkernel/dot-authentication-service:^1.0
          • dotkernel/dot-authentication-web:^1.0.1
          • @@ -83,7 +83,7 @@ If your project is a DotKernel 3 instance (based on expressive 2), you can migra
          • dotkernel/dot-twigrenderer:^1.1
          • dotkernel/dot-user:^1.0
          -

          Zend Packages (support) Updated

          +

          Zend Packages (support) Updated

          • psr/http-server-middleware:^1.0
          • psr/http-server-handler:^1.0
          • @@ -98,7 +98,7 @@ If your project is a DotKernel 3 instance (based on expressive 2), you can migra
          • zendframework/zend-component-installer:^2.0
            -

          Packages Removed

          +

          Packages Removed

          • http-interop/http-middleware
          • webimpress/http-middleware-compatibility
          • diff --git a/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.html.twig index 8973a3c2..0672cd61 100644 --- a/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.html.twig +++ b/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.html.twig @@ -124,7 +124,7 @@

            The Dotkernel development team is actively working on investigating and implementing recommended design patterns. All Dotkernel applications and a large number of packages are still receiving updates that implement bugfixes, improvements and recommendations from the PHP community. Some updates may brake BC (backward compatibility), but this is often highlighted in companion articles that may also contain step-by-step tutorials. The Dotkernel team is available to help you with any issues related to the Dotkernel and Laminas apps and components.

            -

            Additional Resources

            +

            Additional Resources

            • Dotkernel API
            • diff --git a/src/Blog/templates/page/blog-resource/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.html.twig index f48bcdf6..e76f07c7 100644 --- a/src/Blog/templates/page/blog-resource/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.html.twig @@ -150,7 +150,7 @@

              That's it! You are now logged in securely.

              -

              Additional Resources

              +

              Additional Resources

              • dot-totp in GitHub
              • diff --git a/src/Blog/templates/page/blog-resource/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.html.twig b/src/Blog/templates/page/blog-resource/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.html.twig index 09a17c60..fbdd1276 100644 --- a/src/Blog/templates/page/blog-resource/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.html.twig +++ b/src/Blog/templates/page/blog-resource/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.html.twig @@ -46,13 +46,13 @@

                Setup

                -

                Install package Mezzio CORS

                +

                Install package Mezzio CORS

                Run the following command in your application’s root directory:

                composer require mezzio/mezzio-cors
                -

                Register ConfigProvider

                +

                Register ConfigProvider

                Open your application’s config/config.php file and add the following lines to the $aggregator variable:

                @@ -60,7 +60,7 @@

                Mezzio\Cors\ConfigProvider::class,

                -

                Register Middleware

                +

                Register Middleware

                Open your application’s config/pipeline.php file and add the following line (preferrably between ErrorHandlerInterface::class and the handler/middleware that will return your response):

                @@ -70,7 +70,7 @@

                use Mezzio\Cors\Middleware\CorsMiddleware;

                -

                Create config file

                +

                Create config file

                Create and open file config/autoload/cors.local.php and add the following code inside it:

                diff --git a/src/Blog/templates/page/blog-resource/how-to/database-migrations-and-how-to-use-them.html.twig b/src/Blog/templates/page/blog-resource/how-to/database-migrations-and-how-to-use-them.html.twig index baac4d0d..7ac2a372 100644 --- a/src/Blog/templates/page/blog-resource/how-to/database-migrations-and-how-to-use-them.html.twig +++ b/src/Blog/templates/page/blog-resource/how-to/database-migrations-and-how-to-use-them.html.twig @@ -30,7 +30,7 @@
                -

                Migrations, the superhero your database deserves

                +

                Migrations, the superhero your database deserves

                Migrations ease the process of working together on projects, as well as deploying the database changes.   @@ -42,7 +42,7 @@ This article assumes that you're familiar with the basic concepts of migrations, To recap the article above, a database migration in a change in the database schema.   -

                Benefits

                +

                Benefits

                By using migrations and seeders, you ensure a better workflow for teams, as changes to the database no longer require convoluted messages. It also ensures that all team members use the same types with the same limits on each column, and someone doesn't have VARCHAR(50) while another has VARCHAR(150). It keeps the database synchronised throughout the team with minimal effort. When pushing to production, the deployment script will automatically run the migrations and keep the database schema in sync with the codebase. @@ -50,13 +50,13 @@ When pushing to production, the deployment script will automatically run the mig It requires no manual SSH'ing into production servers and no risky manual changes to production databases.   -

                Getting started

                +

                Getting started

                To setup migrations, simply follow the installation instructions in the package provided for DotKernel. After the package has been successfully setup, you're ready to go. To see the available commands, simply write "php dot" in a console that's located in your project root.   -

                How to use migrations in my project?

                +

                How to use migrations in my project?

                Migrations are very easily adapted to any project.
                • Existing projects should start by creating a new migration, and mirror the current database design in that file. @@ -77,7 +77,7 @@ Migrations are very easily adapted to any project. Notice; Migrations are stored in version control, so whenever you pull an update, quickly run php dot migrate, as it will only ever migrate new files, and leave out the ones that have been run already.   -

                  Seeders

                  +

                  Seeders

                  If you need to add some data to a table, e.g. adding a default admin profile on a fresh installation, this can be done via Seeders. Migrations manage the database layouts, seeders add data to the database. @@ -102,7 +102,7 @@ Seeders are created the same way as migrations, and also benefits hugely from be
                -

                Acknowledgements

                +

                Acknowledgements

                The images in the article shows screenshots from MySQL Workbench, and the migration package used is Phinx

                diff --git a/src/Blog/templates/page/blog-resource/how-to/doctrine-cache-in-mezzio-and-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/how-to/doctrine-cache-in-mezzio-and-dotkernel.html.twig index e1e06379..c0d1c221 100644 --- a/src/Blog/templates/page/blog-resource/how-to/doctrine-cache-in-mezzio-and-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/how-to/doctrine-cache-in-mezzio-and-dotkernel.html.twig @@ -79,7 +79,7 @@ ] ] -

                Usage

                +

                Usage

                $query = $em->createQuery('select u from \Entities\User u');
                 $query->useQueryCache(true);
                @@ -94,7 +94,7 @@ $query->useQueryCache(true); ] ] -

                Usage

                +

                Usage

                $query = $em->createQuery('select u from \Entities\User u');
                 $query->enableResultCache();
                diff --git a/src/Blog/templates/page/blog-resource/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.html.twig b/src/Blog/templates/page/blog-resource/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.html.twig index 8a3ac76f..03486a64 100644 --- a/src/Blog/templates/page/blog-resource/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.html.twig +++ b/src/Blog/templates/page/blog-resource/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.html.twig @@ -48,7 +48,7 @@
              • Shareability and Trustworthiness
              -

              How to implement slugs into Dotkernel, a practical example:

              +

              How to implement slugs into Dotkernel, a practical example:

              Let's consider we want to implement a feature that gives users the possibility to view other user's profile.

              diff --git a/src/Blog/templates/page/blog-resource/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.html.twig b/src/Blog/templates/page/blog-resource/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.html.twig index c7861d79..62c0d8bf 100644 --- a/src/Blog/templates/page/blog-resource/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.html.twig +++ b/src/Blog/templates/page/blog-resource/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.html.twig @@ -97,7 +97,7 @@

              The filters are case-insensitive and can be combined.

              -

              Example

              +

              Example

              Let's find which path should one call to register their user account. For this we will list routes where method is POST and name contains the string register:

              diff --git a/src/Blog/templates/page/blog-resource/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.html.twig b/src/Blog/templates/page/blog-resource/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.html.twig index b3876ca8..6bb8ae01 100644 --- a/src/Blog/templates/page/blog-resource/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.html.twig +++ b/src/Blog/templates/page/blog-resource/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.html.twig @@ -40,23 +40,23 @@

              Setup

              -

              Install package

              +

              Install package

              Run the following command in your application's root directory:

              composer require dotkernel/dot-cli
              -

              Register ConfigProvider

              +

              Register ConfigProvider

              Open your application's config/config.php file and add Dot\Cli\ConfigProvider::class, under the DK packages comment.

              -

              Create bootstrap file

              +

              Create bootstrap file

              Locate the provided vendor/dotkernel/dot-cli/bin/cli.php and copy it into your application's bin directory.

              This is the file you will execute your commands through.

              -

              Create config file

              +

              Create config file

              Locate the provided vendor/dotkernel/dot-cli/config/autoload/cli.global.php and copy it into your application's config/autoload directory.

              diff --git a/src/Blog/templates/page/blog-resource/how-to/using-postman-for-documentation-in-dotkernel-api-3.html.twig b/src/Blog/templates/page/blog-resource/how-to/using-postman-for-documentation-in-dotkernel-api-3.html.twig index 836e5025..1dc7b89d 100644 --- a/src/Blog/templates/page/blog-resource/how-to/using-postman-for-documentation-in-dotkernel-api-3.html.twig +++ b/src/Blog/templates/page/blog-resource/how-to/using-postman-for-documentation-in-dotkernel-api-3.html.twig @@ -54,7 +54,7 @@

              There are more terms to cover, like Mock servers, Monitors etc but they are not relevant yet in our case.

              -

              Importing documentation files

              +

              Importing documentation files

              Open Postman (and log in if you have an account), then follow the below steps:

              @@ -73,7 +73,7 @@ NAME FORMAT IMPORT AS

              For more information on importing files into Postman, please consult their docs.

              -

              Interacting with the environment

              +

              Interacting with the environment

              On the top right corner of Postman, you'll see the Environment quick look button (it looks like an eye). On it's left side, there's a dropdown with all available environments, one of the being called DotKernel_API - click on it to set it as the active environment.

              @@ -89,13 +89,13 @@ VARIABLE INITIAL VALUE CURRENT_VALUE

              Usage

              -

              Creating a new request

              +

              Creating a new request

              Inside the collection, right-click on any folder and click Add Request. The new request is automatically opened in a new tab, where you will:

              • enter request name by replacing New Request with a short and descriptive name
              • enter request description (first button under the Environment quick look button)
              • enter request URL - you should use the already existing variable APPLICATION_URL by wrapping it in double curly braces, then append the request path. Example: {{ APPLICATION_URL }}/test becomes http://localhost:8080/test when you send the request.
              • select request method
              • (optionally) inspect tabs:
                • Params: manage query parameters
                • Authorization: select authorization type
                • Headers: manage request headers
                • Body: manage request body
                • Pre-request script: provide JavaScript that Postman should run before sending the request
                • Tests: provide JavaScript that Postman should run after receiving the response
                • Settings: manage miscellaneous request settings
              • save the request using the Save button (or Ctrl + S)
              -

              Sending a request

              +

              Sending a request

              Once opened, you can immediately send a request by clicking the Send button.

              @@ -105,7 +105,7 @@ VARIABLE INITIAL VALUE CURRENT_VALUE

              The collection provided by DotKernel API 3 comes with some built-in security features.

              -

              Global security

              +

              Global security

              Due to the hierarchy of the Collection, all requests are children of the Collection's root folder, so they automatically inherit it's Authorization settings. If you edit the Collection's root folder, you will find the following tabs:

              @@ -115,7 +115,7 @@ VARIABLE INITIAL VALUE CURRENT_VALUE

              To disable this feature at folder/request level, edit the folder/request and set Type to No Auth under the Authorization tab.

              -

              Automatic Token storage

              +

              Automatic Token storage

              Once an authorization token is re/generated, two environment variables are set/updated:

              @@ -129,11 +129,11 @@ VARIABLE INITIAL VALUE CURRENT_VALUE

              TIP: You can inspect your application's documentation at any moment by clicking on the three horizontal dots (you will find them on hover next to your collection's name) then click on View documentation.

              -

              Exporting collection

              +

              Exporting collection

              Click on the same three horizontal dots, then select Export. You will be presented an Export collection modal asking you to choose the format of the export. Unless specified, choose the recommended version, then click Export. When asked to save the exported file, choose to overwrite your application's collection file (documentaion/DotKernel_API.postman_collection.json).

              -

              Export environment

              +

              Export environment

              This step is optional. It is needed only if you made modifications to the environment, else you can skip it.

              diff --git a/src/Blog/templates/page/blog-resource/how-to/using-the-urlgenerator-work-in-fastroute.html.twig b/src/Blog/templates/page/blog-resource/how-to/using-the-urlgenerator-work-in-fastroute.html.twig index ea1bb6f2..dbcd1230 100644 --- a/src/Blog/templates/page/blog-resource/how-to/using-the-urlgenerator-work-in-fastroute.html.twig +++ b/src/Blog/templates/page/blog-resource/how-to/using-the-urlgenerator-work-in-fastroute.html.twig @@ -35,14 +35,14 @@ DotKernel 3 uses FastRoute under the hood, which is an excellent an A wrong setup can lead to many headaches, as it's not prominent that the error you're experiencing is from FastRoute, and you may not know where exactly to look for the cause. To save you some time, here's a how-to for creating and using routes in DotKernel 3. -

              Declaring routes

              +

              Declaring routes

              When defining routes in the routes.php file, they need to follow a specific pattern, a usual route would look like this:
              >$app->route('/page', , , 'page');
              Notice the '/page' part, as this is crucial. FastRoute cannot handle a slash-suffix, which means that /page will give you the indexAction, whereas /page/ will give you a 404.
              To prevent the URLGenerator from adding a slash-suffix, we need to make sure that the optional slash is included in the optional block. The above example is correct, if we instead wrote it like this, it'd be woefully bad.
              >$app->route('/page/', , , 'page');
              Notice that the slash has changed places and is now outside the optional block. This would generate a /page/ route for the index action, which is bad, so always include any optional parts, even slashes, in the optional block.
              -

              Referencing routes

              +

              Referencing routes

              Developers are used to hand-write and hard-code URLs into anchor tags, but this is a bad idea, as a URL may change and then you'd have to go through the entire app to find every single reference for it. Instead, every route we define has a name that can be used to reference it in the URLGenerator.
              >$app->route('/page', , , 'page');
              That very last part there, that's the route name, which can be referenced in any view or controller to generate a route to a specific route in the app.
              diff --git a/src/Blog/templates/page/blog-resource/how-to/what-is-cross-origin-token-redemption.html.twig b/src/Blog/templates/page/blog-resource/how-to/what-is-cross-origin-token-redemption.html.twig index 2a9e2cd7..068fed89 100644 --- a/src/Blog/templates/page/blog-resource/how-to/what-is-cross-origin-token-redemption.html.twig +++ b/src/Blog/templates/page/blog-resource/how-to/what-is-cross-origin-token-redemption.html.twig @@ -30,13 +30,13 @@
              -

              What is cross origin token redemption?

              +

              What is cross origin token redemption?

              Cross-origin token redemption is a technique used to ensure the security and authenticity of a token that is issued by one website or domain, but intended for use on a different website or domain. This process is commonly used in situations where a user needs to access resources from multiple domains, such as when a user is logged in to one website and needs to access resources from another website.

              When a token is issued from one domain, it is typically encrypted and signed to prevent tampering and ensure that it can only be used by the intended recipient. When the token is redeemed on a different domain, the receiving domain must first verify the authenticity of the token by checking the signature and decrypting the token.

              -

              JSON Web Token (JWT)

              +

              JSON Web Token (JWT)

              One common method of cross-origin token redemption is to use the JWT standard. JWTs are a compact, URL-safe means of representing claims to be transferred between two parties. They consist of three parts:

              @@ -49,7 +49,7 @@

              In order to redeem a JWT on a different domain, the receiving domain must first verify the signature using the algorithm specified in the header and the secret key that was used to sign the token. Once the signature is verified, the domain can then read the claims from the payload to determine if the user is authorized to access the requested resources.

              -

              OAuth 2.0

              +

              OAuth 2.0

              Another method of cross-origin token redemption is to use the OAuth 2.0 standard. OAuth 2.0 is an open standard for authorization that enables a third-party application to obtain limited access to an HTTP service. In this method, when a user is logged in to one website and wants to access resources from another website, the first website will redirect the user to the second website and pass a token. The second website will then validate the token and provide the user with access to the requested resources.

              diff --git a/src/Blog/templates/page/blog-resource/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.html.twig b/src/Blog/templates/page/blog-resource/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.html.twig index 58e873ae..d1ce8287 100644 --- a/src/Blog/templates/page/blog-resource/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.html.twig +++ b/src/Blog/templates/page/blog-resource/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.html.twig @@ -30,7 +30,7 @@
              -

              Free PHP, HTML, CSS, JavaScript editor (IDE) - Codelobster PHP Edition

              +

              Free PHP, HTML, CSS, JavaScript editor (IDE) - Codelobster PHP Edition

              Free PHP, HTML, CSS, JavaScript editor (IDE) - Codelobster PHP Edition

              For valuable work on creation of sites you need a good comfortable editor necessarily. There are many requiring paid products for this purpose, but we would like to select free of charge very functional and at the same time of simple in the use editor - Codelobster PHP Edition .

              @@ -49,7 +49,7 @@
            • The portable option allows to use editor without the preliminary installation.
            • Other useful utilities: pair highlighting, possibility of blocks selection, collapsing, tooltips, navigation on descriptions of functions and included files at withholding of the key of CTRL, viewing of structure of files and project, preview in a browser, book-marks, and all other standard possibilities for work with a code.
            -

            Also there are special plugins for work with:

            +

            Also there are special plugins for work with:

            • CMS: Drupal, Joomla
            • PHP frameworks: CakePHP, CodeIgniter, Symfony, Yii
            • diff --git a/src/Blog/templates/page/blog-resource/licensing/mit-versus-lgpl-in-practice-dotkernel-case.html.twig b/src/Blog/templates/page/blog-resource/licensing/mit-versus-lgpl-in-practice-dotkernel-case.html.twig index 3b4c505d..a1ea217c 100644 --- a/src/Blog/templates/page/blog-resource/licensing/mit-versus-lgpl-in-practice-dotkernel-case.html.twig +++ b/src/Blog/templates/page/blog-resource/licensing/mit-versus-lgpl-in-practice-dotkernel-case.html.twig @@ -32,21 +32,21 @@

              After a recent analysis, we discovered that one of the upstream packages we use is licensed under LGPL v3. Even though we at DotKernel use the MIT license for our open source projects, the more restrictive license must be applied to the whole application. We implemented a workaround detailed below.

              -

              Detailing the problem

              +

              Detailing the problem

              The package in question is matomo/device-detector which requires the developers to share their derivative code publicly. This goes against our contractual obligations to the client to keep their custom code business and enterprise friendly. The conflict that results in this scenario may cause legal issues in the future.

              -

              The solution

              +

              The solution

              Some companies explicitly steer clear of scenarios of this nature and DotKernel has decided to do the same. Our solution for DotKernel applications and libraries is to discard the use of 3rd party packages with LGPL v3. In this particular case we used the matomo/device-detector package in our dotkernel/dot-user-agent-sniffer package for the purpose of identifying the user agent and using the results in internal reports.

              ALL of DotKernel's packages are licensed under MIT which has no restrictions regarding the source code, other than keeping the license and copyright notice in a file within each package. Other non-restrictive licenses include Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MPL-2.0 and OSL-3.0. Some of them are used by the dependencies in DotKernel’s packages instead of MIT, but are still deemed acceptable.

              -

              The resolution

              +

              The resolution

              The dotkernel/dot-user-agent-sniffer package must follow the LGPL v3 license, beginning with version 3.4.0. Our admin application will not use the features from matomo/device-detector, but will contain instructions on how to add the package, if other developers intend to use it.

              -

              Important note

              +

              Important note

              Warning: Any application using dotkernel/dot-user-agent-sniffer with a version lower than 3.4.0 is still a legal liability. We at DotKernel will not abandon the issue as is, but are looking into a solution to bring back the device detector functionality in the future under a less restrictive license. For now we will not include dotkernel/dot-user-agent-sniffer in any of our applications by default.

              diff --git a/src/Blog/templates/page/blog-resource/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.html.twig b/src/Blog/templates/page/blog-resource/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.html.twig index 3751aac5..c4f91969 100644 --- a/src/Blog/templates/page/blog-resource/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.html.twig +++ b/src/Blog/templates/page/blog-resource/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.html.twig @@ -30,7 +30,7 @@
              -

              +

              The goal of this update is to implement PSR-15 handlers into Dotkernel Light. There are several advantages to using handlers, which we will explore below.

              diff --git a/src/Blog/templates/page/blog-resource/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.html.twig b/src/Blog/templates/page/blog-resource/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.html.twig index 0ab5f734..7b7053e7 100644 --- a/src/Blog/templates/page/blog-resource/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.html.twig +++ b/src/Blog/templates/page/blog-resource/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.html.twig @@ -48,7 +48,7 @@

              -
              NOTE:
              +

              NOTE:

              The package dotkernel/dot-data-fixture does NOT depend on other DotKernel packages. The only dependency is Doctrine

              @@ -113,7 +113,7 @@ ConsoleRunner::run(

              Usage

              -
              List all available fixtures, by order of execution:
              +

              List all available fixtures, by order of execution:

              php bin/doctrine fixtures:list
              @@ -121,7 +121,7 @@ ConsoleRunner::run(

              By using this command you can check the execution order of your fixtures before executing them.

              -
              Executing fixtures:
              +

              Executing fixtures:

              To execute all fixtures run : php bin/doctrine fixtures:execute

              diff --git a/src/Blog/templates/page/blog-resource/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.html.twig b/src/Blog/templates/page/blog-resource/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.html.twig index c277d74a..9366652c 100644 --- a/src/Blog/templates/page/blog-resource/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.html.twig +++ b/src/Blog/templates/page/blog-resource/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.html.twig @@ -45,8 +45,8 @@ Now let's try it with 0.6 instead of 0.7 var_dump((0.6+0.1)*10); # float(7) var_dump(intval((0.6+0.1)*10)); # int(7) -

              -

              How does the CPU understand these numbers?

              +

              +

              How does the CPU understand these numbers?

              The CPU makes the calculations binary, the floating point numbers are represented as follows: IEEE Short Real: 32 bits     1 bit for the sign, 8 bits for the exponent, and 23 bits for the mantissa. Also called single precision. @@ -67,7 +67,7 @@ The float numbers are sign * exponent * this link. -

              Why does this happen?

              +

              Why does this happen?

              Using this IEEE 754 converter we have found out that: 0.7 is actually represented as 0.699999988079071 @@ -82,7 +82,7 @@ The other example still shows 7 because 0.6 is actually represe   -

              But still ...

              +

              But still ...

              If we use echo and var_dump or if applying mathematical operations, PHP automatically adjusts the values, but intval and casting to int work on their bits before the values were adjusted diff --git a/src/Blog/templates/page/blog-resource/php-development/mezzio-app-development-in-wsl2.html.twig b/src/Blog/templates/page/blog-resource/php-development/mezzio-app-development-in-wsl2.html.twig index cdef3008..01bed6f3 100644 --- a/src/Blog/templates/page/blog-resource/php-development/mezzio-app-development-in-wsl2.html.twig +++ b/src/Blog/templates/page/blog-resource/php-development/mezzio-app-development-in-wsl2.html.twig @@ -34,19 +34,19 @@

              This article will run you through the steps of installing a Mezzio application (DotKernel API) in WSL2 and run it on Ubuntu 20.04 LTS.

              -

              Step 1:

              +

              Step 1:

              Make sure you have WSL2 installed on your machine by following this guide.

              -

              Step 2:

              +

              Step 2:

              Install Ubuntu 20.0 LTS inside WLS2 as described here.

              -

              Step 3:

              +

              Step 3:

              Create a virtualhost for your project using this guide.

              -

              Step 4:

              +

              Step 4:

              Using your terminal, move into the virtualhost directory that you just created:

              @@ -56,7 +56,7 @@

              Make sure your data and log directories are writable by changing their permissions, as described here.

              -

              Step 5:

              +

              Step 5:

              Setup PHPStorm to work with WSL2 files like in this article

              diff --git a/src/Blog/templates/page/blog-resource/php-development/php-environment-development-staging-production.html.twig b/src/Blog/templates/page/blog-resource/php-development/php-environment-development-staging-production.html.twig index d8c0e78d..09e02526 100644 --- a/src/Blog/templates/page/blog-resource/php-development/php-environment-development-staging-production.html.twig +++ b/src/Blog/templates/page/blog-resource/php-development/php-environment-development-staging-production.html.twig @@ -33,11 +33,11 @@ In hosted software development, the environment refers to a server tier designated to a specific stage in a release process. The purpose of these environments is to improve the development, testing and release processes in client-server applications. The most common environments are described below. -

              Development

              +

              Development

              This is where the software is developed – it’s the working environment for individual developers or small teams. The purpose of this environment is for the developer to work on local host, separate from the rest of the team, allowing them to make various changes without worrying that it may alter the work of the other members of the team. -

              Staging

              +

              Staging

              It is used to assemble, test and review the application before it goes into production. Usually the staging environment tries to simulate as much as possible the production environment (hardware and software-wise). Normally, before releasing an update version of the application on the production environment, the update must be tested on the staging environment. This environment can also be used as a demonstration/training environment. -

              Production

              +

              Production

              It is the “live” environment, where the final application goes out to the world and becomes active. To switch from one environment to another use the Subversion source code. diff --git a/src/Blog/templates/page/blog-resource/php-development/using-php-7-express-in-zend-studio-13.html.twig b/src/Blog/templates/page/blog-resource/php-development/using-php-7-express-in-zend-studio-13.html.twig index aca9cf27..477f7a0d 100644 --- a/src/Blog/templates/page/blog-resource/php-development/using-php-7-express-in-zend-studio-13.html.twig +++ b/src/Blog/templates/page/blog-resource/php-development/using-php-7-express-in-zend-studio-13.html.twig @@ -35,9 +35,9 @@ This article will cover the steps needed in order to check the PHP7 compatibilit This article will also contain a compatibility issue check on the latest Zend Framework 1 version. -

              What is PHP 7 Express?

              +

              What is PHP 7 Express?

              PHP 7 Express is the newest feature added in Zend Studio (from version 13). This feature is very useful for checking if your pre-PHP7 code will work on a PHP7 server. -

              The project creation

              +

              The project creation

              If you already have the project backup and you are sure you can modify your project you can skip this step. If you want to create a new project for testing instead of modifying the original project make sure the PHP Version is set to PHP 5.6 or the PHP Version you use in the project. Choosing the PHP 7 option will hide the PHP 7 Express feature because Zend Studio assumes a PHP 7 project doesn't need compatibility checks, but if the project is using a different version of PHP compatibility issues might apper. @@ -45,7 +45,7 @@ If you want to create a new project for testing instead of modifying the origina Long story short: Use the exact PHP version your project is using at the project creation. The New Local PHP Project dialog in Zend Studio 13 The "New Local PHP Project" dialog in Zend Studio 13 and the PHP version selection -

              The 'PHP Interpreter' selection

              +

              The 'PHP Interpreter' selection

              If the PHP 7 Express feature is won't show up the issue might be the PHP Interpreter selected. To check and modify the PHP Interpreter: @@ -58,7 +58,7 @@ To check and modify the PHP Interpreter: Zend Studio PHP Interpreter Dialog Zend Studio PHP Interpreter Dialog The PHP 7 Express feature should now show up. -

              Project preparation

              +

              Project preparation

              As we created a local project named zend-framework-test we will add the latest Zend Framework 1 version in our project.
              • right click on the project
              • @@ -68,7 +68,7 @@ As we created a local project named zend-framework-test we will
              • copy the Zend folder in your project's library folder
              • if the Drag and Drop didn't work and you had to manually copy the folder in your project you can click the project folder in Zend Studio and just press F5 (Refresh)
              -

              Using PHP 7 Express - Testing Zend Framework 1

              +

              Using PHP 7 Express - Testing Zend Framework 1

              Now let's see PHP 7 Express in action. Using PHP 7 Express is very easy:
              • right-click the project, not a folder or a file because the feature won't show up
              • diff --git a/src/Blog/templates/page/blog-resource/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.html.twig b/src/Blog/templates/page/blog-resource/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.html.twig index 30992e4a..1c41b0c0 100644 --- a/src/Blog/templates/page/blog-resource/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.html.twig +++ b/src/Blog/templates/page/blog-resource/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.html.twig @@ -31,7 +31,7 @@
                This article will cover the solution to the PEAR "Cannot use result of built-in function in write context" issue. -

                The Issue

                +

                The Issue

                If installing a pear package (for instance PHP Code Sniffer), when running:
                pear install PHP_CodeSniffer
                This error is shown @@ -42,7 +42,7 @@ This error is shown because the function is called by reference. More details about this issue can be found in this Pull Request.   -

                The solution

                +

                The solution

                You might be tempted to execute the following
                pear install Archive_Tar
                which will result in the same error. @@ -54,7 +54,7 @@ Go to the line indicated in the error (639 in this case) and replace: with
                 $v_att_list = func_get_args();
                The above means the func_get_args() isn't called by reference anymore. -

                Our recommendation

                +

                Our recommendation

                The above does fix the problem, but we recommend installing the Archive_Tar again so you have the latest working version.   diff --git a/src/Blog/templates/page/blog-resource/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.html.twig b/src/Blog/templates/page/blog-resource/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.html.twig index e78c85a2..8223cc72 100644 --- a/src/Blog/templates/page/blog-resource/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.html.twig +++ b/src/Blog/templates/page/blog-resource/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.html.twig @@ -57,7 +57,7 @@ If you have the bundled PHP version, the extension might be existing but not ena  

                Solutions

                -

                For Linux-based Server (assuming you have root access):

                +

                For Linux-based Server (assuming you have root access):

                • Make sure the php_intl.so file exists within your php extensions directory, find the extensions directory by:
                    @@ -92,7 +92,7 @@ If you have the bundled PHP version, the extension might be existing but not ena For projects hosted on a shared hosting platform you must ask your hosting provider to install/enable the PHP Intl extension.   -

                    For Windows-based Server:

                    +

                    For Windows-based Server:

                    • Make sure the php_intl.dll file exists within your php extensions directory
                        diff --git a/src/Blog/templates/page/blog.html.twig b/src/Blog/templates/page/blog.html.twig index 7abe7bed..3e233650 100644 --- a/src/Blog/templates/page/blog.html.twig +++ b/src/Blog/templates/page/blog.html.twig @@ -29,7 +29,7 @@ {{ article.category.name }} -

                        {{ article.title }}

                        +

                        {{ article.title }}

                        {{ article.excerpt|length > 200 ? article.excerpt|slice(0, 200)|trim ~ '…' : article.excerpt }}

                        {{ article.author.name }} . {{ article.postDate|date('M d, Y') }} Read more → diff --git a/src/Blog/templates/page/categories.html.twig b/src/Blog/templates/page/categories.html.twig index 41e22b32..1b0a174c 100644 --- a/src/Blog/templates/page/categories.html.twig +++ b/src/Blog/templates/page/categories.html.twig @@ -26,7 +26,7 @@ style="width:20px;height:20px;color:var(--teal);" aria-hidden="true"> -

                        {{ category.name }}

                        +

                        {{ category.name }}

                        Discover latest articles in this section.

                        Browse articles → diff --git a/src/Blog/templates/page/category-resource.html.twig b/src/Blog/templates/page/category-resource.html.twig index cc17acea..c1c074a9 100644 --- a/src/Blog/templates/page/category-resource.html.twig +++ b/src/Blog/templates/page/category-resource.html.twig @@ -33,7 +33,7 @@ {{ article.category.name }} -

                        {{ article.title }}

                        +

                        {{ article.title }}

                        {{ article.excerpt|length > 200 ? article.excerpt|slice(0, 200)|trim ~ '…' : article.excerpt }}

                        {{ article.author.name }} · {{ article.postDate|date('M d, Y') }} Read more → diff --git a/src/Blog/templates/page/contact.html.twig b/src/Blog/templates/page/contact.html.twig index 39736d34..f729f57d 100644 --- a/src/Blog/templates/page/contact.html.twig +++ b/src/Blog/templates/page/contact.html.twig @@ -79,7 +79,7 @@ style="width:20px;height:20px;color:var(--teal);" aria-hidden="true"> -

                        GitHub

                        +

                        GitHub

                        Browse our repositories, open issues, or contribute to the DotKernel ecosystem.

                        Visit GitHub → @@ -89,7 +89,7 @@ -

                        Documentation

                        +

                        Documentation

                        Read the guides, references, and best practices for building with DotKernel.

                        Read the Docs → @@ -98,7 +98,7 @@ style="width:20px;height:20px;color:var(--teal);" aria-hidden="true"> -

                        Discussion

                        +

                        Discussion

                        Ask questions, share ideas, and join the conversation with the DotKernel community.

                        Open Discussions → diff --git a/src/Blog/templates/page/dotkernel-packages-oss-lifecycle.html.twig b/src/Blog/templates/page/dotkernel-packages-oss-lifecycle.html.twig index 05574a92..2f6e1345 100644 --- a/src/Blog/templates/page/dotkernel-packages-oss-lifecycle.html.twig +++ b/src/Blog/templates/page/dotkernel-packages-oss-lifecycle.html.twig @@ -23,7 +23,7 @@
                        -
                        Active

                        light

                        +
                        Active

                        light

                        PHP version @@ -35,7 +35,7 @@
                        -
                        Active

                        admin

                        +
                        Active

                        admin

                        PHP version @@ -47,7 +47,7 @@
                        -
                        Active

                        queue

                        +
                        Active

                        queue

                        PHP version @@ -59,7 +59,7 @@
                        -
                        Active

                        api

                        +
                        Active

                        api

                        PHP version @@ -71,7 +71,7 @@
                        -
                        Active

                        dot-sso-entra

                        +
                        Active

                        dot-sso-entra

                        PHP version @@ -83,7 +83,7 @@
                        -
                        Active

                        frontend

                        +
                        Active

                        frontend

                        PHP version @@ -95,7 +95,7 @@
                        -
                        Active

                        dot-cli

                        +
                        Active

                        dot-cli

                        PHP version @@ -107,7 +107,7 @@
                        -
                        Active

                        dot-event

                        +
                        Active

                        dot-event

                        PHP version @@ -119,7 +119,7 @@
                        -
                        Active

                        dot-mail

                        +
                        Active

                        dot-mail

                        PHP version @@ -131,7 +131,7 @@
                        -
                        Active

                        dot-twigrenderer

                        +
                        Active

                        dot-twigrenderer

                        PHP version @@ -143,7 +143,7 @@
                        -
                        Active

                        dot-totp

                        +
                        Active

                        dot-totp

                        PHP version @@ -155,7 +155,7 @@
                        -
                        Active

                        dot-authorization

                        +
                        Active

                        dot-authorization

                        PHP version @@ -167,7 +167,7 @@
                        -
                        Active

                        dot-maker

                        +
                        Active

                        dot-maker

                        PHP version @@ -179,7 +179,7 @@
                        -
                        Active

                        dot-geoip

                        +
                        Active

                        dot-geoip

                        PHP version @@ -191,7 +191,7 @@
                        -
                        Security-only

                        dot-annotated-services

                        +
                        Security-only

                        dot-annotated-services

                        PHP version @@ -203,7 +203,7 @@
                        -
                        Active

                        dot-navigation

                        +
                        Active

                        dot-navigation

                        PHP version @@ -215,7 +215,7 @@
                        -
                        Active

                        dot-flashmessenger

                        +
                        Active

                        dot-flashmessenger

                        PHP version @@ -227,7 +227,7 @@
                        -
                        Active

                        dot-session

                        +
                        Active

                        dot-session

                        PHP version @@ -239,7 +239,7 @@
                        -
                        Active

                        dot-log

                        +
                        Active

                        dot-log

                        PHP version @@ -251,7 +251,7 @@
                        -
                        Active

                        dot-errorhandler

                        +
                        Active

                        dot-errorhandler

                        PHP version @@ -263,7 +263,7 @@
                        -
                        Active

                        dot-form

                        +
                        Active

                        dot-form

                        PHP version @@ -275,7 +275,7 @@
                        -
                        Active

                        dot-rbac-guard

                        +
                        Active

                        dot-rbac-guard

                        PHP version @@ -287,7 +287,7 @@
                        -
                        Active

                        dot-mail-outlook

                        +
                        Active

                        dot-mail-outlook

                        PHP version @@ -299,7 +299,7 @@
                        -
                        Active

                        dot-rbac-route-guard

                        +
                        Active

                        dot-rbac-route-guard

                        PHP version @@ -311,7 +311,7 @@
                        -
                        Active

                        dot-controller

                        +
                        Active

                        dot-controller

                        PHP version @@ -323,7 +323,7 @@
                        -
                        Active

                        dot-rbac

                        +
                        Active

                        dot-rbac

                        PHP version @@ -335,7 +335,7 @@
                        -
                        Active

                        dot-authentication

                        +
                        Active

                        dot-authentication

                        PHP version @@ -347,7 +347,7 @@
                        -
                        Active

                        dot-user-agent-sniffer

                        +
                        Active

                        dot-user-agent-sniffer

                        PHP version @@ -359,7 +359,7 @@
                        -
                        Active

                        dot-auth-social

                        +
                        Active

                        dot-auth-social

                        PHP version @@ -371,7 +371,7 @@
                        -
                        Active

                        dot-helpers

                        +
                        Active

                        dot-helpers

                        PHP version @@ -383,7 +383,7 @@
                        -
                        Active

                        dot-router

                        +
                        Active

                        dot-router

                        PHP version @@ -395,7 +395,7 @@
                        -
                        Active

                        dot-response-header

                        +
                        Active

                        dot-response-header

                        PHP version @@ -407,7 +407,7 @@
                        -
                        Active

                        dot-dependency-injection

                        +
                        Active

                        dot-dependency-injection

                        PHP version @@ -419,7 +419,7 @@
                        -
                        Active

                        dot-cache

                        +
                        Active

                        dot-cache

                        PHP version @@ -431,7 +431,7 @@
                        -
                        Active

                        dot-data-fixtures

                        +
                        Active

                        dot-data-fixtures

                        PHP version @@ -443,7 +443,7 @@
                        -
                        Active

                        dot-debugbar

                        +
                        Active

                        dot-debugbar

                        PHP version @@ -455,7 +455,7 @@
                        -
                        Security-only

                        dot-doctrine-metadata

                        +
                        Security-only

                        dot-doctrine-metadata

                        PHP version @@ -467,7 +467,7 @@
                        -
                        Security-only

                        dot-mapper

                        +
                        Security-only

                        dot-mapper

                        PHP version @@ -479,7 +479,7 @@
                        -
                        Security-only

                        dot-paginator

                        +
                        Security-only

                        dot-paginator

                        PHP version @@ -491,7 +491,7 @@
                        -
                        Security-only

                        dot-validator

                        +
                        Security-only

                        dot-validator

                        PHP version @@ -503,7 +503,7 @@
                        -
                        Security-only

                        dot-inputfilter

                        +
                        Security-only

                        dot-inputfilter

                        PHP version @@ -515,7 +515,7 @@
                        -
                        Maintenance

                        dot-authentication-service

                        +
                        Maintenance

                        dot-authentication-service

                        PHP version @@ -527,7 +527,7 @@
                        -
                        Security-only

                        dot-hydrator

                        +
                        Security-only

                        dot-hydrator

                        PHP version @@ -539,7 +539,7 @@
                        -
                        Security-only

                        dot-authentication-web

                        +
                        Security-only

                        dot-authentication-web

                        PHP version @@ -551,7 +551,7 @@
                        -
                        Security-only

                        dot-controller-plugin-mail

                        +
                        Security-only

                        dot-controller-plugin-mail

                        PHP version @@ -563,7 +563,7 @@
                        -
                        Security-only

                        dot-controller-plugin-session

                        +
                        Security-only

                        dot-controller-plugin-session

                        PHP version @@ -575,7 +575,7 @@
                        -
                        Security-only

                        dot-user

                        +
                        Security-only

                        dot-user

                        PHP version @@ -587,7 +587,7 @@
                        -
                        Security-only

                        dot-controller-plugin-flashmessenger

                        +
                        Security-only

                        dot-controller-plugin-flashmessenger

                        PHP version @@ -599,7 +599,7 @@
                        -
                        Security-only

                        dot-filter

                        +
                        Security-only

                        dot-filter

                        PHP version @@ -611,7 +611,7 @@
                        -
                        Security-only

                        dot-controller-plugin-forms

                        +
                        Security-only

                        dot-controller-plugin-forms

                        PHP version @@ -623,7 +623,7 @@
                        -
                        Security-only

                        dot-controller-plugin-authorization

                        +
                        Security-only

                        dot-controller-plugin-authorization

                        PHP version @@ -635,7 +635,7 @@
                        -
                        Security-only

                        dot-controller-plugin-authentication

                        +
                        Security-only

                        dot-controller-plugin-authentication

                        PHP version @@ -647,7 +647,7 @@
                        -
                        Archived

                        dot-console

                        +
                        Archived

                        dot-console

                        PHP version From 5271a42ee731ee667158c9d32dc084da09a9355e Mon Sep 17 00:00:00 2001 From: olaru Date: Tue, 21 Jul 2026 13:50:41 +0300 Subject: [PATCH 2/4] Fixed table structure --- src/App/templates/JSON-LD/index.jsonld.twig | 4 +++- .../dotkernel-api-versus-laminas-api-tools.html.twig | 2 +- .../dotkernel3-stable-release-version-1-0.html.twig | 2 -- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/App/templates/JSON-LD/index.jsonld.twig b/src/App/templates/JSON-LD/index.jsonld.twig index 826a02b5..704e37d8 100644 --- a/src/App/templates/JSON-LD/index.jsonld.twig +++ b/src/App/templates/JSON-LD/index.jsonld.twig @@ -8,7 +8,9 @@ "url": "{{ absolute_url('/') }}", "name": "Dotkernel", "description": "Dotkernel is a collection of open-source application skeletons built on Mezzio and Laminas - pre-configured and ready for anything from a presentation site to an enterprise-grade API.", - "publisher": { "@id": "{{ absolute_url('/') }}#organization" } + "publisher": { + "@id": "{{ absolute_url('/') }}#organization" + } }, { "@type": "Organization", diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig index 2d252c25..81d12719 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig @@ -35,7 +35,7 @@

                        The table below refers to Dotkernel API V7

                        -
                        API Tools (formerly Apigility)Dotkernel API
                        URLapi-toolsDotkernel API
                        First Release20122018
                        PHP Version<= 8.2PHP Version
                        ArchitectureMVC, Event DrivenMiddleware
                        OSS LifecycleArchivedOSS Lifecycle
                        StyleREST, RPCREST
                        VersioningYesDeprecations (API Evolution) *
                        DocumentationSwagger (Automated)Postman (Manual), OpenAPI 3.0 (Swagger)
                        Content-NegotiationCustomCustom
                        LicenseBSD-3MIT
                        Default DB Layerlaminas-dbdoctrine-orm 3.x
                        AuthorizationACLRBAC-guard
                        AuthenticationHTTP Basic/Digest OAuth2.0OAuth2.0
                        CI/CDYesYes
                        Unit TestsYesYes
                        Code (Endpoint) GeneratorYesdot-maker
                        PSRPSR-7PSR-7, PSR-15
                        +
                        API Tools (formerly Apigility)Dotkernel API
                        URLapi-toolsDotkernel API
                        First Release20122018
                        PHP Version<= 8.2PHP Version
                        ArchitectureMVC, Event DrivenMiddleware
                        OSS LifecycleArchivedOSS Lifecycle
                        StyleREST, RPCREST
                        VersioningYesDeprecations (API Evolution) *
                        DocumentationSwagger (Automated)Postman (Manual), OpenAPI 3.0 (Swagger)
                        Content-NegotiationCustomCustom
                        LicenseBSD-3MIT
                        Default DB Layerlaminas-dbdoctrine-orm 3.x
                        AuthorizationACLRBAC-guard
                        AuthenticationHTTP Basic/Digest OAuth2.0OAuth2.0
                        CI/CDYesYes
                        Unit TestsYesYes
                        Code (Endpoint) GeneratorYesdot-maker
                        PSRPSR-7PSR-7, PSR-15

                        Note

                        diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig index 7e4fe464..0de2fd8c 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig @@ -43,8 +43,6 @@ admin -> this guide. - - 

                        What's new

                        • Runs on Zend Stratigility / Zend Expressive 3.0
                        • From 382450840cce674a49457d1c72c895d67d2f7f98 Mon Sep 17 00:00:00 2001 From: olaru Date: Tue, 21 Jul 2026 18:32:30 +0300 Subject: [PATCH 3/4] FAQ generated for twigs, json-ld and .md files (still not all articles are covered) --- public/css/app.css | 2 +- .../listen-for-android-install-referrer.md | 23 +++ ...ers-in-the-same-app-for-the-same-action.md | 31 ++++ ...vider-bootstrap-modern-php-applications.md | 111 +++++++++++++ ...ifecycle-for-a-mezzio-based-application.md | 67 ++++++++ .../architecture/understanding-middleware.md | 139 ++++++++++++++++ .../best-practice/aptana-set-svn-keywords.md | 38 +++++ ...security-in-dotkernel-headless-platform.md | 135 +++++++++++++++ ...golden-rules-of-professional-php-coding.md | 53 ++++++ .../htaccess-301-redirect-non-www-to-www.md | 39 +++++ ...t-update-delete-statements-with-zend-db.md | 96 +++++++++++ .../sql-queries-using-zend-db-select.md | 131 +++++++++++++++ .../best-practice/subqueries-with-zend-db.md | 74 +++++++++ .../svn-export-in-a-virtual-host.md | 71 ++++++++ ...n-keywords-setup-in-php-ide-zend-studio.md | 77 +++++++++ .../using-like-wildcards-with-zend-db.md | 154 ++++++++++++++++++ ...-client-migration-from-postman-to-bruno.md | 89 ++++++++++ .../api-endpoint-to-collect-client-errors.md | 46 ++++++ ...ntent-negotiation-in-dotkernel-rest-api.md | 98 +++++++++++ .../dotkernel-api-1-0-0-released.md | 72 ++++++++ ...dotkernel-api-client-side-authorization.md | 58 +++++++ ...-cors-implementation-to-zend-expressive.md | 139 ++++++++++++++++ ...g-layer-to-wurfl-in-dotkernel-using-apc.md | 53 ++++++ ...poser-support-in-your-dotkernel-project.md | 107 ++++++++++++ ...browser-detection-in-dotkernel-projects.md | 55 +++++++ ...n-using-cookie-remember-me-in-dotkernel.md | 138 ++++++++++++++++ ...through-bootstrap-of-non-existent-files.md | 37 +++++ ...ching-in-dotkernel-using-zend-framework.md | 72 ++++++++ ...melcase-table-names-in-mysql-on-windows.md | 33 ++++ ...end-certified-engineers-zce-in-our-team.md | 24 +++ .../dotkernel/dotkernel-1-2-2-release.md | 51 ++++++ .../dotkernel/dotkernel-1-3-0-release.md | 64 ++++++++ .../dotkernel/dotkernel-1-3-2-release.md | 51 ++++++ .../dotkernel/dotkernel-1-5-0-released.md | 68 ++++++++ .../dotkernel/dotkernel-1-8-0-lts-released.md | 88 ++++++++++ ...ernel-1-8-1-upgrade-from-1-8-0-released.md | 39 +++++ .../dotkernel/dotkernel-coding-standard.md | 78 +++++++++ ...l-database-naming-conventions-for-mysql.md | 77 +++++++++ ...o-microframework-and-laminas-components.md | 93 +++++++++++ ...-best-choice-for-your-presentation-site.md | 153 +++++++++++++++++ public/readme/dotkernel/dotkernel-on-nginx.md | 81 +++++++++ ...nel-reserved-variable-names-for-caching.md | 53 ++++++ .../dotkernel/dotkernel-template-engine.md | 21 +++ .../dotkernel-version-1-0-in-action.md | 48 ++++++ ...-connections-and-character-set-in-mysql.md | 48 ++++++ .../geoip-city-removed-from-dotkernel.md | 80 +++++++++ .../geoip-ip-address-location-in-dotkernel.md | 110 +++++++++++++ ...rrors-with-dot-errorhandler-and-dot-log.md | 87 ++++++++++ ...ration-of-zend-framework-1-pear-channel.md | 68 ++++++++ .../new-features-in-zend-framework-1-12.md | 58 +++++++ ...tter-and-templates-for-zend-studio-10-1.md | 30 ++++ ...ing-admin-folder-with-htaccess-in-plesk.md | 40 +++++ ...as-mail-with-symfony-mailer-in-dot-mail.md | 98 +++++++++++ ...urfl-cloud-php-library-to-dotkernel-1-6.md | 48 ++++++ ...sing-dot-email-component-and-zend-email.md | 83 ++++++++++ .../dotkernel/templating-in-dotkernel3.md | 49 ++++++ ...ng-dotkernel-with-composer-dependencies.md | 123 ++++++++++++++ .../using-utf8-charset-in-dotkernel.md | 56 +++++++ ...fl-cloud-integration-in-dotkernel-1-6-0.md | 52 ++++++ ...api-license-incompatible-with-dotkernel.md | 50 ++++++ ...nd-framework-integration-into-dotkernel.md | 73 +++++++++ ...th-and-zend-acl-integrated-in-dotkernel.md | 93 +++++++++++ ...end-console-implementation-in-dotkernel.md | 59 +++++++ ...rk-dropped-integration-of-wurfl-adapter.md | 33 ++++ .../zend-registry-usage-in-dotkernel.md | 67 ++++++++ ...dotkernel-refactor-of-dot-session-class.md | 60 +++++++ ...tter-file-for-dotkernel-coding-standard.md | 31 ++++ .../php-8-3-support-in-dotkernel-api.md | 94 +++++++++++ .../php-8-3-support-in-dotkernel-frontend.md | 52 ++++++ ...ry-admin-in-dotkernel-headless-platform.md | 90 ++++++++++ ...the-root-of-dotkernel-headless-platform.md | 72 ++++++++ ...adless-platform-the-whats-hows-and-whys.md | 101 ++++++++++++ ...xecution-in-dotkernel-headless-platform.md | 132 +++++++++++++++ ...maker-generate-common-code-in-dotkernel.md | 126 ++++++++++++++ ...evolution-pattern-versus-api-versioning.md | 100 ++++++++++++ ...sed-one-time-password-totp-in-dotkernel.md | 149 +++++++++++++++++ public/readme/index.md | 85 ++++++++++ .../javascript/javascript-email-validator.md | 34 ++++ ...-versus-lgpl-in-practice-dotkernel-case.md | 60 +++++++ ...provements-psr-15-handlers-vite-phpstan.md | 71 ++++++++ ...ic-routing-using-fastroute-in-dotkernel.md | 86 ++++++++++ ...5-compliant-handlers-in-dotkernel-light.md | 141 ++++++++++++++++ .../end-of-support-for-php-5-2-x-branch.md | 39 +++++ ...oint-arithmetic-why-is-int-0-7-0-1-10-7.md | 99 +++++++++++ .../how-to-upgrade-wamp-to-php-5-3-4.md | 53 ++++++ .../mezzio-app-development-in-wsl2.md | 62 +++++++ ...ased-no-upgrade-possible-for-wampserver.md | 29 ++++ ...ironment-development-staging-production.md | 52 ++++++ .../php-support-back-in-aptana-3-0.md | 47 ++++++ ...ion-using-pdo-and-zend-framework-part-2.md | 65 ++++++++ ...-injection-using-pdo-and-zend-framework.md | 70 ++++++++ src/App/assets/scss/components/_custom.scss | 1 + src/App/templates/app/index.html.twig | 40 ++--- src/App/templates/partial/meta.html.twig | 3 +- ...n-for-android-install-referrer.jsonld.twig | 93 ++++++++--- ...e-same-app-for-the-same-action.jsonld.twig | 79 ++++++--- ...tstrap-modern-php-applications.jsonld.twig | 111 ++++++++++--- ...for-a-mezzio-based-application.jsonld.twig | 111 ++++++++++--- .../understanding-middleware.jsonld.twig | 111 ++++++++++--- .../aptana-set-svn-keywords.jsonld.twig | 87 +++++++--- ...in-dotkernel-headless-platform.jsonld.twig | 111 ++++++++++--- ...les-of-professional-php-coding.jsonld.twig | 103 +++++++++--- ...ss-301-redirect-non-www-to-www.jsonld.twig | 79 ++++++--- ...delete-statements-with-zend-db.jsonld.twig | 67 +++++--- ...l-queries-using-zend-db-select.jsonld.twig | 69 +++++--- .../subqueries-with-zend-db.jsonld.twig | 66 +++++--- .../svn-export-in-a-virtual-host.jsonld.twig | 68 +++++--- ...s-setup-in-php-ide-zend-studio.jsonld.twig | 68 +++++--- ...ng-like-wildcards-with-zend-db.jsonld.twig | 68 +++++--- ...e-fetch-functions-from-zend-db.jsonld.twig | 68 +++++--- ...-a-field-that-record-date-time.jsonld.twig | 68 +++++--- ...is-retiring-consider-it-solved.jsonld.twig | 68 +++++--- ...igration-from-postman-to-bruno.jsonld.twig | 111 ++++++++++--- ...point-to-collect-client-errors.jsonld.twig | 87 +++++++--- ...otiation-in-dotkernel-rest-api.jsonld.twig | 111 ++++++++++--- .../dotkernel-api-1-0-0-released.jsonld.twig | 87 +++++++--- ...-api-client-side-authorization.jsonld.twig | 87 +++++++--- ...-api-server-side-authorization.jsonld.twig | 103 +++++++++--- ...ent-mailchimp-in-dotkernel-api.jsonld.twig | 68 +++++--- ...mplementation-in-dotkernel-api.jsonld.twig | 69 +++++--- ...lementation-to-zend-expressive.jsonld.twig | 68 +++++--- ...o-wurfl-in-dotkernel-using-apc.jsonld.twig | 68 +++++--- ...port-in-your-dotkernel-project.jsonld.twig | 68 +++++--- ...etection-in-dotkernel-projects.jsonld.twig | 67 +++++--- ...ookie-remember-me-in-dotkernel.jsonld.twig | 69 +++++--- ...ootstrap-of-non-existent-files.jsonld.twig | 65 +++++--- ...dotkernel-using-zend-framework.jsonld.twig | 68 +++++--- ...able-names-in-mysql-on-windows.jsonld.twig | 65 +++++--- ...fied-engineers-zce-in-our-team.jsonld.twig | 64 +++++--- ...iguring-the-cache-in-dotkernel.jsonld.twig | 67 +++++--- ...in-laminas-mezzio-applications.jsonld.twig | 68 +++++--- .../dotkernel-1-2-2-release.jsonld.twig | 66 +++++--- .../dotkernel-1-3-0-release.jsonld.twig | 67 +++++--- .../dotkernel-1-3-2-release.jsonld.twig | 66 +++++--- .../dotkernel-1-5-0-released.jsonld.twig | 68 +++++--- .../dotkernel-1-8-0-lts-released.jsonld.twig | 69 +++++--- ...-1-upgrade-from-1-8-0-released.jsonld.twig | 65 +++++--- .../dotkernel-coding-standard.jsonld.twig | 67 +++++--- ...e-naming-conventions-for-mysql.jsonld.twig | 68 +++++--- ...amework-and-laminas-components.jsonld.twig | 68 +++++--- ...ice-for-your-presentation-site.jsonld.twig | 69 +++++--- .../dotkernel/dotkernel-on-nginx.jsonld.twig | 67 +++++--- ...ved-variable-names-for-caching.jsonld.twig | 68 +++++--- .../dotkernel-template-engine.jsonld.twig | 65 +++++--- ...otkernel-version-1-0-in-action.jsonld.twig | 68 +++++--- ...ons-and-character-set-in-mysql.jsonld.twig | 66 +++++--- ...ip-city-removed-from-dotkernel.jsonld.twig | 68 +++++--- ...-address-location-in-dotkernel.jsonld.twig | 68 +++++--- ...h-dot-errorhandler-and-dot-log.jsonld.twig | 69 +++++--- ...integration-in-dotkernel-1-6-0.jsonld.twig | 66 +++++--- ...og-files-by-date-using-dot-log.jsonld.twig | 69 +++++--- ...zend-framework-zend-db-adapter.jsonld.twig | 66 +++++--- ...how-to-use-alerts-in-dotkernel.jsonld.twig | 68 +++++--- ...-api-from-php-5-5-in-dotkernel.jsonld.twig | 68 +++++--- ...-in-zend-server-5-6-on-windows.jsonld.twig | 69 +++++--- ...on-in-zend-server-6-on-windows.jsonld.twig | 67 +++++--- ...-zend-expressive-and-dotkernel.jsonld.twig | 69 +++++--- ...-zend-framework-1-pear-channel.jsonld.twig | 67 +++++--- ...eatures-in-zend-framework-1-12.jsonld.twig | 68 +++++--- ...templates-for-zend-studio-10-1.jsonld.twig | 65 +++++--- ...-folder-with-htaccess-in-plesk.jsonld.twig | 65 +++++--- ...ith-symfony-mailer-in-dot-mail.jsonld.twig | 69 +++++--- ...d-php-library-to-dotkernel-1-6.jsonld.twig | 67 +++++--- ...email-component-and-zend-email.jsonld.twig | 68 +++++--- .../templating-in-dotkernel3.jsonld.twig | 67 +++++--- ...nel-with-composer-dependencies.jsonld.twig | 69 +++++--- ...sing-utf8-charset-in-dotkernel.jsonld.twig | 67 +++++--- ...integration-in-dotkernel-1-6-0.jsonld.twig | 67 +++++--- ...se-incompatible-with-dotkernel.jsonld.twig | 66 +++++--- ...ork-integration-into-dotkernel.jsonld.twig | 68 +++++--- ...nd-acl-integrated-in-dotkernel.jsonld.twig | 68 +++++--- ...le-implementation-in-dotkernel.jsonld.twig | 68 +++++--- ...d-integration-of-wurfl-adapter.jsonld.twig | 65 +++++--- ...nd-registry-usage-in-dotkernel.jsonld.twig | 67 +++++--- ...-refactor-of-dot-session-class.jsonld.twig | 67 +++++--- ...-for-dotkernel-coding-standard.jsonld.twig | 79 ++++++--- ...opment-report-december-11-2017.jsonld.twig | 65 +++++--- .../dotkernel3/dotkernel-admin-v4.jsonld.twig | 69 +++++--- ...ernel-admin-version-3-launched.jsonld.twig | 66 +++++--- ...p-8-3-support-in-dotkernel-api.jsonld.twig | 117 ++++++++++--- ...-support-in-dotkernel-frontend.jsonld.twig | 117 ++++++++++--- ...in-dotkernel-headless-platform.jsonld.twig | 68 +++++--- ...of-dotkernel-headless-platform.jsonld.twig | 117 ++++++++++--- ...atform-the-whats-hows-and-whys.jsonld.twig | 117 ++++++++++--- ...in-dotkernel-headless-platform.jsonld.twig | 125 +++++++++++--- ...erate-common-code-in-dotkernel.jsonld.twig | 125 +++++++++++--- ...-pattern-versus-api-versioning.jsonld.twig | 109 ++++++++++--- ...ime-password-totp-in-dotkernel.jsonld.twig | 117 ++++++++++--- ...in-dotkernel-headless-platform.jsonld.twig | 68 +++++--- ...gresql-native-uuid-and-php-8-5.jsonld.twig | 68 +++++--- ...in-dotkernel-using-mezzio-cors.jsonld.twig | 68 +++++--- ...dmin-accounts-in-dotkernel-api.jsonld.twig | 68 +++++--- ...migrations-and-how-to-use-them.jsonld.twig | 68 +++++--- ...-cache-in-mezzio-and-dotkernel.jsonld.twig | 68 +++++--- ...without-dropping-custom-tables.jsonld.twig | 67 +++++--- ...-an-generic-laminas-mezzio-app.jsonld.twig | 67 +++++--- ...hp-mariadb-composer-phpmyadmin.jsonld.twig | 68 +++++--- .../javascript-email-validator.jsonld.twig | 66 +++++--- ...gpl-in-practice-dotkernel-case.jsonld.twig | 68 +++++--- ...s-psr-15-handlers-vite-phpstan.jsonld.twig | 68 +++++--- ...g-using-fastroute-in-dotkernel.jsonld.twig | 69 +++++--- ...nt-handlers-in-dotkernel-light.jsonld.twig | 68 +++++--- ...he-mariadb-composer-phpmyadmin.jsonld.twig | 68 +++++--- ...f-support-for-php-5-2-x-branch.jsonld.twig | 66 +++++--- ...hmetic-why-is-int-0-7-0-1-10-7.jsonld.twig | 68 +++++--- ...w-to-upgrade-wamp-to-php-5-3-4.jsonld.twig | 68 +++++--- ...mezzio-app-development-in-wsl2.jsonld.twig | 69 +++++--- ...pgrade-possible-for-wampserver.jsonld.twig | 65 +++++--- ...development-staging-production.jsonld.twig | 68 +++++--- ...php-support-back-in-aptana-3-0.jsonld.twig | 66 +++++--- ...-pdo-and-zend-framework-part-2.jsonld.twig | 69 +++++--- ...n-using-pdo-and-zend-framework.jsonld.twig | 69 +++++--- .../templates/page/author-resource.html.twig | 4 +- ...ten-for-android-install-referrer.html.twig | 23 ++- ...the-same-app-for-the-same-action.html.twig | 17 ++ ...ootstrap-modern-php-applications.html.twig | 41 +++++ ...e-for-a-mezzio-based-application.html.twig | 41 +++++ .../understanding-middleware.html.twig | 41 +++++ .../aptana-set-svn-keywords.html.twig | 23 +++ ...y-in-dotkernel-headless-platform.html.twig | 41 +++++ ...rules-of-professional-php-coding.html.twig | 35 ++++ ...cess-301-redirect-non-www-to-www.html.twig | 17 ++ ...e-delete-statements-with-zend-db.html.twig | 29 ++++ ...sql-queries-using-zend-db-select.html.twig | 41 +++++ .../subqueries-with-zend-db.html.twig | 23 +++ .../svn-export-in-a-virtual-host.html.twig | 35 ++++ ...rds-setup-in-php-ide-zend-studio.html.twig | 35 ++++ ...sing-like-wildcards-with-zend-db.html.twig | 35 ++++ ...the-fetch-functions-from-zend-db.html.twig | 35 ++++ ...on-a-field-that-record-date-time.html.twig | 35 ++++ ...c-is-retiring-consider-it-solved.html.twig | 35 ++++ ...-migration-from-postman-to-bruno.html.twig | 41 +++++ ...ndpoint-to-collect-client-errors.html.twig | 23 +++ ...egotiation-in-dotkernel-rest-api.html.twig | 41 +++++ .../dotkernel-api-1-0-0-released.html.twig | 23 +++ ...el-api-client-side-authorization.html.twig | 23 +++ ...el-api-server-side-authorization.html.twig | 35 ++++ ...ement-mailchimp-in-dotkernel-api.html.twig | 35 ++++ ...-implementation-in-dotkernel-api.html.twig | 41 +++++ ...mplementation-to-zend-expressive.html.twig | 35 ++++ ...-to-wurfl-in-dotkernel-using-apc.html.twig | 35 ++++ ...upport-in-your-dotkernel-project.html.twig | 35 ++++ ...-detection-in-dotkernel-projects.html.twig | 29 ++++ ...-cookie-remember-me-in-dotkernel.html.twig | 41 +++++ ...-bootstrap-of-non-existent-files.html.twig | 17 ++ ...n-dotkernel-using-zend-framework.html.twig | 35 ++++ ...-table-names-in-mysql-on-windows.html.twig | 17 ++ ...tified-engineers-zce-in-our-team.html.twig | 11 ++ ...nfiguring-the-cache-in-dotkernel.html.twig | 29 ++++ ...y-in-laminas-mezzio-applications.html.twig | 35 ++++ ...obile-devices-in-dotkernel-1-6-0.html.twig | 35 ++++ ...rfl-redirect-for-mobile-browsers.html.twig | 17 ++ ...tion-dotkernel-1-and-dotkernel-3.html.twig | 35 ++++ ...ctrine-cache-using-symfony-cache.html.twig | 41 +++++ ...enum-implementation-in-dotkernel.html.twig | 41 +++++ ...services-north-american-relaunch.html.twig | 17 ++ .../dotkernel-1-2-0-release.html.twig | 35 ++++ .../dotkernel-1-2-2-release.html.twig | 23 +++ .../dotkernel-1-3-0-release.html.twig | 29 ++++ .../dotkernel-1-3-2-release.html.twig | 23 +++ .../dotkernel-1-5-0-released.html.twig | 35 ++++ .../dotkernel-1-8-0-lts-released.html.twig | 41 +++++ ...-8-1-upgrade-from-1-8-0-released.html.twig | 17 ++ .../dotkernel-coding-standard.html.twig | 29 ++++ ...ase-naming-conventions-for-mysql.html.twig | 35 ++++ ...framework-and-laminas-components.html.twig | 35 ++++ ...hoice-for-your-presentation-site.html.twig | 41 +++++ .../dotkernel/dotkernel-on-nginx.html.twig | 29 ++++ ...erved-variable-names-for-caching.html.twig | 35 ++++ .../dotkernel-template-engine.html.twig | 17 ++ .../dotkernel-version-1-0-in-action.html.twig | 35 ++++ ...tions-and-character-set-in-mysql.html.twig | 23 +++ ...eoip-city-removed-from-dotkernel.html.twig | 35 ++++ ...ip-address-location-in-dotkernel.html.twig | 35 ++++ ...ith-dot-errorhandler-and-dot-log.html.twig | 41 +++++ ...s-integration-in-dotkernel-1-6-0.html.twig | 23 +++ ...-log-files-by-date-using-dot-log.html.twig | 41 +++++ ...h-zend-framework-zend-db-adapter.html.twig | 23 +++ .../how-to-use-alerts-in-dotkernel.html.twig | 35 ++++ ...ng-api-from-php-5-5-in-dotkernel.html.twig | 35 ++++ ...on-in-zend-server-5-6-on-windows.html.twig | 43 ++++- ...sion-in-zend-server-6-on-windows.html.twig | 29 ++++ ...in-zend-expressive-and-dotkernel.html.twig | 42 +++++ ...e-of-wurfl-xml-file-in-dotkernel.html.twig | 29 ++++ ...of-zend-framework-1-pear-channel.html.twig | 29 ++++ ...-features-in-zend-framework-1-12.html.twig | 35 ++++ ...d-templates-for-zend-studio-10-1.html.twig | 17 ++ ...in-folder-with-htaccess-in-plesk.html.twig | 17 ++ ...-with-symfony-mailer-in-dot-mail.html.twig | 41 +++++ ...oud-php-library-to-dotkernel-1-6.html.twig | 29 ++++ ...t-email-component-and-zend-email.html.twig | 35 ++++ .../templating-in-dotkernel3.html.twig | 29 ++++ ...ernel-with-composer-dependencies.html.twig | 41 +++++ .../using-utf8-charset-in-dotkernel.html.twig | 29 ++++ ...d-integration-in-dotkernel-1-6-0.html.twig | 29 ++++ ...ense-incompatible-with-dotkernel.html.twig | 23 +++ ...ework-integration-into-dotkernel.html.twig | 35 ++++ ...zend-acl-integrated-in-dotkernel.html.twig | 35 ++++ ...sole-implementation-in-dotkernel.html.twig | 35 ++++ ...ped-integration-of-wurfl-adapter.html.twig | 17 ++ ...zend-registry-usage-in-dotkernel.html.twig | 29 ++++ ...el-refactor-of-dot-session-class.html.twig | 29 ++++ ...le-for-dotkernel-coding-standard.html.twig | 17 ++ ...elopment-report-december-11-2017.html.twig | 17 ++ .../dotkernel3/dotkernel-admin-v4.html.twig | 41 +++++ ...tkernel-admin-version-3-launched.html.twig | 23 +++ ...-api-architecture-and-components.html.twig | 41 +++++ ...rnel-frontend-version-3-launched.html.twig | 23 +++ ...rnel3-stable-release-version-1-0.html.twig | 35 ++++ ...p-8-3-support-in-dotkernel-admin.html.twig | 35 ++++ ...php-8-3-support-in-dotkernel-api.html.twig | 35 ++++ ...-3-support-in-dotkernel-frontend.html.twig | 35 ++++ ...n-in-dotkernel-headless-platform.html.twig | 35 ++++ ...t-of-dotkernel-headless-platform.html.twig | 35 ++++ ...platform-the-whats-hows-and-whys.html.twig | 35 ++++ ...n-in-dotkernel-headless-platform.html.twig | 41 +++++ ...enerate-common-code-in-dotkernel.html.twig | 41 +++++ ...on-pattern-versus-api-versioning.html.twig | 29 ++++ ...-time-password-totp-in-dotkernel.html.twig | 35 ++++ ...e-in-dotkernel-headless-platform.html.twig | 35 ++++ ...stgresql-native-uuid-and-php-8-5.html.twig | 35 ++++ ...p-in-dotkernel-using-mezzio-cors.html.twig | 35 ++++ ...-admin-accounts-in-dotkernel-api.html.twig | 35 ++++ ...e-migrations-and-how-to-use-them.html.twig | 35 ++++ ...ne-cache-in-mezzio-and-dotkernel.html.twig | 35 ++++ ...n-without-dropping-custom-tables.html.twig | 29 ++++ ...in-an-generic-laminas-mezzio-app.html.twig | 29 ++++ ...-php-mariadb-composer-phpmyadmin.html.twig | 35 ++++ ...s-in-dotkernel-api-using-dot-cli.html.twig | 35 ++++ ...xpressive-2-to-zend-expressive-3.html.twig | 41 +++++ ...ith-dot-cli-based-on-laminas-cli.html.twig | 35 ++++ ...documentation-in-dotkernel-api-3.html.twig | 35 ++++ ...e-urlgenerator-work-in-fastroute.html.twig | 29 ++++ ...is-cross-origin-token-redemption.html.twig | 29 ++++ .../what-is-psr-7-and-how-to-use-it.html.twig | 35 ++++ ...p-html-css-javascript-editor-ide.html.twig | 23 +++ .../javascript/intro-to-jquery.html.twig | 35 ++++ .../javascript-email-validator.html.twig | 23 +++ ...-lgpl-in-practice-dotkernel-case.html.twig | 35 ++++ ...nts-psr-15-handlers-vite-phpstan.html.twig | 35 ++++ ...ing-using-fastroute-in-dotkernel.html.twig | 41 +++++ ...iant-handlers-in-dotkernel-light.html.twig | 35 ++++ ...ache-mariadb-composer-phpmyadmin.html.twig | 35 ++++ ...-of-support-for-php-5-2-x-branch.html.twig | 23 +++ ...ithmetic-why-is-int-0-7-0-1-10-7.html.twig | 35 ++++ ...how-to-upgrade-wamp-to-php-5-3-4.html.twig | 35 ++++ .../mezzio-app-development-in-wsl2.html.twig | 41 +++++ ...-upgrade-possible-for-wampserver.html.twig | 17 ++ ...t-development-staging-production.html.twig | 35 ++++ .../php-support-back-in-aptana-3-0.html.twig | 23 +++ ...ng-pdo-and-zend-framework-part-2.html.twig | 41 +++++ ...ion-using-pdo-and-zend-framework.html.twig | 41 +++++ ...ql-server-on-plesk-based-servers.html.twig | 29 ++++ ...sis-replacing-psalm-with-phpstan.html.twig | 35 ++++ ...nect-to-dotkernel-tracker-mantis.html.twig | 29 ++++ ...-php-7-express-in-zend-studio-13.html.twig | 29 ++++ ...l-ignore-patterns-in-zend-studio.html.twig | 17 ++ ...tified-engineer-in-dotboost-team.html.twig | 17 ++ ...erver-5-5-quick-setup-on-windows.html.twig | 23 +++ ...lling-pear-packages-with-php-7-2.html.twig | 29 ++++ ...ntl-php-extension-problem-solved.html.twig | 35 ++++ ...y-how-to-setup-phpcs-in-phpstorm.html.twig | 35 ++++ ...ar-a-very-helpfull-zf-debug-tool.html.twig | 17 ++ ...ear-channel-for-zend-framework-1.html.twig | 23 +++ ...l-php-api-libraries-gpl-versions.html.twig | 17 ++ ...2-4-released-with-security-fixes.html.twig | 23 +++ src/Blog/templates/page/blog.html.twig | 4 +- src/Blog/templates/page/categories.html.twig | 2 +- .../page/category-resource.html.twig | 4 +- 369 files changed, 17793 insertions(+), 2512 deletions(-) create mode 100644 public/readme/android/listen-for-android-install-referrer.md create mode 100644 public/readme/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.md create mode 100644 public/readme/architecture/configprovider-bootstrap-modern-php-applications.md create mode 100644 public/readme/architecture/request-lifecycle-for-a-mezzio-based-application.md create mode 100644 public/readme/architecture/understanding-middleware.md create mode 100644 public/readme/best-practice/aptana-set-svn-keywords.md create mode 100644 public/readme/best-practice/basic-security-in-dotkernel-headless-platform.md create mode 100644 public/readme/best-practice/golden-rules-of-professional-php-coding.md create mode 100644 public/readme/best-practice/htaccess-301-redirect-non-www-to-www.md create mode 100644 public/readme/best-practice/insert-update-delete-statements-with-zend-db.md create mode 100644 public/readme/best-practice/sql-queries-using-zend-db-select.md create mode 100644 public/readme/best-practice/subqueries-with-zend-db.md create mode 100644 public/readme/best-practice/svn-export-in-a-virtual-host.md create mode 100644 public/readme/best-practice/svn-keywords-setup-in-php-ide-zend-studio.md create mode 100644 public/readme/best-practice/using-like-wildcards-with-zend-db.md create mode 100644 public/readme/dotkernel-api/api-client-migration-from-postman-to-bruno.md create mode 100644 public/readme/dotkernel-api/api-endpoint-to-collect-client-errors.md create mode 100644 public/readme/dotkernel-api/content-negotiation-in-dotkernel-rest-api.md create mode 100644 public/readme/dotkernel-api/dotkernel-api-1-0-0-released.md create mode 100644 public/readme/dotkernel-api/dotkernel-api-client-side-authorization.md create mode 100644 public/readme/dotkernel/adding-a-cors-implementation-to-zend-expressive.md create mode 100644 public/readme/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.md create mode 100644 public/readme/dotkernel/adding-composer-support-in-your-dotkernel-project.md create mode 100644 public/readme/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.md create mode 100644 public/readme/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.md create mode 100644 public/readme/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.md create mode 100644 public/readme/dotkernel/caching-in-dotkernel-using-zend-framework.md create mode 100644 public/readme/dotkernel/camelcase-table-names-in-mysql-on-windows.md create mode 100644 public/readme/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.md create mode 100644 public/readme/dotkernel/dotkernel-1-2-2-release.md create mode 100644 public/readme/dotkernel/dotkernel-1-3-0-release.md create mode 100644 public/readme/dotkernel/dotkernel-1-3-2-release.md create mode 100644 public/readme/dotkernel/dotkernel-1-5-0-released.md create mode 100644 public/readme/dotkernel/dotkernel-1-8-0-lts-released.md create mode 100644 public/readme/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.md create mode 100644 public/readme/dotkernel/dotkernel-coding-standard.md create mode 100644 public/readme/dotkernel/dotkernel-database-naming-conventions-for-mysql.md create mode 100644 public/readme/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.md create mode 100644 public/readme/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.md create mode 100644 public/readme/dotkernel/dotkernel-on-nginx.md create mode 100644 public/readme/dotkernel/dotkernel-reserved-variable-names-for-caching.md create mode 100644 public/readme/dotkernel/dotkernel-template-engine.md create mode 100644 public/readme/dotkernel/dotkernel-version-1-0-in-action.md create mode 100644 public/readme/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.md create mode 100644 public/readme/dotkernel/geoip-city-removed-from-dotkernel.md create mode 100644 public/readme/dotkernel/geoip-ip-address-location-in-dotkernel.md create mode 100644 public/readme/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.md create mode 100644 public/readme/dotkernel/migration-of-zend-framework-1-pear-channel.md create mode 100644 public/readme/dotkernel/new-features-in-zend-framework-1-12.md create mode 100644 public/readme/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.md create mode 100644 public/readme/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.md create mode 100644 public/readme/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.md create mode 100644 public/readme/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.md create mode 100644 public/readme/dotkernel/sending-emails-using-dot-email-component-and-zend-email.md create mode 100644 public/readme/dotkernel/templating-in-dotkernel3.md create mode 100644 public/readme/dotkernel/using-dotkernel-with-composer-dependencies.md create mode 100644 public/readme/dotkernel/using-utf8-charset-in-dotkernel.md create mode 100644 public/readme/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.md create mode 100644 public/readme/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.md create mode 100644 public/readme/dotkernel/wurfl-zend-framework-integration-into-dotkernel.md create mode 100644 public/readme/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.md create mode 100644 public/readme/dotkernel/zend-console-implementation-in-dotkernel.md create mode 100644 public/readme/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.md create mode 100644 public/readme/dotkernel/zend-registry-usage-in-dotkernel.md create mode 100644 public/readme/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.md create mode 100644 public/readme/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.md create mode 100644 public/readme/dotkernel3/php-8-3-support-in-dotkernel-api.md create mode 100644 public/readme/dotkernel3/php-8-3-support-in-dotkernel-frontend.md create mode 100644 public/readme/headless-platform/complementary-admin-in-dotkernel-headless-platform.md create mode 100644 public/readme/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.md create mode 100644 public/readme/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.md create mode 100644 public/readme/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.md create mode 100644 public/readme/headless-platform/dotmaker-generate-common-code-in-dotkernel.md create mode 100644 public/readme/headless-platform/evolution-pattern-versus-api-versioning.md create mode 100644 public/readme/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.md create mode 100644 public/readme/index.md create mode 100644 public/readme/javascript/javascript-email-validator.md create mode 100644 public/readme/licensing/mit-versus-lgpl-in-practice-dotkernel-case.md create mode 100644 public/readme/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.md create mode 100644 public/readme/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.md create mode 100644 public/readme/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.md create mode 100644 public/readme/php-development/end-of-support-for-php-5-2-x-branch.md create mode 100644 public/readme/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.md create mode 100644 public/readme/php-development/how-to-upgrade-wamp-to-php-5-3-4.md create mode 100644 public/readme/php-development/mezzio-app-development-in-wsl2.md create mode 100644 public/readme/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.md create mode 100644 public/readme/php-development/php-environment-development-staging-production.md create mode 100644 public/readme/php-development/php-support-back-in-aptana-3-0.md create mode 100644 public/readme/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.md create mode 100644 public/readme/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.md diff --git a/public/css/app.css b/public/css/app.css index b97efa80..de68b2d4 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -1 +1 @@ -:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13, 110, 253;--bs-secondary-rgb:108, 117, 125;--bs-success-rgb:25, 135, 84;--bs-info-rgb:13, 202, 240;--bs-warning-rgb:255, 193, 7;--bs-danger-rgb:220, 53, 69;--bs-light-rgb:248, 249, 250;--bs-dark-rgb:33, 37, 41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255, 255, 255;--bs-black-rgb:0, 0, 0;--bs-font-sans-serif:system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient:linear-gradient(180deg, #ffffff26, #fff0);--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33, 37, 41;--bs-body-bg:#fff;--bs-body-bg-rgb:255, 255, 255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0, 0, 0;--bs-secondary-color:#212529bf;--bs-secondary-color-rgb:33, 37, 41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233, 236, 239;--bs-tertiary-color:#21252980;--bs-tertiary-color-rgb:33, 37, 41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248, 249, 250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13, 110, 253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10, 88, 202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:#0000002d;--bs-border-radius:.375rem;--bs-border-radius-sm:.25rem;--bs-border-radius-lg:.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 .5rem 1rem #00000026;--bs-box-shadow-sm:0 .125rem .25rem #00000013;--bs-box-shadow-lg:0 1rem 3rem #0000002d;--bs-box-shadow-inset:inset 0 1px 2px #00000013;--bs-focus-ring-width:.25rem;--bs-focus-ring-opacity:.25;--bs-focus-ring-color:#0d6efd40;--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222, 226, 230;--bs-body-bg:#212529;--bs-body-bg-rgb:33, 37, 41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255, 255, 255;--bs-secondary-color:#dee2e6bf;--bs-secondary-color-rgb:222, 226, 230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52, 58, 64;--bs-tertiary-color:#dee2e680;--bs-tertiary-color-rgb:222, 226, 230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43, 48, 53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110, 168, 254;--bs-link-hover-color-rgb:139, 185, 254;--bs-code-color:#e685b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:#ffffff26;--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,:before,:after{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:#0000;margin:0}hr{color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25;margin:1rem 0}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{color:var(--bs-heading-color);margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:calc(1.375rem + 1.5vw)}@media (width>=1200px){h1,.h1{font-size:2.5rem}}h2,.h2{font-size:calc(1.325rem + .9vw)}@media (width>=1200px){h2,.h2{font-size:2rem}}h3,.h3{font-size:calc(1.3rem + .6vw)}@media (width>=1200px){h3,.h3{font-size:1.75rem}}h4,.h4{font-size:calc(1.275rem + .3vw)}@media (width>=1200px){h4,.h4{font-size:1.5rem}}h5,.h5{font-size:1.25rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}address{font-style:normal;line-height:inherit;margin-bottom:1rem}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small,.small{font-size:.875em}mark,.mark{color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg);padding:.1875em}sub,sup{vertical-align:baseline;font-size:.75em;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{margin-top:0;margin-bottom:1rem;font-size:.875em;display:block;overflow:auto}pre code{font-size:inherit;color:inherit;word-break:normal}code{color:var(--bs-code-color);word-wrap:break-word;font-size:.875em}a>code{color:inherit}kbd{color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem;padding:.1875rem .375rem;font-size:.875em}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{color:var(--bs-secondary-color);text-align:left;padding-top:.5rem;padding-bottom:.5rem}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{border-style:none;padding:0}textarea{resize:vertical}fieldset{border:0;min-width:0;margin:0;padding:0}legend{float:left;width:100%;line-height:inherit;margin-bottom:.5rem;padding:0;font-size:calc(1.275rem + .3vw)}@media (width>=1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit-text{padding:0}::-webkit-datetime-edit-minute{padding:0}::-webkit-datetime-edit-hour-field{padding:0}::-webkit-datetime-edit-day-field{padding:0}::-webkit-datetime-edit-month-field{padding:0}::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button{cursor:pointer;filter:grayscale()}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{cursor:pointer;display:list-item}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-6{font-size:2.5rem}}.list-unstyled,.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{text-transform:uppercase;font-size:.875em}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{color:#6c757d;margin-top:-1rem;margin-bottom:1rem;font-size:.875em}.blockquote-footer:before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto;padding:.25rem}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{color:var(--bs-secondary-color);font-size:.875em}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-left:auto;margin-right:auto}@media (width>=576px){.container-sm,.container{max-width:540px}}@media (width>=768px){.container-md,.container-sm,.container{max-width:720px}}@media (width>=992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media (width>=1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media (width>=1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x));flex-wrap:wrap;display:flex}.row>*{width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y);flex-shrink:0}.col{flex:1 0 0}.row-cols-auto>*{flex:none;width:auto}.row-cols-1>*{flex:none;width:100%}.row-cols-2>*{flex:none;width:50%}.row-cols-3>*{flex:none;width:33.3333%}.row-cols-4>*{flex:none;width:25%}.row-cols-5>*{flex:none;width:20%}.row-cols-6>*{flex:none;width:16.6667%}.col-auto{flex:none;width:auto}.col-1{flex:none;width:8.33333%}.col-2{flex:none;width:16.6667%}.col-3{flex:none;width:25%}.col-4{flex:none;width:33.3333%}.col-5{flex:none;width:41.6667%}.col-6{flex:none;width:50%}.col-7{flex:none;width:58.3333%}.col-8{flex:none;width:66.6667%}.col-9{flex:none;width:75%}.col-10{flex:none;width:83.3333%}.col-11{flex:none;width:91.6667%}.col-12{flex:none;width:100%}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.6667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333%}.offset-5{margin-left:41.6667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333%}.offset-8{margin-left:66.6667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333%}.offset-11{margin-left:91.6667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:.25rem}.g-1,.gy-1{--bs-gutter-y:.25rem}.g-2,.gx-2{--bs-gutter-x:.5rem}.g-2,.gy-2{--bs-gutter-y:.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (width>=576px){.col-sm{flex:1 0 0}.row-cols-sm-auto>*{flex:none;width:auto}.row-cols-sm-1>*{flex:none;width:100%}.row-cols-sm-2>*{flex:none;width:50%}.row-cols-sm-3>*{flex:none;width:33.3333%}.row-cols-sm-4>*{flex:none;width:25%}.row-cols-sm-5>*{flex:none;width:20%}.row-cols-sm-6>*{flex:none;width:16.6667%}.col-sm-auto{flex:none;width:auto}.col-sm-1{flex:none;width:8.33333%}.col-sm-2{flex:none;width:16.6667%}.col-sm-3{flex:none;width:25%}.col-sm-4{flex:none;width:33.3333%}.col-sm-5{flex:none;width:41.6667%}.col-sm-6{flex:none;width:50%}.col-sm-7{flex:none;width:58.3333%}.col-sm-8{flex:none;width:66.6667%}.col-sm-9{flex:none;width:75%}.col-sm-10{flex:none;width:83.3333%}.col-sm-11{flex:none;width:91.6667%}.col-sm-12{flex:none;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.6667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333%}.offset-sm-5{margin-left:41.6667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333%}.offset-sm-8{margin-left:66.6667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333%}.offset-sm-11{margin-left:91.6667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (width>=768px){.col-md{flex:1 0 0}.row-cols-md-auto>*{flex:none;width:auto}.row-cols-md-1>*{flex:none;width:100%}.row-cols-md-2>*{flex:none;width:50%}.row-cols-md-3>*{flex:none;width:33.3333%}.row-cols-md-4>*{flex:none;width:25%}.row-cols-md-5>*{flex:none;width:20%}.row-cols-md-6>*{flex:none;width:16.6667%}.col-md-auto{flex:none;width:auto}.col-md-1{flex:none;width:8.33333%}.col-md-2{flex:none;width:16.6667%}.col-md-3{flex:none;width:25%}.col-md-4{flex:none;width:33.3333%}.col-md-5{flex:none;width:41.6667%}.col-md-6{flex:none;width:50%}.col-md-7{flex:none;width:58.3333%}.col-md-8{flex:none;width:66.6667%}.col-md-9{flex:none;width:75%}.col-md-10{flex:none;width:83.3333%}.col-md-11{flex:none;width:91.6667%}.col-md-12{flex:none;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.6667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333%}.offset-md-5{margin-left:41.6667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333%}.offset-md-8{margin-left:66.6667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333%}.offset-md-11{margin-left:91.6667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (width>=992px){.col-lg{flex:1 0 0}.row-cols-lg-auto>*{flex:none;width:auto}.row-cols-lg-1>*{flex:none;width:100%}.row-cols-lg-2>*{flex:none;width:50%}.row-cols-lg-3>*{flex:none;width:33.3333%}.row-cols-lg-4>*{flex:none;width:25%}.row-cols-lg-5>*{flex:none;width:20%}.row-cols-lg-6>*{flex:none;width:16.6667%}.col-lg-auto{flex:none;width:auto}.col-lg-1{flex:none;width:8.33333%}.col-lg-2{flex:none;width:16.6667%}.col-lg-3{flex:none;width:25%}.col-lg-4{flex:none;width:33.3333%}.col-lg-5{flex:none;width:41.6667%}.col-lg-6{flex:none;width:50%}.col-lg-7{flex:none;width:58.3333%}.col-lg-8{flex:none;width:66.6667%}.col-lg-9{flex:none;width:75%}.col-lg-10{flex:none;width:83.3333%}.col-lg-11{flex:none;width:91.6667%}.col-lg-12{flex:none;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.6667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333%}.offset-lg-5{margin-left:41.6667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333%}.offset-lg-8{margin-left:66.6667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333%}.offset-lg-11{margin-left:91.6667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (width>=1200px){.col-xl{flex:1 0 0}.row-cols-xl-auto>*{flex:none;width:auto}.row-cols-xl-1>*{flex:none;width:100%}.row-cols-xl-2>*{flex:none;width:50%}.row-cols-xl-3>*{flex:none;width:33.3333%}.row-cols-xl-4>*{flex:none;width:25%}.row-cols-xl-5>*{flex:none;width:20%}.row-cols-xl-6>*{flex:none;width:16.6667%}.col-xl-auto{flex:none;width:auto}.col-xl-1{flex:none;width:8.33333%}.col-xl-2{flex:none;width:16.6667%}.col-xl-3{flex:none;width:25%}.col-xl-4{flex:none;width:33.3333%}.col-xl-5{flex:none;width:41.6667%}.col-xl-6{flex:none;width:50%}.col-xl-7{flex:none;width:58.3333%}.col-xl-8{flex:none;width:66.6667%}.col-xl-9{flex:none;width:75%}.col-xl-10{flex:none;width:83.3333%}.col-xl-11{flex:none;width:91.6667%}.col-xl-12{flex:none;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.6667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333%}.offset-xl-5{margin-left:41.6667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333%}.offset-xl-8{margin-left:66.6667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333%}.offset-xl-11{margin-left:91.6667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (width>=1400px){.col-xxl{flex:1 0 0}.row-cols-xxl-auto>*{flex:none;width:auto}.row-cols-xxl-1>*{flex:none;width:100%}.row-cols-xxl-2>*{flex:none;width:50%}.row-cols-xxl-3>*{flex:none;width:33.3333%}.row-cols-xxl-4>*{flex:none;width:25%}.row-cols-xxl-5>*{flex:none;width:20%}.row-cols-xxl-6>*{flex:none;width:16.6667%}.col-xxl-auto{flex:none;width:auto}.col-xxl-1{flex:none;width:8.33333%}.col-xxl-2{flex:none;width:16.6667%}.col-xxl-3{flex:none;width:25%}.col-xxl-4{flex:none;width:33.3333%}.col-xxl-5{flex:none;width:41.6667%}.col-xxl-6{flex:none;width:50%}.col-xxl-7{flex:none;width:58.3333%}.col-xxl-8{flex:none;width:66.6667%}.col-xxl-9{flex:none;width:75%}.col-xxl-10{flex:none;width:83.3333%}.col-xxl-11{flex:none;width:91.6667%}.col-xxl-12{flex:none;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333%}.offset-xxl-2{margin-left:16.6667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.3333%}.offset-xxl-5{margin-left:41.6667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.3333%}.offset-xxl-8{margin-left:66.6667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.3333%}.offset-xxl-11{margin-left:91.6667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-emphasis-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-emphasis-color);--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), .05);--bs-table-active-color:var(--bs-emphasis-color);--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), .1);--bs-table-hover-color:var(--bs-emphasis-color);--bs-table-hover-bg:rgba(var(--bs-emphasis-color-rgb), .075);vertical-align:top;border-color:var(--bs-table-border-color);width:100%;margin-bottom:1rem}.table>:not(caption)>*>*{color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)));padding:.5rem}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*,.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#a5b5cc;--bs-table-striped-bg:#c4d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe5;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#b4b6b7;--bs-table-striped-bg:#d6d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbcdce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#a7b9b1;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#a5c4ca;--bs-table-striped-bg:#c4e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#ccc2a4;--bs-table-striped-bg:#f2e6c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dab9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece0be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#c6acae;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc1c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7c9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#c6c7c8;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#4d5154;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{-webkit-overflow-scrolling:touch;overflow-x:auto}@media (width<=575.98px){.table-responsive-sm{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=767.98px){.table-responsive-md{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=991.98px){.table-responsive-lg{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=1199.98px){.table-responsive-xl{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=1399.98px){.table-responsive-xxl{-webkit-overflow-scrolling:touch;overflow-x:auto}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));font-size:inherit;margin-bottom:0;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{color:var(--bs-secondary-color);margin-top:.25rem;font-size:.875em}.form-control{width:100%;color:var(--bs-body-color);appearance:none;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-clip:padding-box;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{padding:0;display:block}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::file-selector-button{margin:-.375rem -.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;margin-inline-end:.75rem;padding:.375rem .75rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{width:100%;color:var(--bs-body-color);border:solid #0000;border-width:var(--bs-border-width) 0;background-color:#0000;margin-bottom:0;padding:.375rem 0;line-height:1.5;display:block}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-left:0;padding-right:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));border-radius:var(--bs-border-radius-sm);padding:.25rem .5rem;font-size:.875rem}.form-control-sm::file-selector-button{margin:-.25rem -.5rem;margin-inline-end:.5rem;padding:.25rem .5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));border-radius:var(--bs-border-radius-lg);padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::file-selector-button{margin:-.5rem -1rem;margin-inline-end:1rem;padding:.5rem 1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border-radius:var(--bs-border-radius);border:0!important}.form-control-color::-webkit-color-swatch{border-radius:var(--bs-border-radius);border:0!important}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");width:100%;color:var(--bs-body-color);appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img), var(--bs-form-select-bg-icon,none);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-position:right .75rem center;background-repeat:no-repeat;background-size:16px 12px;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-select[multiple],.form-select[size]:not([size="1"]){background-image:none;padding-right:.75rem}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:#0000;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{border-radius:var(--bs-border-radius-sm);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{border-radius:var(--bs-border-radius-lg);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{min-height:1.5rem;margin-bottom:.125rem;padding-left:1.5em;display:block}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{text-align:right;padding-left:0;padding-right:1.5em}.form-check-reverse .form-check-input{float:right;margin-left:0;margin-right:-1.5em}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);vertical-align:top;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);border:var(--bs-border-width) solid var(--bs-border-color);-webkit-print-color-adjust:exact;print-color-adjust:exact;background-position:50%;background-repeat:no-repeat;background-size:contain;flex-shrink:0;width:1em;height:1em;margin-top:.25em}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");background-color:#0d6efd;border-color:#0d6efd}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input:disabled~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-image:var(--bs-form-switch-bg);background-position:0;border-radius:2em;width:2em;margin-left:-2.5em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28134, 182.5, 254%29'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");background-position:100%}.form-switch.form-check-reverse{padding-left:0;padding-right:2.5em}.form-switch.form-check-reverse .form-check-input{margin-left:0;margin-right:-2.5em}.form-check-inline{margin-right:1rem;display:inline-block}.btn-check{clip:rect(0, 0, 0, 0);pointer-events:none;position:absolute}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{appearance:none;background-color:#0000;width:100%;height:1.5rem;padding:0}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem #0d6efd40}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem #0d6efd40}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;width:1rem;height:1rem;margin-top:-.25rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{color:#0000;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:#0000;border-radius:1rem;width:100%;height:.5rem}.form-range::-moz-range-thumb{appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;width:1rem;height:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{color:#0000;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:#0000;border-radius:1rem;width:100%;height:.5rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{z-index:2;max-width:100%;height:100%;color:rgba(var(--bs-body-color-rgb), .65);text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;padding:1rem .75rem;transition:opacity .1s ease-in-out,transform .1s ease-in-out;position:absolute;top:0;left:0;overflow:hidden}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:#0000}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),:-webkit-any(.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),:is(.form-floating>.form-control:autofill,.form-floating>.form-control-plaintext:autofill){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem;padding-left:.75rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-control-plaintext~label,.form-floating>.form-select~label{transform:scale(.85)translateY(-.5rem)translate(.15rem)}.form-floating>.form-control:-webkit-autofill~label{transform:scale(.85)translateY(-.5rem)translate(.15rem)}.form-floating>textarea:focus~label:after,.form-floating>textarea:not(:placeholder-shown)~label:after{z-index:-1;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);height:1.5em;position:absolute;inset:1rem .375rem}.form-floating>textarea:disabled~label:after{background-color:var(--bs-secondary-bg)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>:disabled~label,.form-floating>.form-control:disabled~label{color:#6c757d}.input-group{flex-wrap:wrap;align-items:stretch;width:100%;display:flex;position:relative}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{flex:auto;width:1%;min-width:0;position:relative}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{z-index:2;position:relative}.input-group .btn:focus{z-index:5}.input-group-text{color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;display:flex}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{border-radius:var(--bs-border-radius-lg);padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{border-radius:var(--bs-border-radius-sm);padding:.25rem .5rem;font-size:.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(-1 * var(--bs-border-width));border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{width:100%;color:var(--bs-form-valid-color);margin-top:.25rem;font-size:.875em;display:none}.valid-tooltip{z-index:5;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius);max-width:100%;margin-top:.1rem;padding:.25rem .5rem;font-size:.875rem;display:none;position:absolute;top:100%}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:var(--bs-form-valid-border-color);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{background-position:right calc(.375em + .1875rem) top calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-position:right .75rem center,right 2.25rem center;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem);padding-right:4.125rem}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(1.5em + 3.75rem)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:var(--bs-form-valid-color)}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{width:100%;color:var(--bs-form-invalid-color);margin-top:.25rem;font-size:.875em;display:none}.invalid-tooltip{z-index:5;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius);max-width:100%;margin-top:.1rem;padding:.25rem .5rem;font-size:.875rem;display:none;position:absolute;top:100%}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:var(--bs-form-invalid-border-color);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{background-position:right calc(.375em + .1875rem) top calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,right 2.25rem center;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem);padding-right:4.125rem}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(1.5em + 3.75rem)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:var(--bs-form-invalid-color)}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x:.75rem;--bs-btn-padding-y:.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 #ffffff26, 0 1px 1px #00000013;--bs-btn-disabled-opacity:.65;--bs-btn-focus-box-shadow:0 0 0 .25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:inline-block}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);box-shadow:var(--bs-btn-focus-box-shadow);outline:0}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);box-shadow:var(--bs-btn-focus-box-shadow);outline:0}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49, 132, 253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130, 138, 145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60, 153, 110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11, 172, 204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217, 164, 6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225, 83, 97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211, 212, 213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66, 70, 73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13, 110, 253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108, 117, 125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25, 135, 84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13, 202, 240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255, 193, 7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220, 53, 69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248, 249, 250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33, 37, 41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:49, 132, 253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y:.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y:.25rem;--bs-btn-padding-x:.5rem;--bs-btn-font-size:.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;transition:height .35s;overflow:hidden}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart,.dropup-center,.dropdown-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-top-color:currentColor;border-bottom:0;margin-left:.255em;display:inline-block}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:.5rem;--bs-dropdown-spacer:.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:.5rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:var(--bs-tertiary-bg);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:.5rem;z-index:var(--bs-dropdown-zindex);min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;background-color:var(--bs-dropdown-bg);border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius);background-clip:padding-box;margin:0;list-style:none;display:none;position:absolute}.dropdown-menu[data-bs-popper]{margin-top:var(--bs-dropdown-spacer);top:100%;left:0}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{left:auto;right:0}@media (width>=576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{left:auto;right:0}}@media (width>=768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{left:auto;right:0}}@media (width>=992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{left:auto;right:0}}@media (width>=1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{left:auto;right:0}}@media (width>=1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{left:auto;right:0}}.dropup .dropdown-menu[data-bs-popper]{margin-top:0;margin-bottom:var(--bs-dropdown-spacer);top:auto;bottom:100%}.dropup .dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-top:0;border-bottom-color:currentColor;margin-left:.255em;display:inline-block}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{margin-top:0;margin-left:var(--bs-dropdown-spacer);top:0;left:100%;right:auto}.dropend .dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-left-color:currentColor;border-right:0;margin-left:.255em;display:inline-block}.dropend .dropdown-toggle:empty:after{margin-left:0}.dropend .dropdown-toggle:after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{margin-top:0;margin-right:var(--bs-dropdown-spacer);top:0;left:auto;right:100%}.dropstart .dropdown-toggle:after{vertical-align:.255em;content:"";margin-left:.255em;display:none}.dropstart .dropdown-toggle:before{vertical-align:.255em;content:"";border-top:.3em solid #0000;border-bottom:.3em solid #0000;border-right:.3em solid;margin-right:.255em;display:inline-block}.dropstart .dropdown-toggle:empty:after{margin-left:0}.dropstart .dropdown-toggle:before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1;overflow:hidden}.dropdown-item{width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;color:var(--bs-dropdown-link-color);text-align:inherit;white-space:nowrap;border-radius:var(--bs-dropdown-item-border-radius,0);background-color:#0000;border:0;font-weight:400;text-decoration:none;display:block}.dropdown-item:hover,.dropdown-item:focus{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);background-color:var(--bs-dropdown-link-active-bg);text-decoration:none}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:#0000}.dropdown-menu.show{display:block}.dropdown-header{padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);color:var(--bs-dropdown-header-color);white-space:nowrap;margin-bottom:0;font-size:.875rem;display:block}.dropdown-item-text{padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color);display:block}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:#ffffff26;--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{vertical-align:middle;display:inline-flex;position:relative}.btn-group>.btn,.btn-group-vertical>.btn{flex:auto;position:relative}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{flex-wrap:wrap;justify-content:flex-start;display:flex}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(-1 * var(--bs-border-width))}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-left:.5625rem;padding-right:.5625rem}.dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after,.dropend .dropdown-toggle-split:after{margin-left:0}.dropstart .dropdown-toggle-split:before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-left:.375rem;padding-right:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-left:.75rem;padding-right:.75rem}.btn-group-vertical{flex-direction:column;justify-content:center;align-items:flex-start}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(-1 * var(--bs-border-width))}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:nth-child(n+3),.btn-group-vertical>:not(.btn-check)+.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);flex-wrap:wrap;margin-bottom:0;padding-left:0;list-style:none;display:flex}.nav-link{padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);background:0 0;border:0;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{border-bottom:var(--bs-nav-underline-border-width) solid transparent;padding-left:0;padding-right:0}.nav-underline .nav-link:hover,.nav-underline .nav-link:focus{border-bottom-color:currentColor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentColor;font-weight:700}.nav-fill>.nav-link,.nav-fill .nav-item{text-align:center;flex:auto}.nav-justified>.nav-link,.nav-justified .nav-item{text-align:center;flex-grow:1;flex-basis:0}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), .65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), .8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), .3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:.5rem;--bs-navbar-toggler-padding-y:.25rem;--bs-navbar-toggler-padding-x:.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), .15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:.25rem;--bs-navbar-toggler-transition:box-shadow .15s ease-in-out;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x);flex-wrap:wrap;justify-content:space-between;align-items:center;display:flex;position:relative}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{flex-wrap:inherit;justify-content:space-between;align-items:center;display:flex}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);white-space:nowrap;text-decoration:none}.navbar-brand:hover,.navbar-brand:focus{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);flex-direction:column;margin-bottom:0;padding-left:0;list-style:none;display:flex}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{color:var(--bs-navbar-color);padding-top:.5rem;padding-bottom:.5rem}.navbar-text a,.navbar-text a:hover,.navbar-text a:focus{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-grow:1;flex-basis:100%;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);color:var(--bs-navbar-color);border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition);background-color:#0000;line-height:1}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width);outline:0;text-decoration:none}.navbar-toggler-icon{vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-position:50%;background-repeat:no-repeat;background-size:100%;width:1.5em;height:1.5em;display:inline-block}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (width>=576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:#ffffff8c;--bs-navbar-hover-color:#ffffffbf;--bs-navbar-disabled-color:#ffffff40;--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:#ffffff1a;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), .03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:.75rem;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius);background-clip:border-box;flex-direction:column;display:flex;position:relative}.card>hr{margin-left:0;margin-right:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius);border-top-width:0}.card>.list-group:last-child{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius);border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color);flex:auto}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));color:var(--bs-card-subtitle-color);margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color);margin-bottom:0}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius);position:absolute;inset:0}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (width>=576px){.card-group{flex-flow:wrap;display:flex}.card-group>.card{flex:1 0 0;margin-bottom:0}.card-group>.card+.card{border-left:0;margin-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child)>.card-img-top,.card-group>.card:not(:last-child)>.card-header{border-top-right-radius:0}.card-group>.card:not(:last-child)>.card-img-bottom,.card-group>.card:not(:last-child)>.card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child)>.card-img-top,.card-group>.card:not(:first-child)>.card-header{border-top-left-radius:0}.card-group>.card:not(:first-child)>.card-img-bottom,.card-group>.card:not(:first-child)>.card-footer{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out, border-radius .15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform .2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%285.2, 44, 101.2%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-focus-box-shadow:0 0 0 .25rem #0d6efd40;--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);overflow-anchor:none;transition:var(--bs-accordion-transition);border:0;border-radius:0;align-items:center;font-size:1rem;display:flex;position:relative}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed):after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button:after{width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition);flex-shrink:0;margin-left:auto}@media (prefers-reduced-motion:reduce){.accordion-button:after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;box-shadow:var(--bs-accordion-btn-focus-box-shadow);outline:0}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type>.accordion-header .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type>.accordion-header .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type>.accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush>.accordion-item{border-left:0;border-right:0;border-radius:0}.accordion-flush>.accordion-item:first-child{border-top:0}.accordion-flush>.accordion-item:last-child{border-bottom:0}.accordion-flush>.accordion-item>.accordion-collapse,.accordion-flush>.accordion-item>.accordion-header .accordion-button,.accordion-flush>.accordion-item>.accordion-header .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button:after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28109.8, 168, 253.8%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28109.8, 168, 253.8%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius);flex-wrap:wrap;list-style:none;display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item:before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider,"/") }.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:.75rem;--bs-pagination-padding-y:.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 .25rem #0d6efd40;--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);padding-left:0;list-style:none;display:flex}.page-link{padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block;position:relative}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);box-shadow:var(--bs-pagination-focus-box-shadow);outline:0}.page-link.active,.active>.page-link{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.page-link.disabled,.disabled>.page-link{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(-1 * var(--bs-border-width))}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:.5rem;--bs-pagination-padding-y:.25rem;--bs-pagination-font-size:.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:.65em;--bs-badge-padding-y:.35em;--bs-badge-font-size:.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius);line-height:1;display:inline-block}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius);position:relative}.alert-heading{color:inherit}.alert-link{color:var(--bs-alert-link-color);font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{z-index:2;padding:1.25rem 1rem;position:absolute;top:0;right:0}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:var(--bs-progress-height)}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:.75rem;--bs-progress-bg:var(--bs-secondary-bg);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width .6s ease;height:var(--bs-progress-height);font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius);display:flex;overflow:hidden}.progress-bar{color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition);flex-direction:column;justify-content:center;display:flex;overflow:hidden}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,#ffffff26 25%,#0000 25% 50%,#ffffff26 50% 75%,#0000 75%,#0000);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:var(--bs-border-color);--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-tertiary-bg);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;border-radius:var(--bs-list-group-border-radius);flex-direction:column;margin-bottom:0;padding-left:0;display:flex}.list-group-numbered{counter-reset:section;list-style-type:none}.list-group-numbered>.list-group-item:before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item{padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);text-decoration:none;display:block;position:relative}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:not(.active):hover,.list-group-item-action:not(.active):focus{z-index:1;color:var(--bs-list-group-action-hover-color);background-color:var(--bs-list-group-action-hover-bg);text-decoration:none}.list-group-item-action:not(.active):active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (width>=576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/%3e%3c/svg%3e");--bs-btn-close-opacity:.5;--bs-btn-close-hover-opacity:.75;--bs-btn-close-focus-shadow:0 0 0 .25rem #0d6efd40;--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:.25;box-sizing:content-box;width:1em;height:1em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;filter:var(--bs-btn-close-filter);opacity:var(--bs-btn-close-opacity);border:0;border-radius:.375rem;padding:.25em}.btn-close:hover{color:var(--bs-btn-close-color);opacity:var(--bs-btn-close-hover-opacity);text-decoration:none}.btn-close:focus{box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity);outline:0}.btn-close:disabled,.btn-close.disabled{pointer-events:none;-webkit-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}:root,[data-bs-theme=light]{--bs-btn-close-filter: }[data-bs-theme=dark]{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:.75rem;--bs-toast-padding-y:.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), .85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), .85);--bs-toast-header-border-color:var(--bs-border-color-translucent);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius);background-clip:padding-box}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;z-index:var(--bs-toast-zindex);pointer-events:none;width:max-content;max-width:100%;position:absolute}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));background-clip:padding-box;align-items:center;display:flex}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:.5rem;--bs-modal-color:var(--bs-body-color);--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);z-index:var(--bs-modal-zindex);outline:0;width:100%;height:100%;display:none;position:fixed;top:0;left:0;overflow:hidden auto}.modal-dialog{width:auto;margin:var(--bs-modal-margin);pointer-events:none;position:relative}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translateY(-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{min-height:calc(100% - var(--bs-modal-margin) * 2);align-items:center;display:flex}.modal-content{width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:relative}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:.5;z-index:var(--bs-backdrop-zindex);background-color:var(--bs-backdrop-bg);width:100vw;height:100vh;position:fixed;top:0;left:0}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius);flex-shrink:0;align-items:center;display:flex}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin-top:calc(-.5 * var(--bs-modal-header-padding-y));margin-right:calc(-.5 * var(--bs-modal-header-padding-x));margin-bottom:calc(-.5 * var(--bs-modal-header-padding-y));margin-left:auto}.modal-title{line-height:var(--bs-modal-title-line-height);margin-bottom:0}.modal-body{padding:var(--bs-modal-padding);flex:auto;position:relative}.modal-footer{padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius);flex-wrap:wrap;flex-shrink:0;justify-content:flex-end;align-items:center;display:flex}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (width>=576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-left:auto;margin-right:auto}.modal-sm{--bs-modal-width:300px}}@media (width>=992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (width>=1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen .modal-header,.modal-fullscreen .modal-footer{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (width<=575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-sm-down .modal-header,.modal-fullscreen-sm-down .modal-footer{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (width<=767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-md-down .modal-header,.modal-fullscreen-md-down .modal-footer{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (width<=991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-lg-down .modal-header,.modal-fullscreen-lg-down .modal-footer{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (width<=1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-xl-down .modal-header,.modal-fullscreen-xl-down .modal-footer{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (width<=1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-xxl-down .modal-header,.modal-fullscreen-xxl-down .modal-footer{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:.5rem;--bs-tooltip-padding-y:.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:.9;--bs-tooltip-arrow-width:.8rem;--bs-tooltip-arrow-height:.4rem;z-index:var(--bs-tooltip-zindex);margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);text-align:left;text-align:start;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-style:normal;font-weight:400;line-height:1.5;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0;text-decoration:none;display:block}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height);display:block}.tooltip .tooltip-arrow:before{content:"";border-style:solid;border-color:#0000;position:absolute}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-top .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow:before{border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg);top:-1px}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-end .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow:before{border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg);right:-1px}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-bottom .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow:before{border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg);bottom:-1px}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-start .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow:before{border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg);left:-1px}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);text-align:left;text-align:start;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-style:normal;font-weight:400;line-height:1.5;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius);background-clip:padding-box;text-decoration:none;display:block}.popover .popover-arrow{width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height);display:block}.popover .popover-arrow:before,.popover .popover-arrow:after{content:"";border:0 solid #0000;display:block;position:absolute}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-top>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:before,.bs-popover-top>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:after{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-top>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:before{border-top-color:var(--bs-popover-arrow-border);bottom:0}.bs-popover-top>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-end>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:before,.bs-popover-end>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:after{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-end>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:before{border-right-color:var(--bs-popover-arrow-border);left:0}.bs-popover-end>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-bottom>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:before,.bs-popover-bottom>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:after{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-bottom>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:before{border-bottom-color:var(--bs-popover-arrow-border);top:0}.bs-popover-bottom>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-bottom .popover-header:before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header:before{width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg);display:block;position:absolute;top:0;left:50%}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-start>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:before,.bs-popover-start>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:after{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-start>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:before{border-left-color:var(--bs-popover-arrow-border);right:0}.bs-popover-start>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius);margin-bottom:0}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{width:100%;position:relative;overflow:hidden}.carousel-inner:after{clear:both;content:"";display:block}.carousel-item{float:left;backface-visibility:hidden;width:100%;margin-right:-100%;transition:transform .6s ease-in-out;display:none;position:relative}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translate(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translate(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{z-index:1;color:#fff;text-align:center;width:15%;filter:var(--bs-carousel-control-icon-filter);opacity:.5;background:0 0;border:0;justify-content:center;align-items:center;padding:0;transition:opacity .15s;display:flex;position:absolute;top:0;bottom:0}@media (prefers-reduced-motion:reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;opacity:.9;outline:0;text-decoration:none}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{background-position:50%;background-repeat:no-repeat;background-size:100% 100%;width:2rem;height:2rem;display:inline-block}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.carousel-indicators{z-index:2;justify-content:center;margin-bottom:1rem;margin-left:15%;margin-right:15%;padding:0;display:flex;position:absolute;bottom:0;left:0;right:0}.carousel-indicators [data-bs-target]{box-sizing:content-box;text-indent:-999px;cursor:pointer;background-color:var(--bs-carousel-indicator-active-bg);opacity:.5;background-clip:padding-box;border:10px solid #0000;border-left:0;border-right:0;flex:0 auto;width:30px;height:3px;margin-left:3px;margin-right:3px;padding:0;transition:opacity .6s}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{color:var(--bs-carousel-caption-color);text-align:center;padding-top:1.25rem;padding-bottom:1.25rem;position:absolute;bottom:1.25rem;left:15%;right:15%}.carousel-dark{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}:root,[data-bs-theme=light]{--bs-carousel-indicator-active-bg:#fff;--bs-carousel-caption-color:#fff;--bs-carousel-control-icon-filter: }[data-bs-theme=dark]{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}.spinner-grow,.spinner-border{width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);border-radius:50%;flex-shrink:0;display:inline-block}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-.125em;--bs-spinner-border-width:.25em;--bs-spinner-animation-speed:.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:#0000}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-.125em;--bs-spinner-animation-speed:.75s;--bs-spinner-animation-name:spinner-grow;opacity:0;background-color:currentColor}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-xxl,.offcanvas-xl,.offcanvas-lg,.offcanvas-md,.offcanvas-sm{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform .3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (width<=575.98px){.offcanvas-sm{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (width<=575.98px){.offcanvas-sm.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-sm.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-sm.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-sm.showing,.offcanvas-sm.show:not(.hiding){transform:none}.offcanvas-sm.showing,.offcanvas-sm.hiding,.offcanvas-sm.show{visibility:visible}}@media (width>=576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=767.98px){.offcanvas-md{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (width<=767.98px){.offcanvas-md.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-md.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-md.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-md.showing,.offcanvas-md.show:not(.hiding){transform:none}.offcanvas-md.showing,.offcanvas-md.hiding,.offcanvas-md.show{visibility:visible}}@media (width>=768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=991.98px){.offcanvas-lg{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (width<=991.98px){.offcanvas-lg.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-lg.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-lg.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-lg.showing,.offcanvas-lg.show:not(.hiding){transform:none}.offcanvas-lg.showing,.offcanvas-lg.hiding,.offcanvas-lg.show{visibility:visible}}@media (width>=992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=1199.98px){.offcanvas-xl{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (width<=1199.98px){.offcanvas-xl.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-xl.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-xl.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-xl.showing,.offcanvas-xl.show:not(.hiding){transform:none}.offcanvas-xl.showing,.offcanvas-xl.hiding,.offcanvas-xl.show{visibility:visible}}@media (width>=1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=1399.98px){.offcanvas-xxl{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (width<=1399.98px){.offcanvas-xxl.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-xxl.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-xxl.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-xxl.showing,.offcanvas-xxl.show:not(.hiding){transform:none}.offcanvas-xxl.showing,.offcanvas-xxl.hiding,.offcanvas-xxl.show{visibility:visible}}@media (width>=1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}.offcanvas{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas.showing,.offcanvas.show:not(.hiding){transform:none}.offcanvas.showing,.offcanvas.hiding,.offcanvas.show{visibility:visible}.offcanvas-backdrop{z-index:1040;background-color:#000;width:100vw;height:100vh;position:fixed;top:0;left:0}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);align-items:center;display:flex}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y));margin-left:auto}.offcanvas-title{line-height:var(--bs-offcanvas-title-line-height);margin-bottom:0}.offcanvas-body{padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);flex-grow:1;overflow-y:auto}.placeholder{vertical-align:middle;cursor:wait;opacity:.5;background-color:currentColor;min-height:1em;display:inline-block}.placeholder.btn:before{content:"";display:inline-block}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:2s ease-in-out infinite placeholder-glow}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{animation:2s linear infinite placeholder-wave;-webkit-mask-image:linear-gradient(130deg,#000 55%,#000c 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,#000c 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%}@keyframes placeholder-wave{to{-webkit-mask-position:-200% 0;mask-position:-200% 0}}.clearfix:after{clear:both;content:"";display:block}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb), var(--bs-bg-opacity,1))!important}.text-bg-info{color:#000!important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#000!important;background-color:RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb), var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity,1))!important}.link-primary:hover,.link-primary:focus{color:RGBA(10, 88, 202, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(10, 88, 202, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(10, 88, 202, var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity,1))!important}.link-secondary:hover,.link-secondary:focus{color:RGBA(86, 94, 100, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(86, 94, 100, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(86, 94, 100, var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity,1))!important}.link-success:hover,.link-success:focus{color:RGBA(20, 108, 67, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(20, 108, 67, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(20, 108, 67, var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity,1))!important}.link-info:hover,.link-info:focus{color:RGBA(61, 213, 243, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(61, 213, 243, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(61, 213, 243, var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity,1))!important}.link-warning:hover,.link-warning:focus{color:RGBA(255, 205, 57, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255, 205, 57, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255, 205, 57, var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity,1))!important}.link-danger:hover,.link-danger:focus{color:RGBA(176, 42, 55, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(176, 42, 55, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(176, 42, 55, var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity,1))!important}.link-light:hover,.link-light:focus{color:RGBA(249, 250, 251, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity,1))!important}.link-dark:hover,.link-dark:focus{color:RGBA(26, 30, 33, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26, 30, 33, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26, 30, 33, var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:hover,.link-body-emphasis:focus{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,.75))!important}.focus-ring:focus{box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color);outline:0}.icon-link{-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity,.5));text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity,.5));text-underline-offset:.25em;backface-visibility:hidden;align-items:center;gap:.375rem;display:inline-flex}.icon-link>.bi{fill:currentColor;flex-shrink:0;width:1em;height:1em;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:hover>.bi,.icon-link-hover:focus-visible>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em, 0, 0))}.ratio{width:100%;position:relative}.ratio:before{padding-top:var(--bs-aspect-ratio);content:"";display:block}.ratio>*{width:100%;height:100%;position:absolute;top:0;left:0}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571%}.fixed-top{z-index:1030;position:fixed;top:0;left:0;right:0}.fixed-bottom{z-index:1030;position:fixed;bottom:0;left:0;right:0}.sticky-top{z-index:1020;position:sticky;top:0}.sticky-bottom{z-index:1020;position:sticky;bottom:0}@media (width>=576px){.sticky-sm-top{z-index:1020;position:sticky;top:0}.sticky-sm-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=768px){.sticky-md-top{z-index:1020;position:sticky;top:0}.sticky-md-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=992px){.sticky-lg-top{z-index:1020;position:sticky;top:0}.sticky-lg-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=1200px){.sticky-xl-top{z-index:1020;position:sticky;top:0}.sticky-xl-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=1400px){.sticky-xxl-top{z-index:1020;position:sticky;top:0}.sticky-xxl-bottom{z-index:1020;position:sticky;bottom:0}}.hstack{flex-direction:row;align-self:stretch;align-items:center;display:flex}.vstack{flex-direction:column;flex:auto;align-self:stretch;display:flex}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){clip:rect(0, 0, 0, 0)!important;white-space:nowrap!important;border:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;overflow:hidden!important}.visually-hidden:not(caption),.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption){position:absolute!important}.visually-hidden *,.visually-hidden-focusable:not(:focus):not(:focus-within) *{overflow:hidden!important}.stretched-link:after{z-index:1;content:"";position:absolute;inset:0}.text-truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.vr{width:var(--bs-border-width);opacity:.25;background-color:currentColor;align-self:stretch;min-height:1em;display:inline-block}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{object-fit:contain!important}.object-fit-cover{object-fit:cover!important}.object-fit-fill{object-fit:fill!important}.object-fit-scale{object-fit:scale-down!important}.object-fit-none{object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translate(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb), var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb), var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb), var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb), var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb), var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb), var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb), var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb), var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb), var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb), var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:.1}.border-opacity-25{--bs-border-opacity:.25}.border-opacity-50{--bs-border-opacity:.5}.border-opacity-75{--bs-border-opacity:.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-left:0!important;margin-right:0!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-3{margin-left:1rem!important;margin-right:1rem!important}.mx-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-5{margin-left:3rem!important;margin-right:3rem!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-left:0!important;padding-right:0!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-3{padding-left:1rem!important;padding-right:1rem!important}.px-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-5{padding-left:3rem!important;padding-right:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{column-gap:0!important}.column-gap-1{column-gap:.25rem!important}.column-gap-2{column-gap:.5rem!important}.column-gap-3{column-gap:1rem!important}.column-gap-4{column-gap:1.5rem!important}.column-gap-5{column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:#00000080!important}.text-white-50{--bs-text-opacity:1;color:#ffffff80!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:.25}.text-opacity-50{--bs-text-opacity:.5}.text-opacity-75{--bs-text-opacity:.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10,.link-opacity-10-hover:hover{--bs-link-opacity:.1}.link-opacity-25,.link-opacity-25-hover:hover{--bs-link-opacity:.25}.link-opacity-50,.link-opacity-50-hover:hover{--bs-link-opacity:.5}.link-opacity-75,.link-opacity-75-hover:hover{--bs-link-opacity:.75}.link-opacity-100,.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1,.link-offset-1-hover:hover{text-underline-offset:.125em!important}.link-offset-2,.link-offset-2-hover:hover{text-underline-offset:.25em!important}.link-offset-3,.link-offset-3-hover:hover{text-underline-offset:.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0,.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10,.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:.1}.link-underline-opacity-25,.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:.25}.link-underline-opacity-50,.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:.5}.link-underline-opacity-75,.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:.75}.link-underline-opacity-100,.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:#0000!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:.1}.bg-opacity-25{--bs-bg-opacity:.25}.bg-opacity-50{--bs-bg-opacity:.5}.bg-opacity-75{--bs-bg-opacity:.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-top-left-radius:0!important;border-bottom-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-top-left-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (width>=576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{object-fit:contain!important}.object-fit-sm-cover{object-fit:cover!important}.object-fit-sm-fill{object-fit:fill!important}.object-fit-sm-scale{object-fit:scale-down!important}.object-fit-sm-none{object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-left:0!important;margin-right:0!important}.mx-sm-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-sm-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-sm-3{margin-left:1rem!important;margin-right:1rem!important}.mx-sm-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-sm-5{margin-left:3rem!important;margin-right:3rem!important}.mx-sm-auto{margin-left:auto!important;margin-right:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-left:0!important;padding-right:0!important}.px-sm-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-sm-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-sm-3{padding-left:1rem!important;padding-right:1rem!important}.px-sm-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-sm-5{padding-left:3rem!important;padding-right:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{column-gap:0!important}.column-gap-sm-1{column-gap:.25rem!important}.column-gap-sm-2{column-gap:.5rem!important}.column-gap-sm-3{column-gap:1rem!important}.column-gap-sm-4{column-gap:1.5rem!important}.column-gap-sm-5{column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (width>=768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{object-fit:contain!important}.object-fit-md-cover{object-fit:cover!important}.object-fit-md-fill{object-fit:fill!important}.object-fit-md-scale{object-fit:scale-down!important}.object-fit-md-none{object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-left:0!important;margin-right:0!important}.mx-md-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-md-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-md-3{margin-left:1rem!important;margin-right:1rem!important}.mx-md-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-md-5{margin-left:3rem!important;margin-right:3rem!important}.mx-md-auto{margin-left:auto!important;margin-right:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-left:0!important;padding-right:0!important}.px-md-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-md-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-md-3{padding-left:1rem!important;padding-right:1rem!important}.px-md-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-md-5{padding-left:3rem!important;padding-right:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{column-gap:0!important}.column-gap-md-1{column-gap:.25rem!important}.column-gap-md-2{column-gap:.5rem!important}.column-gap-md-3{column-gap:1rem!important}.column-gap-md-4{column-gap:1.5rem!important}.column-gap-md-5{column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (width>=992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{object-fit:contain!important}.object-fit-lg-cover{object-fit:cover!important}.object-fit-lg-fill{object-fit:fill!important}.object-fit-lg-scale{object-fit:scale-down!important}.object-fit-lg-none{object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-left:0!important;margin-right:0!important}.mx-lg-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-lg-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-lg-3{margin-left:1rem!important;margin-right:1rem!important}.mx-lg-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-lg-5{margin-left:3rem!important;margin-right:3rem!important}.mx-lg-auto{margin-left:auto!important;margin-right:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-left:0!important;padding-right:0!important}.px-lg-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-lg-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-lg-3{padding-left:1rem!important;padding-right:1rem!important}.px-lg-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-lg-5{padding-left:3rem!important;padding-right:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{column-gap:0!important}.column-gap-lg-1{column-gap:.25rem!important}.column-gap-lg-2{column-gap:.5rem!important}.column-gap-lg-3{column-gap:1rem!important}.column-gap-lg-4{column-gap:1.5rem!important}.column-gap-lg-5{column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (width>=1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{object-fit:contain!important}.object-fit-xl-cover{object-fit:cover!important}.object-fit-xl-fill{object-fit:fill!important}.object-fit-xl-scale{object-fit:scale-down!important}.object-fit-xl-none{object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-left:0!important;margin-right:0!important}.mx-xl-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-xl-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-xl-3{margin-left:1rem!important;margin-right:1rem!important}.mx-xl-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-xl-5{margin-left:3rem!important;margin-right:3rem!important}.mx-xl-auto{margin-left:auto!important;margin-right:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-left:0!important;padding-right:0!important}.px-xl-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-xl-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-xl-3{padding-left:1rem!important;padding-right:1rem!important}.px-xl-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-xl-5{padding-left:3rem!important;padding-right:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{column-gap:0!important}.column-gap-xl-1{column-gap:.25rem!important}.column-gap-xl-2{column-gap:.5rem!important}.column-gap-xl-3{column-gap:1rem!important}.column-gap-xl-4{column-gap:1.5rem!important}.column-gap-xl-5{column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (width>=1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{object-fit:contain!important}.object-fit-xxl-cover{object-fit:cover!important}.object-fit-xxl-fill{object-fit:fill!important}.object-fit-xxl-scale{object-fit:scale-down!important}.object-fit-xxl-none{object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-left:0!important;margin-right:0!important}.mx-xxl-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-xxl-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-xxl-3{margin-left:1rem!important;margin-right:1rem!important}.mx-xxl-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-xxl-5{margin-left:3rem!important;margin-right:3rem!important}.mx-xxl-auto{margin-left:auto!important;margin-right:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-left:0!important;padding-right:0!important}.px-xxl-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-xxl-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-xxl-3{padding-left:1rem!important;padding-right:1rem!important}.px-xxl-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-xxl-5{padding-left:3rem!important;padding-right:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{column-gap:0!important}.column-gap-xxl-1{column-gap:.25rem!important}.column-gap-xxl-2{column-gap:.5rem!important}.column-gap-xxl-3{column-gap:1rem!important}.column-gap-xxl-4{column-gap:1.5rem!important}.column-gap-xxl-5{column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (width>=1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{float:right;color:#fff;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);font-size:20px;font-weight:700;position:relative;top:-.3em;right:-.3em}.toast-close-button:hover,.toast-close-button:focus{color:#000;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40);text-decoration:none}button.toast-close-button{cursor:pointer;-webkit-appearance:none;background:0 0;border:0;padding:0}.toast-top-center{width:100%;top:0;right:0}.toast-bottom-center{width:100%;bottom:0;right:0}.toast-top-full-width{width:100%;top:0;right:0}.toast-bottom-full-width{width:100%;bottom:0;right:0}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{bottom:12px;right:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{z-index:999999;position:fixed}#toast-container *{box-sizing:border-box}#toast-container>div{color:#fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);width:300px;filter:alpha(opacity=80);background-position:15px;background-repeat:no-repeat;border-radius:3px;margin:0 0 6px;padding:15px 15px 15px 50px;position:relative;overflow:hidden;box-shadow:0 0 12px #999}#toast-container>div:hover{opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer;box-shadow:0 0 12px #000}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-top-center>div,#toast-container.toast-bottom-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-top-full-width>div,#toast-container.toast-bottom-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51a351}.toast-error{background-color:#bd362f}.toast-info{background-color:#2f96b4}.toast-warning{background-color:#f89406}.toast-progress{opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);height:4px;filter:alpha(opacity=40);background-color:#000;position:absolute;bottom:0;left:0}@media (width<=240px){#toast-container>div{width:11em;padding:8px 8px 8px 50px}#toast-container .toast-close-button{top:-.2em;right:-.2em}}@media (width>=241px) and (width<=480px){#toast-container>div{width:18em;padding:8px 8px 8px 50px}#toast-container .toast-close-button{top:-.2em;right:-.2em}}@media (width>=481px) and (width<=768px){#toast-container>div{width:25em;padding:15px 15px 15px 50px}}.slider{vertical-align:middle;display:inline-block;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{width:100%;height:10px;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{border-width:0 10px 10px;border-bottom-color:#036fa5;width:0;height:0;margin-top:0;position:relative;top:50%;transform:translateY(-50%)}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;width:100%;position:absolute;top:0;left:0}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{text-align:center;display:inline-block}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-vertical{width:20px;height:210px}.slider.slider-vertical .slider-track{width:10px;height:100%;top:0;left:25%}.slider.slider-vertical .slider-selection{width:100%;top:0;bottom:0;left:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;border-left-color:#036fa5;width:1px;height:1px;margin-left:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-color:#cfcfcf;background-image:-moz-linear-gradient(#bebebe,#dfdfdf);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#DFDFDF", endColorstr="#BEBEBE",GradientType=0);background-image:linear-gradient(#dfdfdf,#bebebe);background-repeat:repeat-x}.slider.slider-disabled .slider-track{background-color:#e7e7e7;background-image:-moz-linear-gradient(#e9e9e9,#e5e5e5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#E5E5E5", endColorstr="#E9E9E9",GradientType=0);cursor:not-allowed;background-image:linear-gradient(#e5e5e5,#e9e9e9);background-repeat:repeat-x}.slider input{display:none}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .bs-tooltip-top .tooltip-inner,.slider .bs-tooltip-auto[data-popper-placement^=top] .tooltip-inner,.slider .bs-tooltip-bottom .tooltip-inner,.slider .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-inner{position:relative;left:-50%}.slider.bs-tooltip-left .tooltip-inner,.slider.bs-tooltip-right .tooltip-inner{position:relative;top:-100%}.slider .tooltip{pointer-events:none}.slider .tooltip.bs-tooltip-top .arrow,.slider .tooltip.bs-tooltip-auto[data-popper-placement^=top] .arrow,.slider .tooltip.bs-tooltip-bottom .arrow,.slider .tooltip.bs-tooltip-auto[data-popper-placement^=bottom] .arrow{left:-.4rem}.slider .tooltip.bs-tooltip-top,.slider .tooltip.bs-tooltip-auto[data-popper-placement^=top]{margin-top:-44px}.slider .tooltip.bs-tooltip-bottom,.slider .tooltip.bs-tooltip-auto[data-popper-placement^=bottom]{margin-top:2px}.slider .tooltip.bs-tooltip-left,.slider .tooltip.bs-tooltip-right{margin-top:-14px}.slider .tooltip.bs-tooltip-left .arrow,.slider .tooltip.bs-tooltip-right .arrow{top:8px}.slider .hide{display:none}.slider-track{background-color:#f7f7f7;background-image:-moz-linear-gradient(#f9f9f9,#f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#F5F5F5", endColorstr="#F9F9F9",GradientType=0);cursor:pointer;background-image:linear-gradient(#f5f5f5,#f9f9f9);background-repeat:repeat-x;border-radius:4px;position:absolute;box-shadow:inset 0 1px 2px #0000001a}.slider-selection{background-color:#f7f7f7;background-image:-moz-linear-gradient(#f5f5f5,#f9f9f9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#F9F9F9", endColorstr="#F5F5F5",GradientType=0);box-sizing:border-box;background-image:linear-gradient(#f9f9f9,#f5f5f5);background-repeat:repeat-x;border-radius:4px;position:absolute;box-shadow:inset 0 -1px #00000026}.slider-selection.tick-slider-selection{background-color:#46c1fe;background-image:-moz-linear-gradient(#3abcfd,#52c5ff);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgb(81.8, 197.2666666667, 255)", endColorstr="rgb(58.4099072165, 188.0633642612, 252.8900927835)",GradientType=0);background-image:linear-gradient(#52c5ff,#3abcfd);background-repeat:repeat-x}.slider-track-low,.slider-track-high{box-sizing:border-box;background:0 0;border-radius:4px;position:absolute}.slider-handle{background-color:#0478b2;background-image:-moz-linear-gradient(#036fa5,#0480be);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#0480BE", endColorstr="rgb(3.4742268041, 111.175257732, 165.0257731959)",GradientType=0);background-color:#0480be;background-image:linear-gradient(#0480be,#036fa5);background-repeat:repeat-x;border:0 solid #0000;width:20px;height:20px;position:absolute;top:0;box-shadow:inset 0 1px #fff3,0 1px 2px #0000000d}.slider-handle:hover{cursor:pointer}.slider-handle.round{border-radius:20px}.slider-handle.triangle,.slider-handle.custom{background:0 0}.slider-handle.custom:before{content:"★";color:#726204;font-size:20px;line-height:20px}.slider-tick{background-color:#f7f7f7;background-image:-moz-linear-gradient(#f9f9f9,#f5f5f5);filter:none;box-sizing:border-box;cursor:pointer;opacity:.8;background-image:linear-gradient(#f5f5f5,#f9f9f9);background-repeat:repeat-x;border:0 solid #0000;width:20px;height:20px;position:absolute;box-shadow:inset 0 -1px #00000026}.slider-tick.round{border-radius:50%}.slider-tick.triangle,.slider-tick.custom{background:0 0}.slider-tick.custom:before{content:"★";color:#726204;font-size:20px;line-height:20px}.slider-tick.in-selection{background-color:#46c1fe;background-image:-moz-linear-gradient(#3abcfd,#52c5ff);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgb(81.8, 197.2666666667, 255)", endColorstr="rgb(58.4099072165, 188.0633642612, 252.8900927835)",GradientType=0);opacity:1;background-image:linear-gradient(#52c5ff,#3abcfd);background-repeat:repeat-x}html[data-theme=light]{--bg:#fbf9f4;--surface:#fff;--surface-2:#f3efe6;--border:#e3ddcf;--border-soft:#ebe6da;--text:#0b0b0b;--text-muted:#5b564d;--text-dim:#8a8478;--brand-red:#c71f38;--amber:#c71f38;--teal:#a33c4b;--periwinkle:#4a463e;--rose:#c97c89;--sage:#8c1729;--amber-dim:#c71f381f;--teal-dim:#a33c4b1f;--nav-bg:#fbf9f4db;--on-accent:#fbf6f0}html[data-theme=dark]{--bg:#0b0b0b;--surface:#161616;--surface-2:#1e1e1e;--border:#2c2c2c;--border-soft:#232323;--text:#f1eee8;--text-muted:#9a948d;--text-dim:#66605a;--brand-red:#c71f38;--amber:#da3550;--teal:#e8637a;--periwinkle:#5c5854;--rose:#f0a9b6;--sage:#8c1729;--amber-dim:#da355029;--teal-dim:#e8637a29;--nav-bg:#0b0b0bdb}html{background:var(--bg);scroll-behavior:smooth;--maxw:1180px;--radius:12px;--mono:"Space Grotesk", sans-serif;--sans:"Barlow", -apple-system, sans-serif;transition:background .2s}body{background:var(--bg);color:var(--text);font-family:var(--sans);-webkit-font-smoothing:antialiased;margin:0;font-size:16px;line-height:1.6;transition:background .2s,color .2s}*{box-sizing:border-box;transition:background-color .2s,border-color .2s,color .2s}a{color:inherit;text-decoration:none}img{max-width:100%;display:block}.wrap{max-width:var(--maxw);margin:0 auto;padding:0 28px}.eyebrow{font-family:var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--teal);align-items:center;gap:8px;font-size:12px;display:inline-flex}.eyebrow:before{content:"";background:var(--teal);width:6px;height:6px;box-shadow:0 0 0 3px var(--teal-dim);border-radius:50%}h1,.h1,h2,.h2,h3,.h3,h4,.h4{font-family:var(--mono);color:var(--text);letter-spacing:-.01em;margin:0;font-weight:600}p{color:var(--text-muted);margin:0}section{padding:88px 0}.section-head{max-width:640px;margin-bottom:48px}.section-head h2,.section-head .h2{margin-top:14px;font-size:30px}.section-head p{margin-top:14px;font-size:16.5px}header{z-index:50;background:var(--nav-bg);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-bottom:1px solid var(--border-soft);position:sticky;top:0}.logo{align-items:center;display:flex}.logo-full{flex-shrink:0;width:auto;height:26px}.navbar{min-height:72px;padding-top:0;padding-bottom:0}.navbar-nav .nav-link{color:var(--text-muted);white-space:nowrap;font-size:14.5px;transition:color .15s}.navbar-nav .nav-link:hover{color:var(--text)}@media (width>=992px){.navbar-nav{gap:30px}.navbar-nav .nav-link{padding-left:0;padding-right:0}}.nav-cta{font-family:var(--mono);border:1px solid var(--amber);border-radius:999px;font-size:13.5px;font-weight:500;transition:background .15s,color .15s;color:var(--amber)!important;padding:9px 18px!important}.nav-cta:hover{background:var(--amber);color:var(--on-accent)!important}.navbar-toggler{border:none;padding:8px}.navbar-toggler:focus{box-shadow:none}html[data-theme=light] .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(11,11,11,1)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}html[data-theme=dark] .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(241,238,232,1)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}@media (width<=991.98px){:is(header:has(.navbar-collapse.show),header:has(.navbar-collapse.collapsing)){background:var(--bg);-webkit-backdrop-filter:none;backdrop-filter:none}.navbar-collapse{background:var(--bg);border-top:1px solid var(--border-soft);margin:0 -28px;padding:0 28px}.navbar-nav{padding:10px 0 20px}.navbar-nav .nav-link{border-bottom:1px solid var(--border-soft);padding:14px 0;font-size:16px}.navbar-nav .nav-cta{text-align:center;border:1px solid var(--amber);border-radius:999px;width:100%;margin-top:16px;padding:12px 18px;font-size:14.5px;transition:background .15s,color .15s;display:block;color:var(--amber)!important}}.theme-toggle{border:1px solid var(--border);width:38px;height:38px;color:var(--text-muted);cursor:pointer;background:0 0;border-radius:9px;flex-shrink:0;justify-content:center;align-items:center;transition:border-color .15s,color .15s;display:flex}.theme-toggle:hover{border-color:var(--text-muted);color:var(--text)}.theme-toggle svg{width:18px;height:18px}.theme-toggle .icon-moon,html[data-theme=light] .theme-toggle .icon-sun{display:none}html[data-theme=light] .theme-toggle .icon-moon{display:block}.btn{font-family:var(--mono);border:1px solid var(--border);white-space:nowrap;border-radius:8px;align-items:center;gap:8px;padding:11px 20px;font-size:13.5px;font-weight:500;transition:border-color .15s,background .15s,transform .15s;display:inline-flex}.btn:hover{transform:translateY(-1px)}.btn svg{width:15px;height:15px}.btn-primary{background:var(--amber);color:var(--on-accent);border-color:var(--amber);font-weight:600}.btn-primary:hover{background:var(--amber);box-shadow:0 6px 20px var(--amber-dim)}.btn-ghost{color:var(--text)}.btn-ghost:hover{border-color:var(--text-muted)}.btn-sm,.btn-group-sm>.btn{border-radius:7px;padding:8px 14px;font-size:12.5px}.hero{padding:76px 0 40px}.hero h1,.hero .h1{letter-spacing:-.02em;margin-top:18px;font-size:44px;line-height:1.15}.hero p.sub{max-width:520px;color:var(--text-muted);margin-top:20px;font-size:18px}.hero-grid{grid-template-columns:1.05fr .95fr;align-items:center;gap:64px;display:grid}.hero-ctas{flex-wrap:wrap;gap:14px;margin-top:32px;display:flex}.hero-stack-note{font-family:var(--mono);color:var(--text-dim);flex-wrap:wrap;gap:18px;margin-top:34px;font-size:12.5px;display:flex}.hero-stack-note span{color:var(--text-muted)}.pipeline{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:26px 26px 30px;position:relative}.pipeline-label{font-family:var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim);justify-content:space-between;margin-bottom:18px;font-size:11px;display:flex}.pipeline-track{padding-left:28px;position:relative}.pipeline-track:before{content:"";background:linear-gradient(180deg, var(--sage), var(--brand-red));opacity:.5;width:2px;position:absolute;top:10px;bottom:10px;left:9px}.pl-step{background:var(--surface-2);border:1px solid var(--border-soft);border-radius:9px;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px;padding:13px 16px;display:flex;position:relative}.pl-step:last-child{margin-bottom:0}.pl-step:before{content:"";background:var(--bg);border:2px solid var(--teal);border-radius:50%;width:9px;height:9px;position:absolute;top:50%;left:-23px;transform:translateY(-50%)}.pl-step .name{font-family:var(--mono);color:var(--text);font-size:13.5px;font-weight:500}.pl-step .tag{font-family:var(--mono);color:var(--text-dim);font-size:10.5px}.pl-packet{background:var(--brand-red);border-radius:50%;width:14px;height:14px;animation:5s ease-in-out infinite travel;position:absolute;left:2px;box-shadow:0 0 12px 2px #c71f3838}@keyframes travel{0%{opacity:0;top:8px}6%{opacity:1}46%{opacity:1;top:calc(100% - 22px)}50%{opacity:0}50.01%{top:8px}56%{opacity:1}96%{opacity:1;top:calc(100% - 22px)}to{opacity:0;top:calc(100% - 22px)}}@media (prefers-reduced-motion:reduce){.pl-packet{opacity:1;animation:none;top:8px}html{scroll-behavior:auto}}.split{grid-template-columns:1.1fr .9fr;align-items:start;gap:64px;display:grid}.split h2,.split .h2{font-size:28px}.split .lede{color:var(--text-muted);margin-top:16px;font-size:17px;line-height:1.7}.extending{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);align-items:center;gap:12px;margin-top:26px;font-size:12px;display:flex}.extending .extending-logo{text-transform:none;align-items:center;display:flex}.extending .extending-logo img{width:auto;height:36px;display:block}.extending .lockup{font-family:var(--mono);color:var(--text);text-transform:none;letter-spacing:0;font-size:15px;font-weight:600}.extending .lockup .x{color:var(--text-dim);margin:0 6px;font-weight:400}.traits{grid-template-columns:1fr 1fr;gap:14px;display:grid}.trait{background:var(--surface);border:1px solid var(--border);text-align:left;border-radius:12px;padding:22px 18px}.trait .icon{margin-bottom:12px}.trait p{font-family:var(--mono);color:var(--text);font-size:13px;font-weight:500;line-height:1.4}.products-grid{grid-template-columns:repeat(3,1fr);gap:18px;display:grid}.other-skeletons-grid{grid-template-columns:repeat(2,1fr);max-width:800px}.card{background:var(--surface);border:1px solid var(--border);border-top:3px solid var(--accent,var(--teal));border-radius:var(--radius);flex-direction:column;height:100%;padding:26px 24px 24px;display:flex}.card .card-top{justify-content:space-between;align-items:flex-start;margin-bottom:14px;display:flex}.card h3,.card .h3{color:var(--text);font-size:20px}.card .kicker{font-family:var(--mono);color:var(--text-dim);letter-spacing:.1em;text-transform:uppercase;margin-bottom:6px;font-size:10.5px;display:block}.card p.desc{flex-grow:1;font-size:14.5px;line-height:1.65}.card .card-btns{flex-wrap:wrap;gap:8px;margin-top:20px;display:flex}.badges{flex-wrap:wrap;gap:5px;margin-bottom:16px;display:flex}.badges img{border-radius:3px;height:19px}.standards-grid{background:var(--border);border:1px solid var(--border);border-radius:var(--radius);grid-template-columns:repeat(3,1fr);gap:1px;display:grid;overflow:hidden}.standard{background:var(--surface);padding:30px 26px}.standard h4,.standard .h4{color:var(--text);margin-bottom:10px;font-size:16px}.standard h4 a,.standard .h4 a{color:inherit;border-bottom:1px solid var(--border)}.standard h4 a:hover,.standard .h4 a:hover{color:var(--amber);border-color:var(--amber)}.standard p{font-size:14px;line-height:1.65}.standard .get-started{font-family:var(--mono);color:var(--text-dim);margin-top:14px;font-size:12px}.standard .get-started a{color:var(--teal)}.standard .get-started a:hover{text-decoration:underline}.components-grid{grid-template-columns:1fr 1fr;gap:0 40px;display:grid}details.acc{border-bottom:1px solid var(--border-soft)}details.acc summary{cursor:pointer;font-family:var(--mono);color:var(--text);justify-content:space-between;align-items:center;padding:18px 0;font-size:15px;font-weight:500;list-style:none;display:flex}details.acc summary::-webkit-details-marker{display:none}details.acc summary .chev{color:var(--text-dim);font-size:18px;transition:transform .2s}details.acc[open] summary .chev{color:var(--amber);transform:rotate(45deg)}.acc-body{padding:0 0 20px}.pkg-row{border-top:1px solid var(--border-soft);justify-content:space-between;gap:16px;padding:9px 0;display:flex}.pkg-row:first-child{border-top:none}.pkg-row a{font-family:var(--mono);color:var(--teal);white-space:nowrap;font-size:13.5px}.pkg-row a:hover{text-decoration:underline}.pkg-row .pkg-desc{color:var(--text-muted);text-align:right;font-size:13px}.posts-grid{grid-template-columns:repeat(3,1fr);gap:20px;display:grid}.posts-grid-2-col{grid-template-columns:repeat(2,1fr);gap:20px;display:grid}.post-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;transition:border-color .15s}.post-card:hover{border-color:var(--text-dim)}.post-card h4,.post-card .h4{color:var(--text);margin-top:10px;font-size:16.5px;line-height:1.4}.post-card p{margin-top:10px;font-size:13.5px;line-height:1.6}.post-card .read-more{font-family:var(--mono);color:var(--amber);margin-top:14px;font-size:12px;display:inline-block}.post-date{font-family:var(--mono);color:var(--text-dim);letter-spacing:.06em;text-transform:uppercase;font-size:11.5px}.about-band{background:var(--surface);border-top:1px solid var(--border-soft);border-bottom:1px solid var(--border-soft)}.about-inner{grid-template-columns:1fr 1.3fr;align-items:center;gap:56px;display:grid}.about-inner h2,.about-inner .h2{margin-top:14px;font-size:26px;line-height:1.3}.about-inner .about-text{font-size:15px;line-height:1.75}.about-inner .about-cta{margin-top:22px}footer{padding:50px 0 40px}.footer-top{border-bottom:1px solid var(--border-soft);flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:30px;padding-bottom:34px;display:flex}.footer-copy{color:var(--text-dim);max-width:460px;font-size:13px;line-height:1.8}.footer-copy a{color:var(--text-muted);border-bottom:1px solid var(--border)}.footer-social{gap:12px;display:flex}.footer-social a{border:1px solid var(--border);width:38px;height:38px;color:var(--text);border-radius:9px;justify-content:center;align-items:center;transition:border-color .15s,color .15s;display:flex}.footer-social a:hover{border-color:var(--teal);color:var(--teal)}.footer-social svg{width:17px;height:17px}.footer-bottom{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:14px;padding-top:24px;display:flex}.footer-bottom .sponsor{font-family:var(--mono);color:var(--text-dim);font-size:12px}.footer-bottom .sponsor a{color:var(--text-muted);border-bottom:1px solid var(--border)}@media (width<=980px){.hero-grid{grid-template-columns:1fr;gap:44px}.hero{padding-top:44px}.split{grid-template-columns:1fr;gap:40px}.products-grid,.standards-grid{grid-template-columns:repeat(2,1fr)}.posts-grid{grid-template-columns:1fr}.about-inner{grid-template-columns:1fr;gap:26px}}@media (width<=700px){.hero h1,.hero .h1{font-size:32px}.products-grid,.standards-grid,.components-grid{grid-template-columns:1fr}section{padding:56px 0}.pkg-row{flex-direction:column;gap:2px}.pkg-row .pkg-desc{text-align:left}}:focus-visible{outline:2px solid var(--amber);outline-offset:2px}.icon{flex-shrink:0;width:18px;height:18px}.icon-lg{width:50px;height:50px}.icon-jetbrains{min-width:100px}[data-theme=light] .icon{filter:none}[data-theme=dark] .icon{filter:invert()}.contact-grid{grid-template-columns:1fr 1fr;gap:18px;display:grid}@media (width<=700px){.contact-grid{grid-template-columns:1fr}}.contact-card{text-align:center;align-items:center}.contact-card svg{width:40px;height:40px;color:var(--teal);margin-bottom:14px}.contact-card .card-btns{justify-content:center}.approach-card{margin-top:18px}.approach-card h3,.approach-card .h3{margin-bottom:10px}.latest-posts{margin-top:48px}.latest-posts .section-head{margin-bottom:24px}.latest-posts .posts-grid{grid-template-columns:repeat(2,1fr)}@media (width<=700px){.latest-posts .posts-grid{grid-template-columns:1fr}}@media (width<=900px){.sidebar{order:2}}.widget-categories li a{justify-content:space-between;align-items:center;gap:10px;display:flex}.widget-categories .cat-count{font-family:var(--mono);color:var(--text-dim);background:var(--surface-2);border:1px solid var(--border-soft);border-radius:999px;flex-shrink:0;padding:1px 9px;font-size:11px}.widget-categories li a:hover .cat-count{color:var(--text)}.widget-more{margin-top:14px}.widget-more a{font-family:var(--mono);color:var(--amber);font-size:12px}.widget-more a:hover{text-decoration:underline}.no-articles{color:var(--text-dim);font-size:13.5px}.widget-connect li a svg{width:17px;height:17px;color:var(--teal);flex-shrink:0}.breadcrumb-back{font-family:var(--mono);color:var(--text-dim);align-items:center;gap:6px;font-size:13px;display:inline-flex}.breadcrumb-back:hover{color:var(--amber)}.breadcrumb-back svg{width:14px;height:14px}.post-hero{border-bottom:1px solid var(--border-soft);padding:40px 0}.post-hero h1,.post-hero .h1{max-width:820px;margin-top:16px;font-size:38px;line-height:1.2}@media (width<=900px){.post-hero h1,.post-hero .h1{font-size:28px}}.post-meta{font-family:var(--mono);color:var(--text-dim);flex-wrap:wrap;align-items:center;gap:6px 14px;margin-top:22px;font-size:13px;display:flex}.post-meta a{color:var(--text-muted)}.post-meta a:hover{color:var(--amber)}.post-meta .featured-tag{color:var(--brand-red);font-weight:600}.post-body-wrap{padding:52px 0 72px}.post-layout{grid-template-columns:270px 1fr;align-items:start;gap:64px;display:grid}@media (width<=900px){.post-layout{grid-template-columns:1fr;gap:44px}.article{order:1}}.sidebar{flex-direction:column;gap:34px;display:flex}@media (width<=900px){.sidebar{order:2}}.widget{border-bottom:1px solid var(--border-soft);padding-bottom:28px}.widget:last-child{border-bottom:none;padding-bottom:0}.widget ul{margin:0;padding:0;list-style:none}.widget-title{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);margin-bottom:16px;font-size:11.5px}.search-form{border:1px solid var(--border);border-radius:8px;display:flex;overflow:hidden}.search-form input{min-width:0;color:var(--text);font-family:var(--sans);background:0 0;border:none;flex:1;padding:10px 12px;font-size:14px}.search-form input:focus{outline:none}.search-form button{background:var(--surface-2);color:var(--text-muted);cursor:pointer;font-family:var(--mono);border:none;padding:0 14px;font-size:12px}.search-form button:hover{color:var(--amber)}.widget-recent ul{flex-direction:column;gap:14px;display:flex}.widget-recent li a{color:var(--text);font-size:14px;line-height:1.4;display:block}.widget-recent li a:hover{color:var(--amber)}.widget-recent li .post-date{font-family:var(--mono);color:var(--text-dim);margin-top:4px;font-size:11px;display:block}.widget-categories li{border-bottom:1px solid var(--border-soft)}.widget-categories li:last-child{border-bottom:none}.widget-categories li a{color:var(--text-muted);padding:9px 0;font-size:14px;display:block}.widget-categories li a:hover{color:var(--text)}.widget-categories li.current a{color:var(--amber);font-weight:600}.widget-connect ul{flex-direction:column;gap:12px;display:flex}.widget-connect li a{color:var(--text-muted);align-items:center;gap:10px;font-size:14px;display:flex}.widget-connect li a:hover{color:var(--text)}.widget-connect li svg{width:17px;height:17px;color:var(--teal);flex-shrink:0}.widget-approach p{color:var(--text-muted);margin-bottom:12px;font-size:13.5px;line-height:1.7}.widget-approach p:last-child{margin-bottom:0}.entry{max-width:720px}@media (width<=900px){.entry{max-width:100%}}.entry h2,.entry .h2{font-family:var(--mono);color:var(--text);margin-top:48px;margin-bottom:16px;font-size:26px}.entry h2:first-child,.entry .h2:first-child{margin-top:0}.entry h3,.entry .h3{font-family:var(--mono);color:var(--text);margin-top:36px;margin-bottom:14px;font-size:20px}.entry h4,.entry .h4{font-family:var(--mono);color:var(--text);margin-top:28px;margin-bottom:12px;font-size:17px}.entry p{color:var(--text-muted);margin-bottom:20px;font-size:16.5px;line-height:1.8}.entry strong{color:var(--text);font-weight:600}.entry a{color:var(--teal);text-decoration:underline;-webkit-text-decoration-color:var(--border);text-decoration-color:var(--border);text-underline-offset:3px}.entry a:hover{color:var(--amber);-webkit-text-decoration-color:var(--amber);text-decoration-color:var(--amber)}.entry ul{margin:0 0 20px;padding-left:0;list-style:none}.entry ul li{color:var(--text-muted);margin-bottom:10px;padding-left:22px;font-size:16px;line-height:1.7;position:relative}.entry ul li:before{content:"";background:var(--amber);border-radius:50%;width:6px;height:6px;position:absolute;top:10px;left:0}.entry figure{margin:28px 0}.entry figure img{border:1px solid var(--border);border-radius:10px;width:100%;height:auto;display:block}.entry .img-row{gap:20px;margin:28px 0;display:flex}@media (width<=900px){.entry .img-row{flex-direction:column}}.entry .img-row figure{flex:1;margin:0}.entry .img-row img{width:100%}.entry code{font-family:var(--mono);background:var(--surface-2);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-size:.9em}.entry pre{font-family:var(--mono);background:var(--surface-2);border:1px solid var(--border);color:var(--text);border-radius:8px;margin:0 0 20px;padding:16px 18px;font-size:14px;line-height:1.6;overflow-x:auto}.entry pre code{font-size:inherit;background:0 0;border:none;padding:0}.callout{background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--brand-red);border-radius:12px;margin:44px 0;padding:28px 30px}.callout h4,.callout .h4{font-family:var(--mono);color:var(--text);margin-bottom:12px;font-size:18px}.callout p{color:var(--text-muted);margin-bottom:14px;font-size:14.5px;line-height:1.7}.callout ul{margin:0 0 20px;padding-left:0;list-style:none}.callout ul li{color:var(--text-muted);margin-bottom:8px;padding-left:20px;font-size:14.5px;position:relative}.callout ul li:before{content:"";background:var(--brand-red);border-radius:50%;width:5px;height:5px;position:absolute;top:9px;left:0}.post-nav{grid-template-columns:1fr 1fr;gap:16px;margin:48px 0;display:grid}@media (width<=900px){.post-nav{grid-template-columns:1fr}}.post-nav a{border:1px solid var(--border);background:var(--surface);border-radius:12px;padding:20px 22px;display:block}.post-nav a:hover{border-color:var(--text-dim)}.post-nav .direction{font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--text-dim);margin-bottom:8px;font-size:11px;display:block}.post-nav .title{color:var(--text);font-size:14.5px;line-height:1.4}.post-nav .next-post{text-align:right}.comment-section{border-top:1px solid var(--border-soft);margin-top:56px;padding-top:40px}.comment-section h3,.comment-section .h3{font-family:var(--mono);color:var(--text);margin-bottom:24px;font-size:20px}.comment-form-mock{flex-direction:column;gap:16px;max-width:560px;display:flex}.comment-form-mock label{font-family:var(--mono);text-transform:uppercase;letter-spacing:.06em;color:var(--text-dim);margin-bottom:6px;font-size:12px;display:block}.comment-form-mock input,.comment-form-mock textarea{background:var(--surface);border:1px solid var(--border);width:100%;color:var(--text);font-family:var(--sans);border-radius:8px;padding:11px 14px;font-size:14.5px}.comment-form-mock input:focus,.comment-form-mock textarea:focus{border-color:var(--amber);outline:none}.comment-form-mock textarea{resize:vertical;min-height:120px} +:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13, 110, 253;--bs-secondary-rgb:108, 117, 125;--bs-success-rgb:25, 135, 84;--bs-info-rgb:13, 202, 240;--bs-warning-rgb:255, 193, 7;--bs-danger-rgb:220, 53, 69;--bs-light-rgb:248, 249, 250;--bs-dark-rgb:33, 37, 41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255, 255, 255;--bs-black-rgb:0, 0, 0;--bs-font-sans-serif:system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient:linear-gradient(180deg, #ffffff26, #fff0);--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33, 37, 41;--bs-body-bg:#fff;--bs-body-bg-rgb:255, 255, 255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0, 0, 0;--bs-secondary-color:#212529bf;--bs-secondary-color-rgb:33, 37, 41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233, 236, 239;--bs-tertiary-color:#21252980;--bs-tertiary-color-rgb:33, 37, 41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248, 249, 250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13, 110, 253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10, 88, 202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:#0000002d;--bs-border-radius:.375rem;--bs-border-radius-sm:.25rem;--bs-border-radius-lg:.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 .5rem 1rem #00000026;--bs-box-shadow-sm:0 .125rem .25rem #00000013;--bs-box-shadow-lg:0 1rem 3rem #0000002d;--bs-box-shadow-inset:inset 0 1px 2px #00000013;--bs-focus-ring-width:.25rem;--bs-focus-ring-opacity:.25;--bs-focus-ring-color:#0d6efd40;--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222, 226, 230;--bs-body-bg:#212529;--bs-body-bg-rgb:33, 37, 41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255, 255, 255;--bs-secondary-color:#dee2e6bf;--bs-secondary-color-rgb:222, 226, 230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52, 58, 64;--bs-tertiary-color:#dee2e680;--bs-tertiary-color-rgb:222, 226, 230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43, 48, 53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110, 168, 254;--bs-link-hover-color-rgb:139, 185, 254;--bs-code-color:#e685b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:#ffffff26;--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,:before,:after{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:#0000;margin:0}hr{color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25;margin:1rem 0}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{color:var(--bs-heading-color);margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:calc(1.375rem + 1.5vw)}@media (width>=1200px){h1,.h1{font-size:2.5rem}}h2,.h2{font-size:calc(1.325rem + .9vw)}@media (width>=1200px){h2,.h2{font-size:2rem}}h3,.h3{font-size:calc(1.3rem + .6vw)}@media (width>=1200px){h3,.h3{font-size:1.75rem}}h4,.h4{font-size:calc(1.275rem + .3vw)}@media (width>=1200px){h4,.h4{font-size:1.5rem}}h5,.h5{font-size:1.25rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}address{font-style:normal;line-height:inherit;margin-bottom:1rem}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small,.small{font-size:.875em}mark,.mark{color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg);padding:.1875em}sub,sup{vertical-align:baseline;font-size:.75em;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{margin-top:0;margin-bottom:1rem;font-size:.875em;display:block;overflow:auto}pre code{font-size:inherit;color:inherit;word-break:normal}code{color:var(--bs-code-color);word-wrap:break-word;font-size:.875em}a>code{color:inherit}kbd{color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem;padding:.1875rem .375rem;font-size:.875em}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{color:var(--bs-secondary-color);text-align:left;padding-top:.5rem;padding-bottom:.5rem}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{border-style:none;padding:0}textarea{resize:vertical}fieldset{border:0;min-width:0;margin:0;padding:0}legend{float:left;width:100%;line-height:inherit;margin-bottom:.5rem;padding:0;font-size:calc(1.275rem + .3vw)}@media (width>=1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit-text{padding:0}::-webkit-datetime-edit-minute{padding:0}::-webkit-datetime-edit-hour-field{padding:0}::-webkit-datetime-edit-day-field{padding:0}::-webkit-datetime-edit-month-field{padding:0}::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button{cursor:pointer;filter:grayscale()}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{cursor:pointer;display:list-item}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-6{font-size:2.5rem}}.list-unstyled,.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{text-transform:uppercase;font-size:.875em}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{color:#6c757d;margin-top:-1rem;margin-bottom:1rem;font-size:.875em}.blockquote-footer:before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto;padding:.25rem}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{color:var(--bs-secondary-color);font-size:.875em}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-left:auto;margin-right:auto}@media (width>=576px){.container-sm,.container{max-width:540px}}@media (width>=768px){.container-md,.container-sm,.container{max-width:720px}}@media (width>=992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media (width>=1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media (width>=1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x));flex-wrap:wrap;display:flex}.row>*{width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y);flex-shrink:0}.col{flex:1 0 0}.row-cols-auto>*{flex:none;width:auto}.row-cols-1>*{flex:none;width:100%}.row-cols-2>*{flex:none;width:50%}.row-cols-3>*{flex:none;width:33.3333%}.row-cols-4>*{flex:none;width:25%}.row-cols-5>*{flex:none;width:20%}.row-cols-6>*{flex:none;width:16.6667%}.col-auto{flex:none;width:auto}.col-1{flex:none;width:8.33333%}.col-2{flex:none;width:16.6667%}.col-3{flex:none;width:25%}.col-4{flex:none;width:33.3333%}.col-5{flex:none;width:41.6667%}.col-6{flex:none;width:50%}.col-7{flex:none;width:58.3333%}.col-8{flex:none;width:66.6667%}.col-9{flex:none;width:75%}.col-10{flex:none;width:83.3333%}.col-11{flex:none;width:91.6667%}.col-12{flex:none;width:100%}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.6667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333%}.offset-5{margin-left:41.6667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333%}.offset-8{margin-left:66.6667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333%}.offset-11{margin-left:91.6667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:.25rem}.g-1,.gy-1{--bs-gutter-y:.25rem}.g-2,.gx-2{--bs-gutter-x:.5rem}.g-2,.gy-2{--bs-gutter-y:.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (width>=576px){.col-sm{flex:1 0 0}.row-cols-sm-auto>*{flex:none;width:auto}.row-cols-sm-1>*{flex:none;width:100%}.row-cols-sm-2>*{flex:none;width:50%}.row-cols-sm-3>*{flex:none;width:33.3333%}.row-cols-sm-4>*{flex:none;width:25%}.row-cols-sm-5>*{flex:none;width:20%}.row-cols-sm-6>*{flex:none;width:16.6667%}.col-sm-auto{flex:none;width:auto}.col-sm-1{flex:none;width:8.33333%}.col-sm-2{flex:none;width:16.6667%}.col-sm-3{flex:none;width:25%}.col-sm-4{flex:none;width:33.3333%}.col-sm-5{flex:none;width:41.6667%}.col-sm-6{flex:none;width:50%}.col-sm-7{flex:none;width:58.3333%}.col-sm-8{flex:none;width:66.6667%}.col-sm-9{flex:none;width:75%}.col-sm-10{flex:none;width:83.3333%}.col-sm-11{flex:none;width:91.6667%}.col-sm-12{flex:none;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.6667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333%}.offset-sm-5{margin-left:41.6667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333%}.offset-sm-8{margin-left:66.6667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333%}.offset-sm-11{margin-left:91.6667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (width>=768px){.col-md{flex:1 0 0}.row-cols-md-auto>*{flex:none;width:auto}.row-cols-md-1>*{flex:none;width:100%}.row-cols-md-2>*{flex:none;width:50%}.row-cols-md-3>*{flex:none;width:33.3333%}.row-cols-md-4>*{flex:none;width:25%}.row-cols-md-5>*{flex:none;width:20%}.row-cols-md-6>*{flex:none;width:16.6667%}.col-md-auto{flex:none;width:auto}.col-md-1{flex:none;width:8.33333%}.col-md-2{flex:none;width:16.6667%}.col-md-3{flex:none;width:25%}.col-md-4{flex:none;width:33.3333%}.col-md-5{flex:none;width:41.6667%}.col-md-6{flex:none;width:50%}.col-md-7{flex:none;width:58.3333%}.col-md-8{flex:none;width:66.6667%}.col-md-9{flex:none;width:75%}.col-md-10{flex:none;width:83.3333%}.col-md-11{flex:none;width:91.6667%}.col-md-12{flex:none;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.6667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333%}.offset-md-5{margin-left:41.6667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333%}.offset-md-8{margin-left:66.6667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333%}.offset-md-11{margin-left:91.6667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (width>=992px){.col-lg{flex:1 0 0}.row-cols-lg-auto>*{flex:none;width:auto}.row-cols-lg-1>*{flex:none;width:100%}.row-cols-lg-2>*{flex:none;width:50%}.row-cols-lg-3>*{flex:none;width:33.3333%}.row-cols-lg-4>*{flex:none;width:25%}.row-cols-lg-5>*{flex:none;width:20%}.row-cols-lg-6>*{flex:none;width:16.6667%}.col-lg-auto{flex:none;width:auto}.col-lg-1{flex:none;width:8.33333%}.col-lg-2{flex:none;width:16.6667%}.col-lg-3{flex:none;width:25%}.col-lg-4{flex:none;width:33.3333%}.col-lg-5{flex:none;width:41.6667%}.col-lg-6{flex:none;width:50%}.col-lg-7{flex:none;width:58.3333%}.col-lg-8{flex:none;width:66.6667%}.col-lg-9{flex:none;width:75%}.col-lg-10{flex:none;width:83.3333%}.col-lg-11{flex:none;width:91.6667%}.col-lg-12{flex:none;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.6667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333%}.offset-lg-5{margin-left:41.6667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333%}.offset-lg-8{margin-left:66.6667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333%}.offset-lg-11{margin-left:91.6667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (width>=1200px){.col-xl{flex:1 0 0}.row-cols-xl-auto>*{flex:none;width:auto}.row-cols-xl-1>*{flex:none;width:100%}.row-cols-xl-2>*{flex:none;width:50%}.row-cols-xl-3>*{flex:none;width:33.3333%}.row-cols-xl-4>*{flex:none;width:25%}.row-cols-xl-5>*{flex:none;width:20%}.row-cols-xl-6>*{flex:none;width:16.6667%}.col-xl-auto{flex:none;width:auto}.col-xl-1{flex:none;width:8.33333%}.col-xl-2{flex:none;width:16.6667%}.col-xl-3{flex:none;width:25%}.col-xl-4{flex:none;width:33.3333%}.col-xl-5{flex:none;width:41.6667%}.col-xl-6{flex:none;width:50%}.col-xl-7{flex:none;width:58.3333%}.col-xl-8{flex:none;width:66.6667%}.col-xl-9{flex:none;width:75%}.col-xl-10{flex:none;width:83.3333%}.col-xl-11{flex:none;width:91.6667%}.col-xl-12{flex:none;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.6667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333%}.offset-xl-5{margin-left:41.6667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333%}.offset-xl-8{margin-left:66.6667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333%}.offset-xl-11{margin-left:91.6667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (width>=1400px){.col-xxl{flex:1 0 0}.row-cols-xxl-auto>*{flex:none;width:auto}.row-cols-xxl-1>*{flex:none;width:100%}.row-cols-xxl-2>*{flex:none;width:50%}.row-cols-xxl-3>*{flex:none;width:33.3333%}.row-cols-xxl-4>*{flex:none;width:25%}.row-cols-xxl-5>*{flex:none;width:20%}.row-cols-xxl-6>*{flex:none;width:16.6667%}.col-xxl-auto{flex:none;width:auto}.col-xxl-1{flex:none;width:8.33333%}.col-xxl-2{flex:none;width:16.6667%}.col-xxl-3{flex:none;width:25%}.col-xxl-4{flex:none;width:33.3333%}.col-xxl-5{flex:none;width:41.6667%}.col-xxl-6{flex:none;width:50%}.col-xxl-7{flex:none;width:58.3333%}.col-xxl-8{flex:none;width:66.6667%}.col-xxl-9{flex:none;width:75%}.col-xxl-10{flex:none;width:83.3333%}.col-xxl-11{flex:none;width:91.6667%}.col-xxl-12{flex:none;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333%}.offset-xxl-2{margin-left:16.6667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.3333%}.offset-xxl-5{margin-left:41.6667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.3333%}.offset-xxl-8{margin-left:66.6667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.3333%}.offset-xxl-11{margin-left:91.6667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-emphasis-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-emphasis-color);--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), .05);--bs-table-active-color:var(--bs-emphasis-color);--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), .1);--bs-table-hover-color:var(--bs-emphasis-color);--bs-table-hover-bg:rgba(var(--bs-emphasis-color-rgb), .075);vertical-align:top;border-color:var(--bs-table-border-color);width:100%;margin-bottom:1rem}.table>:not(caption)>*>*{color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)));padding:.5rem}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*,.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#a5b5cc;--bs-table-striped-bg:#c4d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe5;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#b4b6b7;--bs-table-striped-bg:#d6d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbcdce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#a7b9b1;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#a5c4ca;--bs-table-striped-bg:#c4e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#ccc2a4;--bs-table-striped-bg:#f2e6c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dab9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece0be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#c6acae;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc1c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7c9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#c6c7c8;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#4d5154;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{-webkit-overflow-scrolling:touch;overflow-x:auto}@media (width<=575.98px){.table-responsive-sm{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=767.98px){.table-responsive-md{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=991.98px){.table-responsive-lg{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=1199.98px){.table-responsive-xl{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=1399.98px){.table-responsive-xxl{-webkit-overflow-scrolling:touch;overflow-x:auto}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));font-size:inherit;margin-bottom:0;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{color:var(--bs-secondary-color);margin-top:.25rem;font-size:.875em}.form-control{width:100%;color:var(--bs-body-color);appearance:none;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-clip:padding-box;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{padding:0;display:block}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::file-selector-button{margin:-.375rem -.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;margin-inline-end:.75rem;padding:.375rem .75rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{width:100%;color:var(--bs-body-color);border:solid #0000;border-width:var(--bs-border-width) 0;background-color:#0000;margin-bottom:0;padding:.375rem 0;line-height:1.5;display:block}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-left:0;padding-right:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));border-radius:var(--bs-border-radius-sm);padding:.25rem .5rem;font-size:.875rem}.form-control-sm::file-selector-button{margin:-.25rem -.5rem;margin-inline-end:.5rem;padding:.25rem .5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));border-radius:var(--bs-border-radius-lg);padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::file-selector-button{margin:-.5rem -1rem;margin-inline-end:1rem;padding:.5rem 1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border-radius:var(--bs-border-radius);border:0!important}.form-control-color::-webkit-color-swatch{border-radius:var(--bs-border-radius);border:0!important}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");width:100%;color:var(--bs-body-color);appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img), var(--bs-form-select-bg-icon,none);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-position:right .75rem center;background-repeat:no-repeat;background-size:16px 12px;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-select[multiple],.form-select[size]:not([size="1"]){background-image:none;padding-right:.75rem}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:#0000;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{border-radius:var(--bs-border-radius-sm);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{border-radius:var(--bs-border-radius-lg);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{min-height:1.5rem;margin-bottom:.125rem;padding-left:1.5em;display:block}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{text-align:right;padding-left:0;padding-right:1.5em}.form-check-reverse .form-check-input{float:right;margin-left:0;margin-right:-1.5em}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);vertical-align:top;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);border:var(--bs-border-width) solid var(--bs-border-color);-webkit-print-color-adjust:exact;print-color-adjust:exact;background-position:50%;background-repeat:no-repeat;background-size:contain;flex-shrink:0;width:1em;height:1em;margin-top:.25em}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");background-color:#0d6efd;border-color:#0d6efd}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input:disabled~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-image:var(--bs-form-switch-bg);background-position:0;border-radius:2em;width:2em;margin-left:-2.5em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28134, 182.5, 254%29'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");background-position:100%}.form-switch.form-check-reverse{padding-left:0;padding-right:2.5em}.form-switch.form-check-reverse .form-check-input{margin-left:0;margin-right:-2.5em}.form-check-inline{margin-right:1rem;display:inline-block}.btn-check{clip:rect(0, 0, 0, 0);pointer-events:none;position:absolute}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{appearance:none;background-color:#0000;width:100%;height:1.5rem;padding:0}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem #0d6efd40}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem #0d6efd40}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;width:1rem;height:1rem;margin-top:-.25rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{color:#0000;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:#0000;border-radius:1rem;width:100%;height:.5rem}.form-range::-moz-range-thumb{appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;width:1rem;height:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{color:#0000;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:#0000;border-radius:1rem;width:100%;height:.5rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{z-index:2;max-width:100%;height:100%;color:rgba(var(--bs-body-color-rgb), .65);text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;padding:1rem .75rem;transition:opacity .1s ease-in-out,transform .1s ease-in-out;position:absolute;top:0;left:0;overflow:hidden}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:#0000}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),:-webkit-any(.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),:is(.form-floating>.form-control:autofill,.form-floating>.form-control-plaintext:autofill){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem;padding-left:.75rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-control-plaintext~label,.form-floating>.form-select~label{transform:scale(.85)translateY(-.5rem)translate(.15rem)}.form-floating>.form-control:-webkit-autofill~label{transform:scale(.85)translateY(-.5rem)translate(.15rem)}.form-floating>textarea:focus~label:after,.form-floating>textarea:not(:placeholder-shown)~label:after{z-index:-1;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);height:1.5em;position:absolute;inset:1rem .375rem}.form-floating>textarea:disabled~label:after{background-color:var(--bs-secondary-bg)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>:disabled~label,.form-floating>.form-control:disabled~label{color:#6c757d}.input-group{flex-wrap:wrap;align-items:stretch;width:100%;display:flex;position:relative}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{flex:auto;width:1%;min-width:0;position:relative}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{z-index:2;position:relative}.input-group .btn:focus{z-index:5}.input-group-text{color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;display:flex}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{border-radius:var(--bs-border-radius-lg);padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{border-radius:var(--bs-border-radius-sm);padding:.25rem .5rem;font-size:.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(-1 * var(--bs-border-width));border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{width:100%;color:var(--bs-form-valid-color);margin-top:.25rem;font-size:.875em;display:none}.valid-tooltip{z-index:5;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius);max-width:100%;margin-top:.1rem;padding:.25rem .5rem;font-size:.875rem;display:none;position:absolute;top:100%}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:var(--bs-form-valid-border-color);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{background-position:right calc(.375em + .1875rem) top calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-position:right .75rem center,right 2.25rem center;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem);padding-right:4.125rem}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(1.5em + 3.75rem)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:var(--bs-form-valid-color)}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{width:100%;color:var(--bs-form-invalid-color);margin-top:.25rem;font-size:.875em;display:none}.invalid-tooltip{z-index:5;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius);max-width:100%;margin-top:.1rem;padding:.25rem .5rem;font-size:.875rem;display:none;position:absolute;top:100%}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:var(--bs-form-invalid-border-color);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{background-position:right calc(.375em + .1875rem) top calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,right 2.25rem center;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem);padding-right:4.125rem}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(1.5em + 3.75rem)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:var(--bs-form-invalid-color)}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x:.75rem;--bs-btn-padding-y:.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 #ffffff26, 0 1px 1px #00000013;--bs-btn-disabled-opacity:.65;--bs-btn-focus-box-shadow:0 0 0 .25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:inline-block}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);box-shadow:var(--bs-btn-focus-box-shadow);outline:0}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);box-shadow:var(--bs-btn-focus-box-shadow);outline:0}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49, 132, 253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130, 138, 145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60, 153, 110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11, 172, 204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217, 164, 6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225, 83, 97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211, 212, 213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66, 70, 73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13, 110, 253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108, 117, 125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25, 135, 84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13, 202, 240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255, 193, 7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220, 53, 69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248, 249, 250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33, 37, 41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:49, 132, 253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y:.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y:.25rem;--bs-btn-padding-x:.5rem;--bs-btn-font-size:.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;transition:height .35s;overflow:hidden}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart,.dropup-center,.dropdown-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-top-color:currentColor;border-bottom:0;margin-left:.255em;display:inline-block}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:.5rem;--bs-dropdown-spacer:.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:.5rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:var(--bs-tertiary-bg);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:.5rem;z-index:var(--bs-dropdown-zindex);min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;background-color:var(--bs-dropdown-bg);border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius);background-clip:padding-box;margin:0;list-style:none;display:none;position:absolute}.dropdown-menu[data-bs-popper]{margin-top:var(--bs-dropdown-spacer);top:100%;left:0}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{left:auto;right:0}@media (width>=576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{left:auto;right:0}}@media (width>=768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{left:auto;right:0}}@media (width>=992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{left:auto;right:0}}@media (width>=1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{left:auto;right:0}}@media (width>=1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{left:auto;right:0}}.dropup .dropdown-menu[data-bs-popper]{margin-top:0;margin-bottom:var(--bs-dropdown-spacer);top:auto;bottom:100%}.dropup .dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-top:0;border-bottom-color:currentColor;margin-left:.255em;display:inline-block}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{margin-top:0;margin-left:var(--bs-dropdown-spacer);top:0;left:100%;right:auto}.dropend .dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-left-color:currentColor;border-right:0;margin-left:.255em;display:inline-block}.dropend .dropdown-toggle:empty:after{margin-left:0}.dropend .dropdown-toggle:after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{margin-top:0;margin-right:var(--bs-dropdown-spacer);top:0;left:auto;right:100%}.dropstart .dropdown-toggle:after{vertical-align:.255em;content:"";margin-left:.255em;display:none}.dropstart .dropdown-toggle:before{vertical-align:.255em;content:"";border-top:.3em solid #0000;border-bottom:.3em solid #0000;border-right:.3em solid;margin-right:.255em;display:inline-block}.dropstart .dropdown-toggle:empty:after{margin-left:0}.dropstart .dropdown-toggle:before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1;overflow:hidden}.dropdown-item{width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;color:var(--bs-dropdown-link-color);text-align:inherit;white-space:nowrap;border-radius:var(--bs-dropdown-item-border-radius,0);background-color:#0000;border:0;font-weight:400;text-decoration:none;display:block}.dropdown-item:hover,.dropdown-item:focus{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);background-color:var(--bs-dropdown-link-active-bg);text-decoration:none}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:#0000}.dropdown-menu.show{display:block}.dropdown-header{padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);color:var(--bs-dropdown-header-color);white-space:nowrap;margin-bottom:0;font-size:.875rem;display:block}.dropdown-item-text{padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color);display:block}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:#ffffff26;--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{vertical-align:middle;display:inline-flex;position:relative}.btn-group>.btn,.btn-group-vertical>.btn{flex:auto;position:relative}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{flex-wrap:wrap;justify-content:flex-start;display:flex}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(-1 * var(--bs-border-width))}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-left:.5625rem;padding-right:.5625rem}.dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after,.dropend .dropdown-toggle-split:after{margin-left:0}.dropstart .dropdown-toggle-split:before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-left:.375rem;padding-right:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-left:.75rem;padding-right:.75rem}.btn-group-vertical{flex-direction:column;justify-content:center;align-items:flex-start}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(-1 * var(--bs-border-width))}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:nth-child(n+3),.btn-group-vertical>:not(.btn-check)+.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);flex-wrap:wrap;margin-bottom:0;padding-left:0;list-style:none;display:flex}.nav-link{padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);background:0 0;border:0;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{border-bottom:var(--bs-nav-underline-border-width) solid transparent;padding-left:0;padding-right:0}.nav-underline .nav-link:hover,.nav-underline .nav-link:focus{border-bottom-color:currentColor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentColor;font-weight:700}.nav-fill>.nav-link,.nav-fill .nav-item{text-align:center;flex:auto}.nav-justified>.nav-link,.nav-justified .nav-item{text-align:center;flex-grow:1;flex-basis:0}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), .65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), .8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), .3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:.5rem;--bs-navbar-toggler-padding-y:.25rem;--bs-navbar-toggler-padding-x:.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), .15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:.25rem;--bs-navbar-toggler-transition:box-shadow .15s ease-in-out;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x);flex-wrap:wrap;justify-content:space-between;align-items:center;display:flex;position:relative}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{flex-wrap:inherit;justify-content:space-between;align-items:center;display:flex}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);white-space:nowrap;text-decoration:none}.navbar-brand:hover,.navbar-brand:focus{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);flex-direction:column;margin-bottom:0;padding-left:0;list-style:none;display:flex}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{color:var(--bs-navbar-color);padding-top:.5rem;padding-bottom:.5rem}.navbar-text a,.navbar-text a:hover,.navbar-text a:focus{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-grow:1;flex-basis:100%;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);color:var(--bs-navbar-color);border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition);background-color:#0000;line-height:1}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width);outline:0;text-decoration:none}.navbar-toggler-icon{vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-position:50%;background-repeat:no-repeat;background-size:100%;width:1.5em;height:1.5em;display:inline-block}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (width>=576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:#ffffff8c;--bs-navbar-hover-color:#ffffffbf;--bs-navbar-disabled-color:#ffffff40;--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:#ffffff1a;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), .03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:.75rem;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius);background-clip:border-box;flex-direction:column;display:flex;position:relative}.card>hr{margin-left:0;margin-right:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius);border-top-width:0}.card>.list-group:last-child{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius);border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color);flex:auto}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));color:var(--bs-card-subtitle-color);margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color);margin-bottom:0}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius);position:absolute;inset:0}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (width>=576px){.card-group{flex-flow:wrap;display:flex}.card-group>.card{flex:1 0 0;margin-bottom:0}.card-group>.card+.card{border-left:0;margin-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child)>.card-img-top,.card-group>.card:not(:last-child)>.card-header{border-top-right-radius:0}.card-group>.card:not(:last-child)>.card-img-bottom,.card-group>.card:not(:last-child)>.card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child)>.card-img-top,.card-group>.card:not(:first-child)>.card-header{border-top-left-radius:0}.card-group>.card:not(:first-child)>.card-img-bottom,.card-group>.card:not(:first-child)>.card-footer{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out, border-radius .15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform .2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%285.2, 44, 101.2%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-focus-box-shadow:0 0 0 .25rem #0d6efd40;--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);overflow-anchor:none;transition:var(--bs-accordion-transition);border:0;border-radius:0;align-items:center;font-size:1rem;display:flex;position:relative}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed):after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button:after{width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition);flex-shrink:0;margin-left:auto}@media (prefers-reduced-motion:reduce){.accordion-button:after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;box-shadow:var(--bs-accordion-btn-focus-box-shadow);outline:0}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type>.accordion-header .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type>.accordion-header .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type>.accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush>.accordion-item{border-left:0;border-right:0;border-radius:0}.accordion-flush>.accordion-item:first-child{border-top:0}.accordion-flush>.accordion-item:last-child{border-bottom:0}.accordion-flush>.accordion-item>.accordion-collapse,.accordion-flush>.accordion-item>.accordion-header .accordion-button,.accordion-flush>.accordion-item>.accordion-header .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button:after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28109.8, 168, 253.8%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28109.8, 168, 253.8%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius);flex-wrap:wrap;list-style:none;display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item:before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider,"/") }.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:.75rem;--bs-pagination-padding-y:.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 .25rem #0d6efd40;--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);padding-left:0;list-style:none;display:flex}.page-link{padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block;position:relative}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);box-shadow:var(--bs-pagination-focus-box-shadow);outline:0}.page-link.active,.active>.page-link{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.page-link.disabled,.disabled>.page-link{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(-1 * var(--bs-border-width))}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:.5rem;--bs-pagination-padding-y:.25rem;--bs-pagination-font-size:.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:.65em;--bs-badge-padding-y:.35em;--bs-badge-font-size:.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius);line-height:1;display:inline-block}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius);position:relative}.alert-heading{color:inherit}.alert-link{color:var(--bs-alert-link-color);font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{z-index:2;padding:1.25rem 1rem;position:absolute;top:0;right:0}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:var(--bs-progress-height)}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:.75rem;--bs-progress-bg:var(--bs-secondary-bg);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width .6s ease;height:var(--bs-progress-height);font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius);display:flex;overflow:hidden}.progress-bar{color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition);flex-direction:column;justify-content:center;display:flex;overflow:hidden}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,#ffffff26 25%,#0000 25% 50%,#ffffff26 50% 75%,#0000 75%,#0000);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:var(--bs-border-color);--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-tertiary-bg);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;border-radius:var(--bs-list-group-border-radius);flex-direction:column;margin-bottom:0;padding-left:0;display:flex}.list-group-numbered{counter-reset:section;list-style-type:none}.list-group-numbered>.list-group-item:before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item{padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);text-decoration:none;display:block;position:relative}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:not(.active):hover,.list-group-item-action:not(.active):focus{z-index:1;color:var(--bs-list-group-action-hover-color);background-color:var(--bs-list-group-action-hover-bg);text-decoration:none}.list-group-item-action:not(.active):active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (width>=576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/%3e%3c/svg%3e");--bs-btn-close-opacity:.5;--bs-btn-close-hover-opacity:.75;--bs-btn-close-focus-shadow:0 0 0 .25rem #0d6efd40;--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:.25;box-sizing:content-box;width:1em;height:1em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;filter:var(--bs-btn-close-filter);opacity:var(--bs-btn-close-opacity);border:0;border-radius:.375rem;padding:.25em}.btn-close:hover{color:var(--bs-btn-close-color);opacity:var(--bs-btn-close-hover-opacity);text-decoration:none}.btn-close:focus{box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity);outline:0}.btn-close:disabled,.btn-close.disabled{pointer-events:none;-webkit-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}:root,[data-bs-theme=light]{--bs-btn-close-filter: }[data-bs-theme=dark]{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:.75rem;--bs-toast-padding-y:.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), .85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), .85);--bs-toast-header-border-color:var(--bs-border-color-translucent);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius);background-clip:padding-box}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;z-index:var(--bs-toast-zindex);pointer-events:none;width:max-content;max-width:100%;position:absolute}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));background-clip:padding-box;align-items:center;display:flex}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:.5rem;--bs-modal-color:var(--bs-body-color);--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);z-index:var(--bs-modal-zindex);outline:0;width:100%;height:100%;display:none;position:fixed;top:0;left:0;overflow:hidden auto}.modal-dialog{width:auto;margin:var(--bs-modal-margin);pointer-events:none;position:relative}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translateY(-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{min-height:calc(100% - var(--bs-modal-margin) * 2);align-items:center;display:flex}.modal-content{width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:relative}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:.5;z-index:var(--bs-backdrop-zindex);background-color:var(--bs-backdrop-bg);width:100vw;height:100vh;position:fixed;top:0;left:0}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius);flex-shrink:0;align-items:center;display:flex}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin-top:calc(-.5 * var(--bs-modal-header-padding-y));margin-right:calc(-.5 * var(--bs-modal-header-padding-x));margin-bottom:calc(-.5 * var(--bs-modal-header-padding-y));margin-left:auto}.modal-title{line-height:var(--bs-modal-title-line-height);margin-bottom:0}.modal-body{padding:var(--bs-modal-padding);flex:auto;position:relative}.modal-footer{padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius);flex-wrap:wrap;flex-shrink:0;justify-content:flex-end;align-items:center;display:flex}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (width>=576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-left:auto;margin-right:auto}.modal-sm{--bs-modal-width:300px}}@media (width>=992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (width>=1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen .modal-header,.modal-fullscreen .modal-footer{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (width<=575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-sm-down .modal-header,.modal-fullscreen-sm-down .modal-footer{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (width<=767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-md-down .modal-header,.modal-fullscreen-md-down .modal-footer{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (width<=991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-lg-down .modal-header,.modal-fullscreen-lg-down .modal-footer{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (width<=1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-xl-down .modal-header,.modal-fullscreen-xl-down .modal-footer{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (width<=1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-xxl-down .modal-header,.modal-fullscreen-xxl-down .modal-footer{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:.5rem;--bs-tooltip-padding-y:.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:.9;--bs-tooltip-arrow-width:.8rem;--bs-tooltip-arrow-height:.4rem;z-index:var(--bs-tooltip-zindex);margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);text-align:left;text-align:start;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-style:normal;font-weight:400;line-height:1.5;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0;text-decoration:none;display:block}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height);display:block}.tooltip .tooltip-arrow:before{content:"";border-style:solid;border-color:#0000;position:absolute}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-top .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow:before{border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg);top:-1px}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-end .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow:before{border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg);right:-1px}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-bottom .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow:before{border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg);bottom:-1px}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-start .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow:before{border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg);left:-1px}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);text-align:left;text-align:start;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-style:normal;font-weight:400;line-height:1.5;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius);background-clip:padding-box;text-decoration:none;display:block}.popover .popover-arrow{width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height);display:block}.popover .popover-arrow:before,.popover .popover-arrow:after{content:"";border:0 solid #0000;display:block;position:absolute}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-top>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:before,.bs-popover-top>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:after{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-top>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:before{border-top-color:var(--bs-popover-arrow-border);bottom:0}.bs-popover-top>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-end>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:before,.bs-popover-end>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:after{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-end>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:before{border-right-color:var(--bs-popover-arrow-border);left:0}.bs-popover-end>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-bottom>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:before,.bs-popover-bottom>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:after{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-bottom>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:before{border-bottom-color:var(--bs-popover-arrow-border);top:0}.bs-popover-bottom>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-bottom .popover-header:before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header:before{width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg);display:block;position:absolute;top:0;left:50%}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-start>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:before,.bs-popover-start>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:after{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-start>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:before{border-left-color:var(--bs-popover-arrow-border);right:0}.bs-popover-start>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius);margin-bottom:0}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{width:100%;position:relative;overflow:hidden}.carousel-inner:after{clear:both;content:"";display:block}.carousel-item{float:left;backface-visibility:hidden;width:100%;margin-right:-100%;transition:transform .6s ease-in-out;display:none;position:relative}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translate(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translate(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{z-index:1;color:#fff;text-align:center;width:15%;filter:var(--bs-carousel-control-icon-filter);opacity:.5;background:0 0;border:0;justify-content:center;align-items:center;padding:0;transition:opacity .15s;display:flex;position:absolute;top:0;bottom:0}@media (prefers-reduced-motion:reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;opacity:.9;outline:0;text-decoration:none}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{background-position:50%;background-repeat:no-repeat;background-size:100% 100%;width:2rem;height:2rem;display:inline-block}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.carousel-indicators{z-index:2;justify-content:center;margin-bottom:1rem;margin-left:15%;margin-right:15%;padding:0;display:flex;position:absolute;bottom:0;left:0;right:0}.carousel-indicators [data-bs-target]{box-sizing:content-box;text-indent:-999px;cursor:pointer;background-color:var(--bs-carousel-indicator-active-bg);opacity:.5;background-clip:padding-box;border:10px solid #0000;border-left:0;border-right:0;flex:0 auto;width:30px;height:3px;margin-left:3px;margin-right:3px;padding:0;transition:opacity .6s}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{color:var(--bs-carousel-caption-color);text-align:center;padding-top:1.25rem;padding-bottom:1.25rem;position:absolute;bottom:1.25rem;left:15%;right:15%}.carousel-dark{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}:root,[data-bs-theme=light]{--bs-carousel-indicator-active-bg:#fff;--bs-carousel-caption-color:#fff;--bs-carousel-control-icon-filter: }[data-bs-theme=dark]{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}.spinner-grow,.spinner-border{width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);border-radius:50%;flex-shrink:0;display:inline-block}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-.125em;--bs-spinner-border-width:.25em;--bs-spinner-animation-speed:.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:#0000}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-.125em;--bs-spinner-animation-speed:.75s;--bs-spinner-animation-name:spinner-grow;opacity:0;background-color:currentColor}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-xxl,.offcanvas-xl,.offcanvas-lg,.offcanvas-md,.offcanvas-sm{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform .3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (width<=575.98px){.offcanvas-sm{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (width<=575.98px){.offcanvas-sm.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-sm.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-sm.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-sm.showing,.offcanvas-sm.show:not(.hiding){transform:none}.offcanvas-sm.showing,.offcanvas-sm.hiding,.offcanvas-sm.show{visibility:visible}}@media (width>=576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=767.98px){.offcanvas-md{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (width<=767.98px){.offcanvas-md.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-md.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-md.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-md.showing,.offcanvas-md.show:not(.hiding){transform:none}.offcanvas-md.showing,.offcanvas-md.hiding,.offcanvas-md.show{visibility:visible}}@media (width>=768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=991.98px){.offcanvas-lg{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (width<=991.98px){.offcanvas-lg.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-lg.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-lg.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-lg.showing,.offcanvas-lg.show:not(.hiding){transform:none}.offcanvas-lg.showing,.offcanvas-lg.hiding,.offcanvas-lg.show{visibility:visible}}@media (width>=992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=1199.98px){.offcanvas-xl{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (width<=1199.98px){.offcanvas-xl.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-xl.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-xl.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-xl.showing,.offcanvas-xl.show:not(.hiding){transform:none}.offcanvas-xl.showing,.offcanvas-xl.hiding,.offcanvas-xl.show{visibility:visible}}@media (width>=1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=1399.98px){.offcanvas-xxl{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (width<=1399.98px){.offcanvas-xxl.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-xxl.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-xxl.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-xxl.showing,.offcanvas-xxl.show:not(.hiding){transform:none}.offcanvas-xxl.showing,.offcanvas-xxl.hiding,.offcanvas-xxl.show{visibility:visible}}@media (width>=1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}.offcanvas{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas.showing,.offcanvas.show:not(.hiding){transform:none}.offcanvas.showing,.offcanvas.hiding,.offcanvas.show{visibility:visible}.offcanvas-backdrop{z-index:1040;background-color:#000;width:100vw;height:100vh;position:fixed;top:0;left:0}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);align-items:center;display:flex}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y));margin-left:auto}.offcanvas-title{line-height:var(--bs-offcanvas-title-line-height);margin-bottom:0}.offcanvas-body{padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);flex-grow:1;overflow-y:auto}.placeholder{vertical-align:middle;cursor:wait;opacity:.5;background-color:currentColor;min-height:1em;display:inline-block}.placeholder.btn:before{content:"";display:inline-block}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:2s ease-in-out infinite placeholder-glow}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{animation:2s linear infinite placeholder-wave;-webkit-mask-image:linear-gradient(130deg,#000 55%,#000c 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,#000c 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%}@keyframes placeholder-wave{to{-webkit-mask-position:-200% 0;mask-position:-200% 0}}.clearfix:after{clear:both;content:"";display:block}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb), var(--bs-bg-opacity,1))!important}.text-bg-info{color:#000!important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#000!important;background-color:RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb), var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity,1))!important}.link-primary:hover,.link-primary:focus{color:RGBA(10, 88, 202, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(10, 88, 202, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(10, 88, 202, var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity,1))!important}.link-secondary:hover,.link-secondary:focus{color:RGBA(86, 94, 100, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(86, 94, 100, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(86, 94, 100, var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity,1))!important}.link-success:hover,.link-success:focus{color:RGBA(20, 108, 67, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(20, 108, 67, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(20, 108, 67, var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity,1))!important}.link-info:hover,.link-info:focus{color:RGBA(61, 213, 243, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(61, 213, 243, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(61, 213, 243, var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity,1))!important}.link-warning:hover,.link-warning:focus{color:RGBA(255, 205, 57, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255, 205, 57, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255, 205, 57, var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity,1))!important}.link-danger:hover,.link-danger:focus{color:RGBA(176, 42, 55, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(176, 42, 55, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(176, 42, 55, var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity,1))!important}.link-light:hover,.link-light:focus{color:RGBA(249, 250, 251, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity,1))!important}.link-dark:hover,.link-dark:focus{color:RGBA(26, 30, 33, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26, 30, 33, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26, 30, 33, var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:hover,.link-body-emphasis:focus{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,.75))!important}.focus-ring:focus{box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color);outline:0}.icon-link{-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity,.5));text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity,.5));text-underline-offset:.25em;backface-visibility:hidden;align-items:center;gap:.375rem;display:inline-flex}.icon-link>.bi{fill:currentColor;flex-shrink:0;width:1em;height:1em;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:hover>.bi,.icon-link-hover:focus-visible>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em, 0, 0))}.ratio{width:100%;position:relative}.ratio:before{padding-top:var(--bs-aspect-ratio);content:"";display:block}.ratio>*{width:100%;height:100%;position:absolute;top:0;left:0}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571%}.fixed-top{z-index:1030;position:fixed;top:0;left:0;right:0}.fixed-bottom{z-index:1030;position:fixed;bottom:0;left:0;right:0}.sticky-top{z-index:1020;position:sticky;top:0}.sticky-bottom{z-index:1020;position:sticky;bottom:0}@media (width>=576px){.sticky-sm-top{z-index:1020;position:sticky;top:0}.sticky-sm-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=768px){.sticky-md-top{z-index:1020;position:sticky;top:0}.sticky-md-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=992px){.sticky-lg-top{z-index:1020;position:sticky;top:0}.sticky-lg-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=1200px){.sticky-xl-top{z-index:1020;position:sticky;top:0}.sticky-xl-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=1400px){.sticky-xxl-top{z-index:1020;position:sticky;top:0}.sticky-xxl-bottom{z-index:1020;position:sticky;bottom:0}}.hstack{flex-direction:row;align-self:stretch;align-items:center;display:flex}.vstack{flex-direction:column;flex:auto;align-self:stretch;display:flex}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){clip:rect(0, 0, 0, 0)!important;white-space:nowrap!important;border:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;overflow:hidden!important}.visually-hidden:not(caption),.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption){position:absolute!important}.visually-hidden *,.visually-hidden-focusable:not(:focus):not(:focus-within) *{overflow:hidden!important}.stretched-link:after{z-index:1;content:"";position:absolute;inset:0}.text-truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.vr{width:var(--bs-border-width);opacity:.25;background-color:currentColor;align-self:stretch;min-height:1em;display:inline-block}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{object-fit:contain!important}.object-fit-cover{object-fit:cover!important}.object-fit-fill{object-fit:fill!important}.object-fit-scale{object-fit:scale-down!important}.object-fit-none{object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translate(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb), var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb), var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb), var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb), var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb), var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb), var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb), var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb), var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb), var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb), var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:.1}.border-opacity-25{--bs-border-opacity:.25}.border-opacity-50{--bs-border-opacity:.5}.border-opacity-75{--bs-border-opacity:.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-left:0!important;margin-right:0!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-3{margin-left:1rem!important;margin-right:1rem!important}.mx-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-5{margin-left:3rem!important;margin-right:3rem!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-left:0!important;padding-right:0!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-3{padding-left:1rem!important;padding-right:1rem!important}.px-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-5{padding-left:3rem!important;padding-right:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{column-gap:0!important}.column-gap-1{column-gap:.25rem!important}.column-gap-2{column-gap:.5rem!important}.column-gap-3{column-gap:1rem!important}.column-gap-4{column-gap:1.5rem!important}.column-gap-5{column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:#00000080!important}.text-white-50{--bs-text-opacity:1;color:#ffffff80!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:.25}.text-opacity-50{--bs-text-opacity:.5}.text-opacity-75{--bs-text-opacity:.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10,.link-opacity-10-hover:hover{--bs-link-opacity:.1}.link-opacity-25,.link-opacity-25-hover:hover{--bs-link-opacity:.25}.link-opacity-50,.link-opacity-50-hover:hover{--bs-link-opacity:.5}.link-opacity-75,.link-opacity-75-hover:hover{--bs-link-opacity:.75}.link-opacity-100,.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1,.link-offset-1-hover:hover{text-underline-offset:.125em!important}.link-offset-2,.link-offset-2-hover:hover{text-underline-offset:.25em!important}.link-offset-3,.link-offset-3-hover:hover{text-underline-offset:.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0,.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10,.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:.1}.link-underline-opacity-25,.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:.25}.link-underline-opacity-50,.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:.5}.link-underline-opacity-75,.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:.75}.link-underline-opacity-100,.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:#0000!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:.1}.bg-opacity-25{--bs-bg-opacity:.25}.bg-opacity-50{--bs-bg-opacity:.5}.bg-opacity-75{--bs-bg-opacity:.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-top-left-radius:0!important;border-bottom-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-top-left-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (width>=576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{object-fit:contain!important}.object-fit-sm-cover{object-fit:cover!important}.object-fit-sm-fill{object-fit:fill!important}.object-fit-sm-scale{object-fit:scale-down!important}.object-fit-sm-none{object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-left:0!important;margin-right:0!important}.mx-sm-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-sm-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-sm-3{margin-left:1rem!important;margin-right:1rem!important}.mx-sm-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-sm-5{margin-left:3rem!important;margin-right:3rem!important}.mx-sm-auto{margin-left:auto!important;margin-right:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-left:0!important;padding-right:0!important}.px-sm-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-sm-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-sm-3{padding-left:1rem!important;padding-right:1rem!important}.px-sm-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-sm-5{padding-left:3rem!important;padding-right:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{column-gap:0!important}.column-gap-sm-1{column-gap:.25rem!important}.column-gap-sm-2{column-gap:.5rem!important}.column-gap-sm-3{column-gap:1rem!important}.column-gap-sm-4{column-gap:1.5rem!important}.column-gap-sm-5{column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (width>=768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{object-fit:contain!important}.object-fit-md-cover{object-fit:cover!important}.object-fit-md-fill{object-fit:fill!important}.object-fit-md-scale{object-fit:scale-down!important}.object-fit-md-none{object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-left:0!important;margin-right:0!important}.mx-md-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-md-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-md-3{margin-left:1rem!important;margin-right:1rem!important}.mx-md-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-md-5{margin-left:3rem!important;margin-right:3rem!important}.mx-md-auto{margin-left:auto!important;margin-right:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-left:0!important;padding-right:0!important}.px-md-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-md-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-md-3{padding-left:1rem!important;padding-right:1rem!important}.px-md-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-md-5{padding-left:3rem!important;padding-right:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{column-gap:0!important}.column-gap-md-1{column-gap:.25rem!important}.column-gap-md-2{column-gap:.5rem!important}.column-gap-md-3{column-gap:1rem!important}.column-gap-md-4{column-gap:1.5rem!important}.column-gap-md-5{column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (width>=992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{object-fit:contain!important}.object-fit-lg-cover{object-fit:cover!important}.object-fit-lg-fill{object-fit:fill!important}.object-fit-lg-scale{object-fit:scale-down!important}.object-fit-lg-none{object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-left:0!important;margin-right:0!important}.mx-lg-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-lg-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-lg-3{margin-left:1rem!important;margin-right:1rem!important}.mx-lg-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-lg-5{margin-left:3rem!important;margin-right:3rem!important}.mx-lg-auto{margin-left:auto!important;margin-right:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-left:0!important;padding-right:0!important}.px-lg-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-lg-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-lg-3{padding-left:1rem!important;padding-right:1rem!important}.px-lg-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-lg-5{padding-left:3rem!important;padding-right:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{column-gap:0!important}.column-gap-lg-1{column-gap:.25rem!important}.column-gap-lg-2{column-gap:.5rem!important}.column-gap-lg-3{column-gap:1rem!important}.column-gap-lg-4{column-gap:1.5rem!important}.column-gap-lg-5{column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (width>=1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{object-fit:contain!important}.object-fit-xl-cover{object-fit:cover!important}.object-fit-xl-fill{object-fit:fill!important}.object-fit-xl-scale{object-fit:scale-down!important}.object-fit-xl-none{object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-left:0!important;margin-right:0!important}.mx-xl-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-xl-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-xl-3{margin-left:1rem!important;margin-right:1rem!important}.mx-xl-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-xl-5{margin-left:3rem!important;margin-right:3rem!important}.mx-xl-auto{margin-left:auto!important;margin-right:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-left:0!important;padding-right:0!important}.px-xl-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-xl-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-xl-3{padding-left:1rem!important;padding-right:1rem!important}.px-xl-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-xl-5{padding-left:3rem!important;padding-right:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{column-gap:0!important}.column-gap-xl-1{column-gap:.25rem!important}.column-gap-xl-2{column-gap:.5rem!important}.column-gap-xl-3{column-gap:1rem!important}.column-gap-xl-4{column-gap:1.5rem!important}.column-gap-xl-5{column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (width>=1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{object-fit:contain!important}.object-fit-xxl-cover{object-fit:cover!important}.object-fit-xxl-fill{object-fit:fill!important}.object-fit-xxl-scale{object-fit:scale-down!important}.object-fit-xxl-none{object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-left:0!important;margin-right:0!important}.mx-xxl-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-xxl-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-xxl-3{margin-left:1rem!important;margin-right:1rem!important}.mx-xxl-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-xxl-5{margin-left:3rem!important;margin-right:3rem!important}.mx-xxl-auto{margin-left:auto!important;margin-right:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-left:0!important;padding-right:0!important}.px-xxl-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-xxl-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-xxl-3{padding-left:1rem!important;padding-right:1rem!important}.px-xxl-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-xxl-5{padding-left:3rem!important;padding-right:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{column-gap:0!important}.column-gap-xxl-1{column-gap:.25rem!important}.column-gap-xxl-2{column-gap:.5rem!important}.column-gap-xxl-3{column-gap:1rem!important}.column-gap-xxl-4{column-gap:1.5rem!important}.column-gap-xxl-5{column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (width>=1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{float:right;color:#fff;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);font-size:20px;font-weight:700;position:relative;top:-.3em;right:-.3em}.toast-close-button:hover,.toast-close-button:focus{color:#000;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40);text-decoration:none}button.toast-close-button{cursor:pointer;-webkit-appearance:none;background:0 0;border:0;padding:0}.toast-top-center{width:100%;top:0;right:0}.toast-bottom-center{width:100%;bottom:0;right:0}.toast-top-full-width{width:100%;top:0;right:0}.toast-bottom-full-width{width:100%;bottom:0;right:0}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{bottom:12px;right:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{z-index:999999;position:fixed}#toast-container *{box-sizing:border-box}#toast-container>div{color:#fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);width:300px;filter:alpha(opacity=80);background-position:15px;background-repeat:no-repeat;border-radius:3px;margin:0 0 6px;padding:15px 15px 15px 50px;position:relative;overflow:hidden;box-shadow:0 0 12px #999}#toast-container>div:hover{opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer;box-shadow:0 0 12px #000}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-top-center>div,#toast-container.toast-bottom-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-top-full-width>div,#toast-container.toast-bottom-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51a351}.toast-error{background-color:#bd362f}.toast-info{background-color:#2f96b4}.toast-warning{background-color:#f89406}.toast-progress{opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);height:4px;filter:alpha(opacity=40);background-color:#000;position:absolute;bottom:0;left:0}@media (width<=240px){#toast-container>div{width:11em;padding:8px 8px 8px 50px}#toast-container .toast-close-button{top:-.2em;right:-.2em}}@media (width>=241px) and (width<=480px){#toast-container>div{width:18em;padding:8px 8px 8px 50px}#toast-container .toast-close-button{top:-.2em;right:-.2em}}@media (width>=481px) and (width<=768px){#toast-container>div{width:25em;padding:15px 15px 15px 50px}}.slider{vertical-align:middle;display:inline-block;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{width:100%;height:10px;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{border-width:0 10px 10px;border-bottom-color:#036fa5;width:0;height:0;margin-top:0;position:relative;top:50%;transform:translateY(-50%)}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;width:100%;position:absolute;top:0;left:0}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{text-align:center;display:inline-block}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-vertical{width:20px;height:210px}.slider.slider-vertical .slider-track{width:10px;height:100%;top:0;left:25%}.slider.slider-vertical .slider-selection{width:100%;top:0;bottom:0;left:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;border-left-color:#036fa5;width:1px;height:1px;margin-left:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-color:#cfcfcf;background-image:-moz-linear-gradient(#bebebe,#dfdfdf);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#DFDFDF", endColorstr="#BEBEBE",GradientType=0);background-image:linear-gradient(#dfdfdf,#bebebe);background-repeat:repeat-x}.slider.slider-disabled .slider-track{background-color:#e7e7e7;background-image:-moz-linear-gradient(#e9e9e9,#e5e5e5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#E5E5E5", endColorstr="#E9E9E9",GradientType=0);cursor:not-allowed;background-image:linear-gradient(#e5e5e5,#e9e9e9);background-repeat:repeat-x}.slider input{display:none}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .bs-tooltip-top .tooltip-inner,.slider .bs-tooltip-auto[data-popper-placement^=top] .tooltip-inner,.slider .bs-tooltip-bottom .tooltip-inner,.slider .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-inner{position:relative;left:-50%}.slider.bs-tooltip-left .tooltip-inner,.slider.bs-tooltip-right .tooltip-inner{position:relative;top:-100%}.slider .tooltip{pointer-events:none}.slider .tooltip.bs-tooltip-top .arrow,.slider .tooltip.bs-tooltip-auto[data-popper-placement^=top] .arrow,.slider .tooltip.bs-tooltip-bottom .arrow,.slider .tooltip.bs-tooltip-auto[data-popper-placement^=bottom] .arrow{left:-.4rem}.slider .tooltip.bs-tooltip-top,.slider .tooltip.bs-tooltip-auto[data-popper-placement^=top]{margin-top:-44px}.slider .tooltip.bs-tooltip-bottom,.slider .tooltip.bs-tooltip-auto[data-popper-placement^=bottom]{margin-top:2px}.slider .tooltip.bs-tooltip-left,.slider .tooltip.bs-tooltip-right{margin-top:-14px}.slider .tooltip.bs-tooltip-left .arrow,.slider .tooltip.bs-tooltip-right .arrow{top:8px}.slider .hide{display:none}.slider-track{background-color:#f7f7f7;background-image:-moz-linear-gradient(#f9f9f9,#f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#F5F5F5", endColorstr="#F9F9F9",GradientType=0);cursor:pointer;background-image:linear-gradient(#f5f5f5,#f9f9f9);background-repeat:repeat-x;border-radius:4px;position:absolute;box-shadow:inset 0 1px 2px #0000001a}.slider-selection{background-color:#f7f7f7;background-image:-moz-linear-gradient(#f5f5f5,#f9f9f9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#F9F9F9", endColorstr="#F5F5F5",GradientType=0);box-sizing:border-box;background-image:linear-gradient(#f9f9f9,#f5f5f5);background-repeat:repeat-x;border-radius:4px;position:absolute;box-shadow:inset 0 -1px #00000026}.slider-selection.tick-slider-selection{background-color:#46c1fe;background-image:-moz-linear-gradient(#3abcfd,#52c5ff);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgb(81.8, 197.2666666667, 255)", endColorstr="rgb(58.4099072165, 188.0633642612, 252.8900927835)",GradientType=0);background-image:linear-gradient(#52c5ff,#3abcfd);background-repeat:repeat-x}.slider-track-low,.slider-track-high{box-sizing:border-box;background:0 0;border-radius:4px;position:absolute}.slider-handle{background-color:#0478b2;background-image:-moz-linear-gradient(#036fa5,#0480be);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#0480BE", endColorstr="rgb(3.4742268041, 111.175257732, 165.0257731959)",GradientType=0);background-color:#0480be;background-image:linear-gradient(#0480be,#036fa5);background-repeat:repeat-x;border:0 solid #0000;width:20px;height:20px;position:absolute;top:0;box-shadow:inset 0 1px #fff3,0 1px 2px #0000000d}.slider-handle:hover{cursor:pointer}.slider-handle.round{border-radius:20px}.slider-handle.triangle,.slider-handle.custom{background:0 0}.slider-handle.custom:before{content:"★";color:#726204;font-size:20px;line-height:20px}.slider-tick{background-color:#f7f7f7;background-image:-moz-linear-gradient(#f9f9f9,#f5f5f5);filter:none;box-sizing:border-box;cursor:pointer;opacity:.8;background-image:linear-gradient(#f5f5f5,#f9f9f9);background-repeat:repeat-x;border:0 solid #0000;width:20px;height:20px;position:absolute;box-shadow:inset 0 -1px #00000026}.slider-tick.round{border-radius:50%}.slider-tick.triangle,.slider-tick.custom{background:0 0}.slider-tick.custom:before{content:"★";color:#726204;font-size:20px;line-height:20px}.slider-tick.in-selection{background-color:#46c1fe;background-image:-moz-linear-gradient(#3abcfd,#52c5ff);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="rgb(81.8, 197.2666666667, 255)", endColorstr="rgb(58.4099072165, 188.0633642612, 252.8900927835)",GradientType=0);opacity:1;background-image:linear-gradient(#52c5ff,#3abcfd);background-repeat:repeat-x}html[data-theme=light]{--bg:#fbf9f4;--surface:#fff;--surface-2:#f3efe6;--border:#e3ddcf;--border-soft:#ebe6da;--text:#0b0b0b;--text-muted:#5b564d;--text-dim:#8a8478;--brand-red:#c71f38;--amber:#c71f38;--teal:#a33c4b;--periwinkle:#4a463e;--rose:#c97c89;--sage:#8c1729;--amber-dim:#c71f381f;--teal-dim:#a33c4b1f;--nav-bg:#fbf9f4db;--on-accent:#fbf6f0}html[data-theme=dark]{--bg:#0b0b0b;--surface:#161616;--surface-2:#1e1e1e;--border:#2c2c2c;--border-soft:#232323;--text:#f1eee8;--text-muted:#9a948d;--text-dim:#66605a;--brand-red:#c71f38;--amber:#da3550;--teal:#e8637a;--periwinkle:#5c5854;--rose:#f0a9b6;--sage:#8c1729;--amber-dim:#da355029;--teal-dim:#e8637a29;--nav-bg:#0b0b0bdb}html{background:var(--bg);scroll-behavior:smooth;--maxw:1180px;--radius:12px;--mono:"Space Grotesk", sans-serif;--sans:"Barlow", -apple-system, sans-serif;transition:background .2s}body{background:var(--bg);color:var(--text);font-family:var(--sans);-webkit-font-smoothing:antialiased;margin:0;font-size:16px;line-height:1.6;transition:background .2s,color .2s}*{box-sizing:border-box;transition:background-color .2s,border-color .2s,color .2s}a{color:inherit;text-decoration:none}img{max-width:100%;display:block}.wrap{max-width:var(--maxw);margin:0 auto;padding:0 28px}.eyebrow{font-family:var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--teal);align-items:center;gap:8px;font-size:12px;display:inline-flex}.eyebrow:before{content:"";background:var(--teal);width:6px;height:6px;box-shadow:0 0 0 3px var(--teal-dim);border-radius:50%}h1,.h1,h2,.h2,h3,.h3,h4,.h4{font-family:var(--mono);color:var(--text);letter-spacing:-.01em;margin:0;font-weight:600}p{color:var(--text-muted);margin:0}section{padding:88px 0}.section-head{max-width:640px;margin-bottom:48px}.section-head h2,.section-head .h2{margin-top:14px;font-size:30px}.section-head p{margin-top:14px;font-size:16.5px}header{z-index:50;background:var(--nav-bg);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-bottom:1px solid var(--border-soft);position:sticky;top:0}.logo{align-items:center;display:flex}.logo-full{flex-shrink:0;width:auto;height:26px}.navbar{min-height:72px;padding-top:0;padding-bottom:0}.navbar-nav .nav-link{color:var(--text-muted);white-space:nowrap;font-size:14.5px;transition:color .15s}.navbar-nav .nav-link:hover{color:var(--text)}@media (width>=992px){.navbar-nav{gap:30px}.navbar-nav .nav-link{padding-left:0;padding-right:0}}.nav-cta{font-family:var(--mono);border:1px solid var(--amber);border-radius:999px;font-size:13.5px;font-weight:500;transition:background .15s,color .15s;color:var(--amber)!important;padding:9px 18px!important}.nav-cta:hover{background:var(--amber);color:var(--on-accent)!important}.navbar-toggler{border:none;padding:8px}.navbar-toggler:focus{box-shadow:none}html[data-theme=light] .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(11,11,11,1)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}html[data-theme=dark] .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(241,238,232,1)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}@media (width<=991.98px){:is(header:has(.navbar-collapse.show),header:has(.navbar-collapse.collapsing)){background:var(--bg);-webkit-backdrop-filter:none;backdrop-filter:none}.navbar-collapse{background:var(--bg);border-top:1px solid var(--border-soft);margin:0 -28px;padding:0 28px}.navbar-nav{padding:10px 0 20px}.navbar-nav .nav-link{border-bottom:1px solid var(--border-soft);padding:14px 0;font-size:16px}.navbar-nav .nav-cta{text-align:center;border:1px solid var(--amber);border-radius:999px;width:100%;margin-top:16px;padding:12px 18px;font-size:14.5px;transition:background .15s,color .15s;display:block;color:var(--amber)!important}}.theme-toggle{border:1px solid var(--border);width:38px;height:38px;color:var(--text-muted);cursor:pointer;background:0 0;border-radius:9px;flex-shrink:0;justify-content:center;align-items:center;transition:border-color .15s,color .15s;display:flex}.theme-toggle:hover{border-color:var(--text-muted);color:var(--text)}.theme-toggle svg{width:18px;height:18px}.theme-toggle .icon-moon,html[data-theme=light] .theme-toggle .icon-sun{display:none}html[data-theme=light] .theme-toggle .icon-moon{display:block}.btn{font-family:var(--mono);border:1px solid var(--border);white-space:nowrap;border-radius:8px;align-items:center;gap:8px;padding:11px 20px;font-size:13.5px;font-weight:500;transition:border-color .15s,background .15s,transform .15s;display:inline-flex}.btn:hover{transform:translateY(-1px)}.btn svg{width:15px;height:15px}.btn-primary{background:var(--amber);color:var(--on-accent);border-color:var(--amber);font-weight:600}.btn-primary:hover{background:var(--amber);box-shadow:0 6px 20px var(--amber-dim)}.btn-ghost{color:var(--text)}.btn-ghost:hover{border-color:var(--text-muted)}.btn-sm,.btn-group-sm>.btn{border-radius:7px;padding:8px 14px;font-size:12.5px}.hero{padding:76px 0 40px}.hero h1,.hero .h1{letter-spacing:-.02em;margin-top:18px;font-size:44px;line-height:1.15}.hero p.sub{max-width:520px;color:var(--text-muted);margin-top:20px;font-size:18px}.hero-grid{grid-template-columns:1.05fr .95fr;align-items:center;gap:64px;display:grid}.hero-ctas{flex-wrap:wrap;gap:14px;margin-top:32px;display:flex}.hero-stack-note{font-family:var(--mono);color:var(--text-dim);flex-wrap:wrap;gap:18px;margin-top:34px;font-size:12.5px;display:flex}.hero-stack-note span{color:var(--text-muted)}.pipeline{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:26px 26px 30px;position:relative}.pipeline-label{font-family:var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim);justify-content:space-between;margin-bottom:18px;font-size:11px;display:flex}.pipeline-track{padding-left:28px;position:relative}.pipeline-track:before{content:"";background:linear-gradient(180deg, var(--sage), var(--brand-red));opacity:.5;width:2px;position:absolute;top:10px;bottom:10px;left:9px}.pl-step{background:var(--surface-2);border:1px solid var(--border-soft);border-radius:9px;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px;padding:13px 16px;display:flex;position:relative}.pl-step:last-child{margin-bottom:0}.pl-step:before{content:"";background:var(--bg);border:2px solid var(--teal);border-radius:50%;width:9px;height:9px;position:absolute;top:50%;left:-23px;transform:translateY(-50%)}.pl-step .name{font-family:var(--mono);color:var(--text);font-size:13.5px;font-weight:500}.pl-step .tag{font-family:var(--mono);color:var(--text-dim);font-size:10.5px}.pl-packet{background:var(--brand-red);border-radius:50%;width:14px;height:14px;animation:5s ease-in-out infinite travel;position:absolute;left:2px;box-shadow:0 0 12px 2px #c71f3838}@keyframes travel{0%{opacity:0;top:8px}6%{opacity:1}46%{opacity:1;top:calc(100% - 22px)}50%{opacity:0}50.01%{top:8px}56%{opacity:1}96%{opacity:1;top:calc(100% - 22px)}to{opacity:0;top:calc(100% - 22px)}}@media (prefers-reduced-motion:reduce){.pl-packet{opacity:1;animation:none;top:8px}html{scroll-behavior:auto}}.split{grid-template-columns:1.1fr .9fr;align-items:start;gap:64px;display:grid}.split h2,.split .h2{font-size:28px}.split .lede{color:var(--text-muted);margin-top:16px;font-size:17px;line-height:1.7}.extending{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);align-items:center;gap:12px;margin-top:26px;font-size:12px;display:flex}.extending .extending-logo{text-transform:none;align-items:center;display:flex}.extending .extending-logo img{width:auto;height:36px;display:block}.extending .lockup{font-family:var(--mono);color:var(--text);text-transform:none;letter-spacing:0;font-size:15px;font-weight:600}.extending .lockup .x{color:var(--text-dim);margin:0 6px;font-weight:400}.traits{grid-template-columns:1fr 1fr;gap:14px;display:grid}.trait{background:var(--surface);border:1px solid var(--border);text-align:left;border-radius:12px;padding:22px 18px}.trait .icon{margin-bottom:12px}.trait p{font-family:var(--mono);color:var(--text);font-size:13px;font-weight:500;line-height:1.4}.products-grid{grid-template-columns:repeat(3,1fr);gap:18px;display:grid}.other-skeletons-grid{grid-template-columns:repeat(2,1fr);max-width:800px}.card{background:var(--surface);border:1px solid var(--border);border-top:3px solid var(--accent,var(--teal));border-radius:var(--radius);flex-direction:column;height:100%;padding:26px 24px 24px;display:flex}.card .card-top{justify-content:space-between;align-items:flex-start;margin-bottom:14px;display:flex}.card h3,.card .h3{color:var(--text);font-size:20px}.card .kicker{font-family:var(--mono);color:var(--text-dim);letter-spacing:.1em;text-transform:uppercase;margin-bottom:6px;font-size:10.5px;display:block}.card p.desc{flex-grow:1;font-size:14.5px;line-height:1.65}.card .card-btns{flex-wrap:wrap;gap:8px;margin-top:20px;display:flex}.badges{flex-wrap:wrap;gap:5px;margin-bottom:16px;display:flex}.badges img{border-radius:3px;height:19px}.standards-grid{background:var(--border);border:1px solid var(--border);border-radius:var(--radius);grid-template-columns:repeat(3,1fr);gap:1px;display:grid;overflow:hidden}.standard{background:var(--surface);padding:30px 26px}.standard h4,.standard .h4{color:var(--text);margin-bottom:10px;font-size:16px}.standard h4 a,.standard .h4 a{color:inherit;border-bottom:1px solid var(--border)}.standard h4 a:hover,.standard .h4 a:hover{color:var(--amber);border-color:var(--amber)}.standard p{font-size:14px;line-height:1.65}.standard .get-started{font-family:var(--mono);color:var(--text-dim);margin-top:14px;font-size:12px}.standard .get-started a{color:var(--teal)}.standard .get-started a:hover{text-decoration:underline}.components-grid{grid-template-columns:1fr 1fr;gap:0 40px;display:grid}details.acc{border-bottom:1px solid var(--border-soft)}details.acc summary{cursor:pointer;font-family:var(--mono);color:var(--text);justify-content:space-between;align-items:center;padding:18px 0;font-size:15px;font-weight:500;list-style:none;display:flex}details.acc summary::-webkit-details-marker{display:none}details.acc summary .chev{color:var(--text-dim);font-size:18px;transition:transform .2s}details.acc[open] summary .chev{color:var(--amber);transform:rotate(45deg)}.accordion{--bs-accordion-color:var(--text);--bs-accordion-bg:var(--surface);--bs-accordion-border-color:var(--border-soft);--bs-accordion-btn-color:var(--text);--bs-accordion-btn-bg:var(--surface);--bs-accordion-active-color:var(--teal);--bs-accordion-active-bg:var(--surface-2);--bs-accordion-btn-focus-box-shadow:0 0 0 3px var(--teal-dim);font-family:var(--mono);margin:24px 0}.accordion-button{font-size:15px;font-weight:500}html[data-theme=dark] .accordion-button:after{filter:invert()grayscale()brightness(1.8)}.acc-body{padding:0 0 20px}.pkg-row{border-top:1px solid var(--border-soft);justify-content:space-between;gap:16px;padding:9px 0;display:flex}.pkg-row:first-child{border-top:none}.pkg-row a{font-family:var(--mono);color:var(--teal);white-space:nowrap;font-size:13.5px}.pkg-row a:hover{text-decoration:underline}.pkg-row .pkg-desc{color:var(--text-muted);text-align:right;font-size:13px}.posts-grid{grid-template-columns:repeat(3,1fr);gap:20px;display:grid}.posts-grid-2-col{grid-template-columns:repeat(2,1fr);gap:20px;display:grid}.post-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;transition:border-color .15s}.post-card:hover{border-color:var(--text-dim)}.post-card h4,.post-card .h4{color:var(--text);margin-top:10px;font-size:16.5px;line-height:1.4}.post-card p{margin-top:10px;font-size:13.5px;line-height:1.6}.post-card .read-more{font-family:var(--mono);color:var(--amber);margin-top:14px;margin-left:2px;font-size:12px;display:inline-block}.post-date{font-family:var(--mono);color:var(--text-dim);letter-spacing:.06em;text-transform:uppercase;font-size:11.5px}.about-band{background:var(--surface);border-top:1px solid var(--border-soft);border-bottom:1px solid var(--border-soft)}.about-inner{grid-template-columns:1fr 1.3fr;align-items:center;gap:56px;display:grid}.about-inner h2,.about-inner .h2{margin-top:14px;font-size:26px;line-height:1.3}.about-inner .about-text{font-size:15px;line-height:1.75}.about-inner .about-cta{margin-top:22px}footer{padding:50px 0 40px}.footer-top{border-bottom:1px solid var(--border-soft);flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:30px;padding-bottom:34px;display:flex}.footer-copy{color:var(--text-dim);max-width:460px;font-size:13px;line-height:1.8}.footer-copy a{color:var(--text-muted);border-bottom:1px solid var(--border)}.footer-social{gap:12px;display:flex}.footer-social a{border:1px solid var(--border);width:38px;height:38px;color:var(--text);border-radius:9px;justify-content:center;align-items:center;transition:border-color .15s,color .15s;display:flex}.footer-social a:hover{border-color:var(--teal);color:var(--teal)}.footer-social svg{width:17px;height:17px}.footer-bottom{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:14px;padding-top:24px;display:flex}.footer-bottom .sponsor{font-family:var(--mono);color:var(--text-dim);font-size:12px}.footer-bottom .sponsor a{color:var(--text-muted);border-bottom:1px solid var(--border)}@media (width<=980px){.hero-grid{grid-template-columns:1fr;gap:44px}.hero{padding-top:44px}.split{grid-template-columns:1fr;gap:40px}.products-grid,.standards-grid{grid-template-columns:repeat(2,1fr)}.posts-grid{grid-template-columns:1fr}.about-inner{grid-template-columns:1fr;gap:26px}}@media (width<=700px){.hero h1,.hero .h1{font-size:32px}.products-grid,.standards-grid,.components-grid{grid-template-columns:1fr}section{padding:56px 0}.pkg-row{flex-direction:column;gap:2px}.pkg-row .pkg-desc{text-align:left}}:focus-visible{outline:2px solid var(--amber);outline-offset:2px}.icon{flex-shrink:0;width:18px;height:18px}.icon-lg{width:50px;height:50px}.icon-jetbrains{min-width:100px}[data-theme=light] .icon{filter:none}[data-theme=dark] .icon{filter:invert()}.contact-grid{grid-template-columns:1fr 1fr;gap:18px;display:grid}@media (width<=700px){.contact-grid{grid-template-columns:1fr}}.contact-card{text-align:center;align-items:center}.contact-card svg{width:40px;height:40px;color:var(--teal);margin-bottom:14px}.contact-card .card-btns{justify-content:center}.approach-card{margin-top:18px}.approach-card h3,.approach-card .h3{margin-bottom:10px}.latest-posts{margin-top:48px}.latest-posts .section-head{margin-bottom:24px}.latest-posts .posts-grid{grid-template-columns:repeat(2,1fr)}@media (width<=700px){.latest-posts .posts-grid{grid-template-columns:1fr}}@media (width<=900px){.sidebar{order:2}}.widget-categories li a{justify-content:space-between;align-items:center;gap:10px;display:flex}.widget-categories .cat-count{font-family:var(--mono);color:var(--text-dim);background:var(--surface-2);border:1px solid var(--border-soft);border-radius:999px;flex-shrink:0;padding:1px 9px;font-size:11px}.widget-categories li a:hover .cat-count{color:var(--text)}.widget-more{margin-top:14px}.widget-more a{font-family:var(--mono);color:var(--amber);font-size:12px}.widget-more a:hover{text-decoration:underline}.no-articles{color:var(--text-dim);font-size:13.5px}.widget-connect li a svg{width:17px;height:17px;color:var(--teal);flex-shrink:0}.breadcrumb-back{font-family:var(--mono);color:var(--text-dim);align-items:center;gap:6px;font-size:13px;display:inline-flex}.breadcrumb-back:hover{color:var(--amber)}.breadcrumb-back svg{width:14px;height:14px}.post-hero{border-bottom:1px solid var(--border-soft);padding:40px 0}.post-hero h1,.post-hero .h1{max-width:820px;margin-top:16px;font-size:38px;line-height:1.2}@media (width<=900px){.post-hero h1,.post-hero .h1{font-size:28px}}.post-meta{font-family:var(--mono);color:var(--text-dim);flex-wrap:wrap;align-items:center;gap:6px 14px;margin-top:22px;font-size:13px;display:flex}.post-meta a{color:var(--text-muted)}.post-meta a:hover{color:var(--amber)}.post-meta .featured-tag{color:var(--brand-red);font-weight:600}.post-body-wrap{padding:52px 0 72px}.post-layout{grid-template-columns:270px 1fr;align-items:start;gap:64px;display:grid}@media (width<=900px){.post-layout{grid-template-columns:1fr;gap:44px}.article{order:1}}.sidebar{flex-direction:column;gap:34px;display:flex}@media (width<=900px){.sidebar{order:2}}.widget{border-bottom:1px solid var(--border-soft);padding-bottom:28px}.widget:last-child{border-bottom:none;padding-bottom:0}.widget ul{margin:0;padding:0;list-style:none}.widget-title{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);margin-bottom:16px;font-size:11.5px}.search-form{border:1px solid var(--border);border-radius:8px;display:flex;overflow:hidden}.search-form input{min-width:0;color:var(--text);font-family:var(--sans);background:0 0;border:none;flex:1;padding:10px 12px;font-size:14px}.search-form input:focus{outline:none}.search-form button{background:var(--surface-2);color:var(--text-muted);cursor:pointer;font-family:var(--mono);border:none;padding:0 14px;font-size:12px}.search-form button:hover{color:var(--amber)}.widget-recent ul{flex-direction:column;gap:14px;display:flex}.widget-recent li a{color:var(--text);font-size:14px;line-height:1.4;display:block}.widget-recent li a:hover{color:var(--amber)}.widget-recent li .post-date{font-family:var(--mono);color:var(--text-dim);margin-top:4px;font-size:11px;display:block}.widget-categories li{border-bottom:1px solid var(--border-soft)}.widget-categories li:last-child{border-bottom:none}.widget-categories li a{color:var(--text-muted);padding:9px 0;font-size:14px;display:block}.widget-categories li a:hover{color:var(--text)}.widget-categories li.current a{color:var(--amber);font-weight:600}.widget-connect ul{flex-direction:column;gap:12px;display:flex}.widget-connect li a{color:var(--text-muted);align-items:center;gap:10px;font-size:14px;display:flex}.widget-connect li a:hover{color:var(--text)}.widget-connect li svg{width:17px;height:17px;color:var(--teal);flex-shrink:0}.widget-approach p{color:var(--text-muted);margin-bottom:12px;font-size:13.5px;line-height:1.7}.widget-approach p:last-child{margin-bottom:0}.entry{max-width:720px}@media (width<=900px){.entry{max-width:100%}}.entry h2,.entry .h2{font-family:var(--mono);color:var(--text);margin-top:48px;margin-bottom:16px;font-size:26px}.entry h2:first-child,.entry .h2:first-child{margin-top:0}.entry h3,.entry .h3{font-family:var(--mono);color:var(--text);margin-top:36px;margin-bottom:14px;font-size:20px}.entry h4,.entry .h4{font-family:var(--mono);color:var(--text);margin-top:28px;margin-bottom:12px;font-size:17px}.entry p{color:var(--text-muted);margin-bottom:20px;font-size:16.5px;line-height:1.8}.entry strong{color:var(--text);font-weight:600}.entry a{color:var(--teal);text-decoration:underline;-webkit-text-decoration-color:var(--border);text-decoration-color:var(--border);text-underline-offset:3px}.entry a:hover{color:var(--amber);-webkit-text-decoration-color:var(--amber);text-decoration-color:var(--amber)}.entry ul{margin:0 0 20px;padding-left:0;list-style:none}.entry ul li{color:var(--text-muted);margin-bottom:10px;padding-left:22px;font-size:16px;line-height:1.7;position:relative}.entry ul li:before{content:"";background:var(--amber);border-radius:50%;width:6px;height:6px;position:absolute;top:10px;left:0}.entry figure{margin:28px 0}.entry figure img{border:1px solid var(--border);border-radius:10px;width:100%;height:auto;display:block}.entry .img-row{gap:20px;margin:28px 0;display:flex}@media (width<=900px){.entry .img-row{flex-direction:column}}.entry .img-row figure{flex:1;margin:0}.entry .img-row img{width:100%}.entry code{font-family:var(--mono);background:var(--surface-2);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-size:.9em}.entry pre{font-family:var(--mono);background:var(--surface-2);border:1px solid var(--border);color:var(--text);border-radius:8px;margin:0 0 20px;padding:16px 18px;font-size:14px;line-height:1.6;overflow-x:auto}.entry pre code{font-size:inherit;background:0 0;border:none;padding:0}.callout{background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--brand-red);border-radius:12px;margin:44px 0;padding:28px 30px}.callout h4,.callout .h4{font-family:var(--mono);color:var(--text);margin-bottom:12px;font-size:18px}.callout p{color:var(--text-muted);margin-bottom:14px;font-size:14.5px;line-height:1.7}.callout ul{margin:0 0 20px;padding-left:0;list-style:none}.callout ul li{color:var(--text-muted);margin-bottom:8px;padding-left:20px;font-size:14.5px;position:relative}.callout ul li:before{content:"";background:var(--brand-red);border-radius:50%;width:5px;height:5px;position:absolute;top:9px;left:0}.post-nav{grid-template-columns:1fr 1fr;gap:16px;margin:48px 0;display:grid}@media (width<=900px){.post-nav{grid-template-columns:1fr}}.post-nav a{border:1px solid var(--border);background:var(--surface);border-radius:12px;padding:20px 22px;display:block}.post-nav a:hover{border-color:var(--text-dim)}.post-nav .direction{font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--text-dim);margin-bottom:8px;font-size:11px;display:block}.post-nav .title{color:var(--text);font-size:14.5px;line-height:1.4}.post-nav .next-post{text-align:right}.comment-section{border-top:1px solid var(--border-soft);margin-top:56px;padding-top:40px}.comment-section h3,.comment-section .h3{font-family:var(--mono);color:var(--text);margin-bottom:24px;font-size:20px}.comment-form-mock{flex-direction:column;gap:16px;max-width:560px;display:flex}.comment-form-mock label{font-family:var(--mono);text-transform:uppercase;letter-spacing:.06em;color:var(--text-dim);margin-bottom:6px;font-size:12px;display:block}.comment-form-mock input,.comment-form-mock textarea{background:var(--surface);border:1px solid var(--border);width:100%;color:var(--text);font-family:var(--sans);border-radius:8px;padding:11px 14px;font-size:14.5px}.comment-form-mock input:focus,.comment-form-mock textarea:focus{border-color:var(--amber);outline:none}.comment-form-mock textarea{resize:vertical;min-height:120px} diff --git a/public/readme/android/listen-for-android-install-referrer.md b/public/readme/android/listen-for-android-install-referrer.md new file mode 100644 index 00000000..002e5f8a --- /dev/null +++ b/public/readme/android/listen-for-android-install-referrer.md @@ -0,0 +1,23 @@ +--- +title: "Listen for Android install referrer" +description: "Android market broadcasts an intent containing referrer information at install time, before the app is opened, which can be used for install tracking." +author: "n3vrax" +date_published: "2011-07-24" +canonical_url: "https://new.dotkernel.com/android/listen-for-android-install-referrer/" +category: "Android" +language: "en" +--- + +# Listen for Android install referrer + +## Getting Referrer Data at Install Time + +Android market sends information at the moment of app install, delivered as a broadcasted intent by Android market at install time - even before the app is opened for the first time. This can be used to create custom links to an Android application, including bits of information about the referrer, sent directly to the app for processing at install. It can be a simple and accurate solution for mobile app install tracking, among other uses. + +## FAQ + +**Q: Does Android send information when the app is installed?** +A: Yes. Android market broadcasts an intent containing referrer information at the moment the app is installed. + +**Q: When is this referrer information available to the app?** +A: It's delivered as a broadcasted intent at install time, before the app is ever opened. diff --git a/public/readme/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.md b/public/readme/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.md new file mode 100644 index 00000000..b0d0c6bb --- /dev/null +++ b/public/readme/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.md @@ -0,0 +1,31 @@ +--- +title: "Multiple broadcast receivers in the same app, for the same action" +description: "Using multiple broadcast receivers to listen separately for the same intent in the same Android app can lead to unexpected results, since one receiver may consume the broadcast and leave the others with nothing." +author: "n3vrax" +date_published: "2011-07-22" +canonical_url: "https://new.dotkernel.com/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action/" +category: "Android" +language: "en" +--- + +# Multiple broadcast receivers in the same app, for the same action + +## The problem + +When multiple broadcast receivers are registered separately to listen for the same intent within the same Android app, this can lead to unexpected results: one broadcast receiver might consume the broadcasted intent, leaving the others with nothing to receive. This can happen when using 3rd party libraries that define their own broadcast receivers alongside an app's own receivers. + +## The approach + +A solution for this kind of problem is a code snippet inspired by the way Admob for Android solves this, as shown in Admob's own documentation, using meta-data in the manifest file. + +## FAQ + +**Q: What problem does this article address?** +A: When multiple broadcast receivers are registered separately to listen for the same intent in the same Android app, this can lead to unexpected results: one broadcast receiver might consume the broadcasted intent, leaving the others with nothing to receive. + +**Q: When is this issue most likely to occur?** +A: This can happen when you use 3rd party libraries that already define their own broadcast receivers alongside your app's own receivers. + +## Resources + +- [Admob App Download Tracking documentation](http://developer.admob.com/wiki/Android_App_Download_Tracking) diff --git a/public/readme/architecture/configprovider-bootstrap-modern-php-applications.md b/public/readme/architecture/configprovider-bootstrap-modern-php-applications.md new file mode 100644 index 00000000..1f3142fe --- /dev/null +++ b/public/readme/architecture/configprovider-bootstrap-modern-php-applications.md @@ -0,0 +1,111 @@ +--- +title: "ConfigProvider - Bootstrap Modern PHP Applications" +description: "An overview of the ConfigProvider pattern used in Laminas/Mezzio-based applications, including Dotkernel, to bootstrap middleware pipelines and dependency injection." +author: "Florin Bidirean" +date_published: "2025-08-20" +canonical_url: "https://new.dotkernel.com/architecture/configprovider-bootstrap-modern-php-applications/" +category: "Architecture" +language: "en" +--- + +# ConfigProvider - Bootstrap Modern PHP Applications + +## TL;DR + +In PHP, a `ConfigProvider` is a class or callable that is part of an application's bootstrap process, returning configuration data that tells the platform which middleware should run, in what order, and under what conditions. Frameworks like Mezzio, Laminas, Slim, and the Dotkernel Headless Platform use ConfigProviders to declare middleware pipeline configuration, dependency injection mappings, and request handlers, which get merged together automatically during bootstrap (except in Dotkernel, where new ConfigProviders must be registered manually). + +## Where Is the ConfigProvider Used? + +Mezzio (formerly Zend Expressive), Laminas, Slim, the Dotkernel Headless Platform, and other middleware-based frameworks often have a `ConfigProvider` class. In Laminas/Mezzio specifically, each module or package may contain a `ConfigProvider` that returns: + +- Middleware pipeline configuration: + - Middleware classes or service names. + - Error-handling middleware, which should have the lowest priority. + - Middleware groups or nested arrays. +- Dependency injection mappings. +- Request Handlers. + +Example structure used in Dotkernel: + +```php +class ConfigProvider +{ + public function __invoke(): array + { + return [ /* ... */ ]; + } + + public function getDependencies(): array + { + return [ + 'factories' => [ /* ... */ ], + 'invokables' => [ /* ... */ ], + ]; + } + + public function getTemplates(): array + { + return [ + 'paths' => [ /* ... */ ], + 'error' => [ /* ... */ ], + ]; + } +} +``` + +What each item means: + +| Item | Meaning | +|---|---| +| `dependencies` | Used by the dependency injector (e.g. laminas-servicemanager) to construct every requested service. | +| `factories` | The factory builds the service. | +| `invokables` | The service is built with `new` directly. | +| `aliases` | Redirects to another service name. | +| `delegators` | Wraps the original service. | +| `templates` | Defines the paths for the template files. | + +## How the ConfigProvider Works + +The ConfigProvider is automatically picked up by the framework during application bootstrap: + +1. **Merge the global configuration** - All ConfigProviders are merged into one array. +2. **Read the configuration array** - A call similar to `$config = $container->get('config') ?? [];` reads an array of entries. +3. **Resolve item** - `$app->pipe()` is called to resolve one of the following: resolve the service name from the container, wrap the middleware if an array is provided, or call the closure or invokable object. +4. **Handle errors** - The error-handling middleware is the last one in the pipeline, to make sure it can handle any exceptions. +5. **Execute at runtime** - Laminas Stratigility iterates over the pipeline in the order it was registered. Each middleware can handle the request and return a response, or delegate execution to the next middleware in the pipeline, until a `ResponseInterface` is returned to the client. + +## Benefits + +- **Centralized setup** - Instead of hardcoding bootstrap code, it's declared in a config provider so it's easy to read, change, or extend. +- **Modular** - Each package can ship with its own config without interfering with others. +- **Container-friendly** - Works well with frameworks using DI containers like Laminas ServiceManager, PHP-DI, or Pimple. +- **Standardized service definitions** - Consistent rules for object creation, separate from business logic. +- **Auto-Discovery** - In Laminas/Mezzio, the ConfigAggregator automatically loads and merges all ConfigProviders. Dotkernel is an exception: new ConfigProviders have to be added manually in `config/config.php`, because all the initial ConfigProviders required to install the applications are already injected. +- **Environment-agnostic** - Returns an array that defines dev, test, or prod environments. +- **Testability** - The consistent, central configuration promotes isolated (e.g. per-module) testing, easier swapping of dependencies, and assertion of pipeline setup (e.g. checking if a config key is present). + +## FAQ + +**Q: What is a ConfigProvider in PHP?** +A: It is a class that is part of an application's bootstrap process: a class or callable that returns configuration data telling the platform which middleware should run, in what order, and sometimes under what conditions. + +**Q: What does the ConfigProvider return in the Laminas/Mezzio ecosystem?** +A: In the Laminas/Mezzio ecosystem, it's literally an array of configuration, settings, or anything else the application needs, and each module or package may contain its own ConfigProvider returning middleware pipeline configuration, dependency injection mappings, and request handlers. + +**Q: What is the difference between 'factories' and 'invokables' in the dependencies array?** +A: `factories` will have the factory build the service, while `invokables` will use `new` directly. You can also use `aliases` to redirect to another service name and `delegators` to wrap the original service. + +**Q: How does the ConfigProvider get used during application bootstrap?** +A: It is automatically picked up by the framework during bootstrap: all ConfigProviders are merged into one array, the configuration array is read, each item is resolved via `$app->pipe()`, the error-handling middleware is placed last in the pipeline, and at runtime Laminas Stratigility iterates over the pipeline in the order it was registered. + +**Q: Are new ConfigProviders auto-discovered in Dotkernel?** +A: Dotkernel is an exception to the usual auto-discovery rule: new ConfigProviders have to be added manually in `config/config.php`, because all the initial ConfigProviders required to install the applications are already injected. + +**Q: What are the benefits of using a ConfigProvider?** +A: Benefits include centralized setup instead of hardcoded bootstrap code, modularity so each package can ship its own config, container-friendliness with DI containers like Laminas ServiceManager, PHP-DI or Pimple, standardized service definitions, environment-agnostic configuration for dev/test/prod, and better testability of the pipeline setup. + +## Resources + +- [Mezzio Container](https://docs.mezzio.dev/mezzio/v3/features/container/config/) +- [Laminas Config Aggregator](https://docs.laminas.dev/laminas-config-aggregator/config-providers/) +- [PSR-15 (HTTP Server Request Handlers)](https://www.php-fig.org/psr/psr-15/) diff --git a/public/readme/architecture/request-lifecycle-for-a-mezzio-based-application.md b/public/readme/architecture/request-lifecycle-for-a-mezzio-based-application.md new file mode 100644 index 00000000..a61978f6 --- /dev/null +++ b/public/readme/architecture/request-lifecycle-for-a-mezzio-based-application.md @@ -0,0 +1,67 @@ +--- +title: "Request Lifecycle for a Mezzio-Based Application" +description: "A step-by-step walkthrough of how Dotkernel Light, a Mezzio-based application, handles an HTTP request from bootstrap through to the emitted response." +author: "Florin Bidirean" +date_published: "2026-05-26" +canonical_url: "https://new.dotkernel.com/architecture/request-lifecycle-for-a-mezzio-based-application/" +category: "Architecture" +language: "en" +--- + +# Request Lifecycle for a Mezzio-Based Application + +## TL;DR + +The request lifecycle is the sequence of steps that happen from the moment a user makes an HTTP request until the server sends back a response. This is illustrated using Dotkernel Light, one of the applications in the Dotkernel Headless Platform suite, walking through entry point setup, routing, handler execution, template rendering, response creation, and the response emitter. + +## The Request Lifecycle, Step by Step + +### Entry Point + +1. **HTTP Request** - Bootstrap the application, load configuration and create the Mezzio application instance. +2. **Service Container** - Register factories, aliases and delegators. All services are configured and ready to use. +3. **Route Registration** - Read all available routes with their allowed request methods and dynamically register them in the application. Routes are managed by FastRoute. Example: `/page/about` -> `GetPageViewHandler`, Method: `GET`, Route name: `page::about`. +4. **Middleware Pipeline** - Loads the predefined order of middleware. It defines how incoming HTTP requests move through the application and how responses are generated. + +### Processing + +5. **Routing** - FastRoute matches the URL and method against registered routes. Match: `GET /page/about`, Handler: `GetPageViewHandler`, Route name: `page::about`. +6. **Handler Invocation** - Extract the matched route name from the request and pass it to the renderer: + ```php + $template = $request->getAttribute(RouteResult::class)->getMatchedRouteName(); + // $template = 'page::about'; + ``` +7. **Custom Logic Execution in Handler** - Execute the business logic in the handler. The process can involve services and any custom logic. +8. **Template Rendering** - Twig loads the template, applies the layout, renders blocks and includes partials. Load: `src/Page/templates/page/about.html.twig`, Extends: `@layout/default.html.twig`, Render blocks: `title`, `content`, Include partials: `alerts.html.twig`, etc., Output: Final HTML. +9. **Response Creation** - An `HtmlResponse` is created with status, headers and the rendered HTML body. Status: `200 OK`, Content-Type: `text/html; charset=utf-8`, Body: Rendered HTML. +10. **Response Pipeline** - The response flows back through the middleware stack. Middleware can modify headers, cookies, compress content, etc. + +### Exit Point + +11. **Response Emitter** - The final response is sent back to the browser. The page is rendered and sent to the user, as one of `HTTP 20x/30x`, `HTTP 40x`, or `HTTP 50x`. + +## FAQ + +**Q: What is the request lifecycle?** +A: The request lifecycle is the sequence of steps that happen from the moment a user makes an HTTP request until the server sends back a response. + +**Q: What happens at the entry point of a request?** +A: The application bootstraps and loads configuration to create the Mezzio application instance, registers factories, aliases and delegators in the service container, reads all available routes with their allowed request methods and registers them (managed by FastRoute), and loads the predefined order of middleware in the pipeline. + +**Q: How does routing work in a Mezzio-based application?** +A: FastRoute matches the incoming URL and method against the registered routes, for example matching a GET request to `/page/about` against the `GetPageViewHandler` handler under the route name `page::about`. + +**Q: What happens during handler invocation?** +A: The matched route name is extracted from the request attribute and passed to the renderer, using code similar to `$template = $request->getAttribute(RouteResult::class)->getMatchedRouteName();`, after which the handler executes the custom business logic. + +**Q: What happens during template rendering?** +A: Twig loads the matched template file, applies the layout it extends, renders its blocks, and includes any partials, producing the final HTML output. + +**Q: How is the response created and returned to the browser?** +A: An `HtmlResponse` is created with a status code, headers, and the rendered HTML body. It then flows back through the middleware stack in reverse (the response pipeline), where middleware can modify headers, cookies, or compress content, before the response emitter sends the final response back to the browser as HTTP 20x/30x, 40x, or 50x. + +## Resources + +- [Dotkernel Light on GitHub](https://github.com/dotkernel/light) +- [Dotkernel Light documentation](https://docs.dotkernel.org/light-documentation/) +- [Dotkernel Headless Platform suite on GitHub](https://github.com/dotkernel) diff --git a/public/readme/architecture/understanding-middleware.md b/public/readme/architecture/understanding-middleware.md new file mode 100644 index 00000000..bce2f0c2 --- /dev/null +++ b/public/readme/architecture/understanding-middleware.md @@ -0,0 +1,139 @@ +--- +title: "Understanding Middleware" +description: "An introduction to middleware in PHP web applications: what it is, what it's used for, how PSR-15 defines it, and how it's called within an application's pipeline." +author: "Florin Bidirean" +date_published: "2025-05-22" +canonical_url: "https://new.dotkernel.com/architecture/understanding-middleware/" +category: "Architecture" +language: "en" +--- + +# Understanding Middleware + +## TL;DR + +Middleware is code that exists between the request and response: it can take an incoming request, act on it, and either complete the response itself or delegate to the next middleware in the queue. It's used for concerns like authentication, CORS, caching, rate limiting, and more, and in PHP a PSR-15 compliant middleware implements `Psr\Http\Server\MiddlewareInterface` with a single `process()` method. + +## The Purpose of Middleware + +Middleware makes it easier for software developers to implement communication and input/output, so they can focus on the specific purpose of their application. In web services, the `Input` represents the `Request` received, and `Output` represents the `Response` to be sent. + +## Using Middleware + +Middleware can be used for purposes such as, but not limited to: + +- A/B Testing +- Debugging +- Caching +- CORS +- Authentication (HTTP Basic Auth, OAuth 2.0, OpenID) +- CSRF Protection +- Rate Limiting +- Referrals +- IP Restriction + +## Usage + +According to PSR-15: HTTP Server Request Handlers, a component that processes an incoming request and generates a response is a middleware. To be compliant with the PSR-15 standard, the middleware must implement `Psr\Http\Server\MiddlewareInterface`: + +```php +class MyMiddleware implements MiddlewareInterface +``` + +The middleware class must then implement the `process` method: + +```php +public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface; +``` + +Example implementation of a middleware which processes the request: + +```php +class ExampleMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + //process request + + return $handler->handle($request); + } +} +``` + +Example implementation of a middleware which processes the response: + +```php +class ExampleMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + + //process response + + return $response; + } +} +``` + +An approach that processes both the request and response: + +```php +class ExampleMiddleware implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + //process request + + $response = $handler->handle($request); + + //process response + + return $response; + } +} +``` + +## How Middleware Is Called + +The application pipeline defines the execution flow. The request passes through the middleware in the pipeline, one by one, in the order they are placed in the pipeline. Each middleware processes the request and/or response and either passes control to the next middleware in the chain or terminates the request and returns a response. + +- If control passes through all middleware successfully, execution is eventually passed to the custom code which generates a response of its own. Execution then passes through the middleware in reverse order and returns the response. +- If execution is terminated before reaching the custom code (e.g. via an exception), the response is generated by the last middleware reached by the execution. + +## Middleware in Practice + +A simple real world example of middleware usage is the enhancement of a request with the user IP for logging purposes or building reports based on geographical data. For this example the pipeline has a single middleware. + +The flow begins with a request. Execution passes control to the IP middleware, which enhances the request with the user's IP and other relevant data. Control passes to the custom handler that processes the request and returns a response. The flow continues in reverse order, back to the IP middleware, which can, if needed, change the output before it gets returned to the user that initiated the request. + +## FAQ + +**Q: What is middleware?** +A: Middleware is code that exists between the request and response, and which can take the incoming request, perform actions based on it, and either complete the response or pass delegation on to the next middleware in the queue. + +**Q: What is the purpose of middleware?** +A: Middleware makes it easier for software developers to implement communication and input/output, so they can focus on the specific purpose of their application. In web services, the Input represents the Request received, and Output represents the Response to be sent. + +**Q: What can middleware be used for?** +A: Middleware can be used for purposes such as A/B testing, debugging, caching, CORS, authentication (HTTP Basic Auth, OAuth 2.0, OpenID), CSRF protection, rate limiting, referrals, and IP restriction. + +**Q: What interface must PHP middleware implement to be PSR-15 compliant?** +A: According to PSR-15, a compliant middleware must implement `Psr\Http\Server\MiddlewareInterface`, which requires a `process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface` method. + +**Q: How does middleware get called within the application pipeline?** +A: The application pipeline defines the execution flow: the request passes through the middleware one by one, in the order they are placed. If control passes through all middleware successfully, execution is passed to your custom code, which generates a response, and execution then passes back through the middleware in reverse order. If execution is terminated before reaching your custom code (e.g. via an exception), the response is generated by the last middleware reached. + +**Q: What is a practical, real-world example of middleware?** +A: A simple example is enhancing a request with the user's IP for logging purposes or geographical reporting. The request first passes through the IP middleware, which enhances the request with the user's IP and other relevant data, then control passes to the custom handler that processes the request and returns a response. The flow continues in reverse, back through the IP middleware, which can change the output before it's returned to the user. + +## Resources + +- [Why Care About PHP Middleware?](https://philsturgeon.uk/php/2016/05/31/why-care-about-php-middleware/) +- [Learn more about Mezzio from the source](https://docs.mezzio.dev/) +- [Laminas components](https://docs.laminas.dev/components/) +- [Dotkernel Light, an implementation of Mezzio using handlers](https://github.com/dotkernel/light) +- [The Slim PHP micro framework](https://www.slimframework.com/) +- [The PHP Framework Interop Group's full list of PSRs](https://www.php-fig.org/psr/) +- [PSR-7: The magical middleware tour](https://vimeo.com/showcase/4061778/video/177154167) +- [From Helpers to Middleware](https://www.youtube.com/watch?v=v1I57-_Rsv0) diff --git a/public/readme/best-practice/aptana-set-svn-keywords.md b/public/readme/best-practice/aptana-set-svn-keywords.md new file mode 100644 index 00000000..360359e0 --- /dev/null +++ b/public/readme/best-practice/aptana-set-svn-keywords.md @@ -0,0 +1,38 @@ +--- +title: "Aptana - set SVN keywords" +description: "How to set the svn:keywords property (e.g. Id) for a file in Aptana, so SVN replaces the keyword marker with commit metadata." +author: "Teo" +date_published: "2011-04-04" +canonical_url: "https://new.dotkernel.com/best-practice/aptana-set-svn-keywords/" +category: "Best Practice" +language: "en" +--- + +# Aptana - set SVN keywords + +## Overview + +In Aptana it's very simple to set the svn:keywords property for a file. For example, to set the svn keyword property `Id`: + +## Steps + +1. In the file where the svn keyword property should be added, write `$Id$`. +2. Right click on the file, then follow Team -> Set Property... (Note: "Set Property..." will not be active if the file hasn't first been added to SVN via Team -> Add to Version Controller). +3. Select `svn:keywords`, and write `Id` in the text field. + +When the SVN commit of the file is made, the `$Id$` keyword will be replaced with text containing the file's SVN metadata, in a specific format. + +## FAQ + +**Q: How do you set the svn:keywords property for a file in Aptana?** +A: Write the keyword marker (for example `$Id$`) in the file, then right click the file and follow Team -> Set Property..., select `svn:keywords`, and write `Id` in the text field. + +**Q: Why is "Set Property..." not active when I right click the file?** +A: Set Property... will not be active if the file hasn't first been added to SVN. Use Team -> Add to Version Controller before trying to set the property. + +**Q: What happens to the $Id$ keyword after an SVN commit?** +A: After the SVN commit of the file, the `$Id$` keyword is replaced with text containing the file's SVN metadata, in a specific format. + +## Resources + +- [svn:keywords property documentation](http://svnbook.red-bean.com/en/1.4/svn.advanced.props.special.keywords.html) diff --git a/public/readme/best-practice/basic-security-in-dotkernel-headless-platform.md b/public/readme/best-practice/basic-security-in-dotkernel-headless-platform.md new file mode 100644 index 00000000..03757580 --- /dev/null +++ b/public/readme/best-practice/basic-security-in-dotkernel-headless-platform.md @@ -0,0 +1,135 @@ +--- +title: "Basic Security in Dotkernel Headless Platform" +description: "A practical overview of software security practices implemented across the Dotkernel Headless Platform, covering input validation, content negotiation, CORS, RBAC, OAuth2, sessions, dependencies, and more." +author: "Florin Bidirean" +date_published: "2025-10-14" +canonical_url: "https://new.dotkernel.com/best-practice/basic-security-in-dotkernel-headless-platform/" +category: "Best Practice" +language: "en" +--- + +# Basic Security in Dotkernel Headless Platform + +## TL;DR + +Software security should always be top of mind for a developer, since ignoring it can lead to major costs, data loss, GDPR fines, or the loss of client trust. The article surveys many facets of software security and walks through the practical measures Dotkernel Headless Platform takes for each: input validation, content negotiation, CORS, RBAC, demo credentials, error reporting, OpenAPI docs, PHP and JavaScript dependencies, OAuth2, session/cookie settings, and CI checks. + +## Facets of Software Security + +There are many potential ways a hacker can access code or data fraudulently: + +- Authentication and access control. +- Data protection. +- Input validation and injection. +- Web and API security. +- Dependency and supply chain risks. +- Configuration and deployment. +- Network and infrastructure security. +- Logging, monitoring and incident response. +- Secure software development lifecycle. +- Human and organizational factors. + +## The Tenets of Software Security in Dotkernel Headless Platform + +Dotkernel aims to: + +- Create code that follows software security guidelines. +- Implement community recommendations related to software security. +- Use 3rd-party code and libraries from trusted sources. +- Constantly monitor software news related to security vulnerabilities and mitigate them as soon as possible. + +## Form Input Validation + +Never trust that user input is correct by passing it directly into business logic. By defining the configuration for an input filter, a field's presence and type are both ensured. Dotkernel API uses laminas/laminas-inputfilter for this purpose. Dotkernel Admin additionally uses laminas/laminas-form, which contains a thin layer of objects representing form elements, an InputFilter for each input (or custom validators), and methods for binding data to and from the form. laminas-form integrates with the Laminas Security Ecosystem: laminas-escaper, laminas-validator, laminas-session, and laminas-filter. + +## Content Negotiation + +Content negotiation is used in RESTful APIs so client and server agree on the format and language of exchanged data. Dotkernel API handles this via a middleware configured in `config/autoload/content-negotiation.global.php`, using the `Content-Type` and `Accept` HTTP request headers, and returning `application/json` or `application/hal+json` data formats. + +## Cross-Origin Resource Sharing + +CORS is a browser security mechanism controlling how web pages can request resources from a different domain. In Dotkernel API, CORS is handled by mezzio/mezzio-cors and configured in `config/autoload/cors.local.php`. It starts detecting the proper `cors` configuration whenever it detects a `cors preflight`, validating the call using configuration items: origins, headers, max age, and credentials. + +> When configuring your pipeline, make sure to add the CorsMiddleware BEFORE the RouteMiddleware. + +## Role-Based Access Control + +RBAC manages access to resources by assigning roles to user types, which are in turn assigned to users requiring a certain level of access. Dotkernel API uses mezzio/mezzio-authorization-rbac for this purpose, with several predefined roles configurable in `config/autoload/authorization.global.php`. + +## Demo Credentials + +Demo credentials are provided in Dotkernel API for convenience, to allow easy testing of the installation. + +> It is important to update or remove these accounts in your production environment. + +## Error Reporting Endpoint and ErrorReportingTokens + +The error reporting endpoint provides a reliable channel through which 3rd-party developers can report issues directly. Dotkernel API has a dedicated `/error-report` endpoint for this, using an `ErrorReportingToken` set up in `config/autoload/error-handling.global.php`. + +## OpenAPI Documentation + +OpenAPI documentation (formerly Swagger) provides a standardized, machine-readable way to describe API requests and responses. It's critical for developer efficiency (streamlines front/back-end communication, allows mock servers before the backend is implemented), reliability (auto-generated docs, easier testing), and integration (tools like Postman and Codegen libraries). Dotkernel API implements zircote/swagger-php to provide interactive documentation. + +> Do not include sensitive information for your endpoints. Do not enable documentation in a production environment. + +## PHP Dependencies + +Modern PHP projects rely heavily on external packages via Composer, and there is a tangible risk of exposing an application through insecure dependencies. Dotkernel API has regular checks for vulnerable and outdated packages, including transient dependencies. + +> Always use dependencies from reliable sources and keep them updated to their latest version. + +## OAuth2 Security + +OAuth 2.0 is a secure authorization framework letting one application access resources on behalf of a user without requiring the user's password, an industry standard for web, mobile, and API-based systems. Dotkernel API uses mezzio/mezzio-authentication-oauth2 for OAuth2 authentication. The package itself is secure, but it must be used properly: + +- Replace or update the default `admin` and `frontend` clients on production. +- Update the `access` and `refresh` tokens to match your application's requirements (defaults are one day for access, one month for refresh). +- Never commit any local keys generated by `./vendor/bin/generate-oauth2-keys`, since they verify the transmitted JWTs. + +## Session and Cookie Settings + +Sessions and cookies store data between HTTP requests, such as login information, preferences, or user behavior tracking. Dotkernel configures cookies in `config/autoload/session.global.php`, which contains parameters that must be revised and adapted: + +- `session_config.cookie_httponly` +- `session_config.cookie_samesite` +- `session_config.cookie_secure` + +## JavaScript Dependencies + +JavaScript has its own dependencies, usually installed via npm or yarn. The JavaScript ecosystem has recently been attacked by hackers targeting several widely used npm packages with billions of total uses. Dotkernel uses npm to handle JavaScript dependencies, monitors the news for security issues, and uses packages from reliable sources. `npm audit` should still be used regularly to check for vulnerabilities. + +## Other Security Considerations + +All components of Dotkernel Headless Platform have configuration files named `*.global.php`, `*.php.dist`, and `*.local.php`. Sensitive information must only go in `*.local.php` files, since they are ignored by the VCS by default. Development mode enables features like debug mode, cache clear, and error details, which should be hidden from production to avoid exposing sensitive data or code. The Laminas Continuous Integration GitHub Action is integral to Dotkernel API, running a matrix of static analysis, coding standards checks, and unit tests, most often triggered by commits. + +## FAQ + +**Q: What are the main facets of software security to consider?** +A: Software security spans many areas: authentication and access control, data protection, input validation and injection, web and API security, dependency and supply chain risks, configuration and deployment, network and infrastructure security, logging/monitoring and incident response, secure software development lifecycle, and human and organizational factors. + +**Q: How does Dotkernel handle form input validation?** +A: Dotkernel API uses laminas/laminas-inputfilter to ensure a field is present and of the correct type. Dotkernel Admin additionally uses laminas/laminas-form, which provides form element objects, an InputFilter for each input (or custom validators), and methods for binding data to and from the form, integrating with laminas-escaper, laminas-validator, laminas-session, and laminas-filter. + +**Q: How does Dotkernel API handle content negotiation?** +A: Content negotiation is handled via a middleware configured in the `config/autoload/content-negotiation.global.php` file. It uses the Content-Type and Accept HTTP request headers to negotiate with the client, returning application/json or application/hal+json data formats. + +**Q: How is CORS handled and configured in Dotkernel API?** +A: CORS is handled by mezzio/mezzio-cors and configured in the `config/autoload/cors.local.php` file, validating calls using configuration items like origins, headers, max age, and credentials. When configuring the pipeline, the CorsMiddleware must be added before the RouteMiddleware. + +**Q: What should be done with the demo credentials before going to production?** +A: Demo credentials are provided for convenience during installation testing, but it is important to update or remove these accounts in your production environment. + +**Q: What are the security recommendations around OpenAPI documentation?** +A: You should not include sensitive information for your endpoints in the OpenAPI documentation, and you should not enable the documentation in a production environment. + +## Resources + +- [Basic Security in Dotkernel Admin](https://docs.dotkernel.org/admin-documentation/v6/security/basic-security/) +- [Basic Security in Dotkernel API](https://docs.dotkernel.org/api-documentation/v6/security/basic-security/) +- [Content Negotiation in Dotkernel REST API](https://www.dotkernel.com/dotkernel-api/content-negotiation-in-dotkernel-rest-api/) +- [laminas-form Documentation](https://docs.laminas.dev/laminas-form/v3/intro/) +- [CORS in Dotkernel API](https://docs.dotkernel.org/api-documentation/v6/tutorials/cors/) +- [CORS Policy Setup in Dotkernel](https://www.dotkernel.com/how-to/mezzio-cors-implementation-in-dotkernel/) +- [Error Reporting Endpoint](https://docs.dotkernel.org/api-documentation/v6/core-features/error-reporting/) +- [OpenAPI Documentation](https://docs.dotkernel.org/api-documentation/v6/openapi/introduction/) +- [mezzio/mezzio-authentication-oauth2 Configuration](https://docs.mezzio.dev/mezzio-authentication-oauth2/v1/intro/#configuration) diff --git a/public/readme/best-practice/golden-rules-of-professional-php-coding.md b/public/readme/best-practice/golden-rules-of-professional-php-coding.md new file mode 100644 index 00000000..07310e0e --- /dev/null +++ b/public/readme/best-practice/golden-rules-of-professional-php-coding.md @@ -0,0 +1,53 @@ +--- +title: "Golden Rules of Professional PHP Coding" +description: "A short list of practical rules for professional PHP development: error reporting settings, fixing warnings, marking hacks, single-responsibility functions, version control, and IDE usage." +author: "admin" +date_published: "2011-06-12" +canonical_url: "https://new.dotkernel.com/best-practice/golden-rules-of-professional-php-coding/" +category: "Best Practice" +language: "en" +--- + +# Golden Rules of Professional PHP Coding + +## The Rules + +1. Always use, in development and in staging, the highest error reporting level, and display_errors ON: + ```php + error_reporting(-1); + ini_set('display_errors', 1); + ``` +2. Fix every warning or notice that occurs. +3. Check regularly the server's error_log for notices/warnings. +4. Identify any temporary hack with a special mark, for example: + ```php + #@TODO masterpiece by @smartguy, to quick fix the division by zero + ``` +5. Each function must do a single task. If it logs in the user and records the login in a stats table, create a separate function for the "record the login" part - maybe even a distinct class for stats. +6. Use a version control system. SVN is NOT dead. +7. Use an IDE, such as Aptana 2, Aptana 3, Eclipse, or Zend Studio. +8. Know your IDE: code snippets, code assist, integration with Zend Framework, SVN integration, bug tracker integration, and so on. + +## FAQ + +**Q: What error reporting settings should be used in development and staging?** +A: Always use the highest error reporting level and turn display_errors ON, for example with `error_reporting(-1);` and `ini_set('display_errors', 1);`. + +**Q: What should you do about warnings and notices?** +A: Fix every warning or notice that occurs, and regularly check the server's error_log for notices and warnings. + +**Q: How should temporary hacks or quick fixes be marked in code?** +A: Identify any temporary hack with a special mark, such as a `#@TODO` comment noting who added it and why. + +**Q: What is the rule about what a function should do?** +A: Each function must do a single task. For example, if you're logging in a user and also recording that login in a stats table, create a separate function (or even a distinct class) for the stats recording, rather than combining both tasks in one function. + +**Q: What tools does the article recommend for professional PHP development?** +A: It recommends using a version control system (noting that SVN is not dead) and using an IDE such as Aptana 2, Aptana 3, Eclipse, or Zend Studio, and knowing your IDE's code snippets, code assist, Zend Framework integration, SVN integration, and bug tracker integration. + +## Resources + +- [Integrated development environment (Wikipedia)](http://en.wikipedia.org/wiki/Integrated_development_environment) +- [Aptana 2 download](http://www.aptana.com/products/studio2/download) +- [Aptana 3 download](http://www.aptana.com/products/studio3/download) +- [Zend Studio](http://www.zend.com/en/products/studio/) diff --git a/public/readme/best-practice/htaccess-301-redirect-non-www-to-www.md b/public/readme/best-practice/htaccess-301-redirect-non-www-to-www.md new file mode 100644 index 00000000..5692ef1b --- /dev/null +++ b/public/readme/best-practice/htaccess-301-redirect-non-www-to-www.md @@ -0,0 +1,39 @@ +--- +title: "htaccess 301 redirect non-www to www" +description: "How to configure .htaccess RewriteCond/RewriteRule directives to redirect a non-www domain to its www version, or vice versa." +author: "admin" +date_published: "2011-03-14" +canonical_url: "https://new.dotkernel.com/best-practice/htaccess-301-redirect-non-www-to-www/" +category: "Best Practice" +language: "en" +--- + +# htaccess 301 redirect non-www to www + +## Redirect non-www to www + +To always redirect users to the www site (for example: `http://dotboost.com` to `http://www.dotboost.com`), add the following lines to `.htaccess`, right after `RewriteEngine On`: + +```shell +RewriteCond %{HTTP_HOST} ^dotboost.com +RewriteRule ^(.*)$ http://www.dotboost.com/$1 +``` + +## Redirect www to non-www + +If, instead, you want to redirect `http://www.dotboost.com` to `http://dotboost.com`, add the following lines instead: + +```shell +RewriteCond %{HTTP_HOST} ^www.dotboost.com +RewriteRule ^(.*)$ http://dotboost.com/$1 +``` + +Replace `dotboost.com` with your site's domain in either case. + +## FAQ + +**Q: How do I redirect a non-www domain to www using .htaccess?** +A: Add `RewriteCond %{HTTP_HOST} ^dotboost.com` and `RewriteRule ^(.*)$ http://www.dotboost.com/$1` to your .htaccess file, right after `RewriteEngine On`, replacing dotboost.com with your own domain. + +**Q: How do I redirect a www domain to non-www instead?** +A: Add `RewriteCond %{HTTP_HOST} ^www.dotboost.com` and `RewriteRule ^(.*)$ http://dotboost.com/$1` instead, again replacing dotboost.com with your own domain. diff --git a/public/readme/best-practice/insert-update-delete-statements-with-zend-db.md b/public/readme/best-practice/insert-update-delete-statements-with-zend-db.md new file mode 100644 index 00000000..6f59c25f --- /dev/null +++ b/public/readme/best-practice/insert-update-delete-statements-with-zend-db.md @@ -0,0 +1,96 @@ +--- +title: "INSERT, UPDATE, DELETE statements with Zend_Db" +description: "How to write INSERT, UPDATE, and DELETE (DML) statements using Zend_Db, alongside their equivalent raw SQL." +author: "Teo" +date_published: "2010-06-16" +canonical_url: "https://new.dotkernel.com/best-practice/insert-update-delete-statements-with-zend-db/" +category: "Best Practice" +language: "en" +--- + +# INSERT, UPDATE, DELETE statements with Zend_Db + +## TL;DR + +DML (Data Manipulation Language) statements change data values in database tables. This article, continuing the Zend_Db series, shows how the three primary DML statements — INSERT, UPDATE, and DELETE — are written in raw SQL and translated into Zend_Db method calls. + +## Connecting to the database + +```php +$db = Zend_Db::factory('Pdo_Mysql', $dbConnect); +``` + +## INSERT + +SQL: + +```sql +INSERT INTO user(email, password, firstName, lastName, active) + VALUES ('$email', '$password', '$firstName', '$lastName', 1); +``` + +Zend_Db: + +```php +$data = array( 'email' => $email, + 'password' => $password, + 'firstName' => $firstName, + 'lastName' => $lastName, + 'active' => '1'); +$db->insert('user', $data); +``` + +## UPDATE + +SQL: + +```sql +UPDATE user + SET password = '$password', + firstName = '$firstName', + lastName = '$lastName', + accountUpdate = (accountUpdate +1) + WHERE id = '$id' +``` + +Zend_Db: + +```php +$data = array('password' => $password, + 'firstName' => $firstName, + 'lastName' => $vlastname, + 'accountUpdate' => new Zend_Db_Expr('accountUpdate+1')); +$db->update('user', $data, 'id = '.$id); +``` + +## DELETE + +SQL: + +```sql +DELETE FROM user WHERE id = '$id' +``` + +Zend_Db: + +```php +$db->delete('user', 'id = '.$id); +``` + +## FAQ + +**Q: What are DML statements?** +A: DML (Data Manipulation Language) statements are statements that change data values in database tables. There are 3 primary DML statements: INSERT, UPDATE, and DELETE. + +**Q: How do you insert a new row with Zend_Db?** +A: Build an associative array of column names to values (e.g. email, password, firstName, lastName, active) and pass it to $db->insert('user', $data), which corresponds to an SQL INSERT INTO ... VALUES statement. + +**Q: How do you update rows with Zend_Db, including incrementing a column?** +A: Build a $data array of the columns to update, using a Zend_Db_Expr for expressions such as incrementing accountUpdate (new Zend_Db_Expr('accountUpdate+1')), then call $db->update('user', $data, 'id = '.$id). + +**Q: How do you delete a row with Zend_Db?** +A: Call $db->delete('user', 'id = '.$id), which is equivalent to the SQL statement DELETE FROM user WHERE id = '$id'. + +## Resources + +- [Zend_Db series](http://www.dotkernel.com/dotkernel/sql-select-zend-db/) diff --git a/public/readme/best-practice/sql-queries-using-zend-db-select.md b/public/readme/best-practice/sql-queries-using-zend-db-select.md new file mode 100644 index 00000000..2c81919b --- /dev/null +++ b/public/readme/best-practice/sql-queries-using-zend-db-select.md @@ -0,0 +1,131 @@ +--- +title: "SQL queries using Zend_Db – SELECT" +description: "How to write SELECT queries with JOINs and WHERE IN clauses using Zend_Db, alongside their equivalent raw SQL." +author: "Teo" +date_published: "2010-06-15" +canonical_url: "https://new.dotkernel.com/best-practice/sql-queries-using-zend-db-select/" +category: "Best Practice" +language: "en" +--- + +# SQL queries using Zend_Db – SELECT + +## TL;DR + +Zend_Db and its related classes provide a simple SQL database interface for Zend Framework. This article shows how classical SELECT queries with JOINs and WHERE IN clauses are translated into Zend_Db's select() style, and how to debug the generated query. + +## Connecting to the database + +```php +$db = Zend_Db::factory('Pdo_Mysql', $dbConnect); +``` + +## SELECT query - WHERE clause + +The following two classical SQL queries are equivalent — the first is a simple comma join, the second uses INNER JOIN — but the result is the same: + +```sql +SELECT a.id, a.name, b.order_id +FROM users AS a, orders AS b +WHERE a.id = b.user_id +AND a.id = {$userId} +``` + +```sql +SELECT `a`.`id`, `a`.`name`, `b`.`order_id` +FROM `users` AS `a` INNER JOIN `orders` AS `b` ON a.id = b.user_id +WHERE (a.id = '{$userId}') +``` + +Translated into Zend_Db style: + +```php +$select = $db->select() + ->from(array('a'=>'users'), + array('a.id', 'a.name')) + ->join(array('b'=>'orders'), 'a.id = b.user_id', array('b.order_id')) + ->where('a.id = ?', $userId) +``` + +If no column should be selected from the second table, the 3rd parameter of join() should be an empty string: + +```sql +SELECT a.id, a.name +FROM users AS a, orders AS b +WHERE a.id = b.user_id +AND a.id = {$userId} +``` + +```php +$select = $db->select() + ->from(array('a'=>'users'), + array('a.id', 'a.name')) + ->join(array('b'=>'orders'), 'a.id = b.user_id', '') + ->where('a.id = ?', $userId) +``` + +Note: if the 3rd parameter is not written at all, it will select all the fields from that table: + +```sql +SELECT a.id, a.name, b.* +FROM users AS a, orders AS b +WHERE a.id = b.user_id +AND a.id = {$user_id} +``` + +```php +$select = $db->select() + ->from(array('a'=>'users'), + array('a.id', 'a.name')) + ->join(array('b'=>'orders'), 'a.id = b.user_id') + ->where('a.id = ?', $userId) +``` + +## SELECT query - WHERE IN clause + +```sql +SELECT id +FROM users +WHERE aff_id IN ('1','2','3') +``` + +```php +$select = $db->select() + ->from('users', array('id')) + ->where('aff_id IN (?)', array(1,2,3)); +``` + +## Debugging a query + +If you are not sure the correct query is being generated, echo it before fetching: + +```php +echo $select->__toString();exit; +``` + +## FAQ + +**Q: What does Zend_Db provide?** +A: Zend_Db and its related classes provide a simple SQL database interface for Zend Framework. To connect to a MySQL database, the Pdo_Mysql adapter is used via Zend_Db::factory('Pdo_Mysql', $dbConnect). + +**Q: How do you write a SELECT with a JOIN and a WHERE clause in Zend_Db style?** +A: Use $db->select()->from(array('a'=>'users'), array('a.id','a.name'))->join(array('b'=>'orders'), 'a.id = b.user_id', array('b.order_id'))->where('a.id = ?', $userId), which is equivalent to a classical SQL query using INNER JOIN. + +**Q: How do you join a table without selecting any of its columns?** +A: Pass an empty string as the 3rd parameter of the join() method, e.g. ->join(array('b'=>'orders'), 'a.id = b.user_id', ''). + +**Q: What happens if the 3rd parameter of join() is omitted entirely?** +A: If the 3rd parameter is not written, it will select all the fields from that joined table (equivalent to SELECT ..., b.* in SQL). + +**Q: How do you write a WHERE IN clause with Zend_Db?** +A: Use ->where('aff_id IN (?)', array(1,2,3)) on the select object, equivalent to SQL's WHERE aff_id IN ('1','2','3'). + +**Q: How can you check that a Zend_Db select is generating the correct query?** +A: Before fetching it, echo the query to visualize it: echo $select->__toString();exit; + +## Resources + +- [Zend_Db](http://framework.zend.com/manual/en/zend.db.adapter.html) +- [What are returning the FETCH functions from Zend_Db](http://www.dotkernel.com/best-practice/sql-fetch-zend-db/) +- [Subqueries with Zend_Db](http://www.dotkernel.com/best-practice/subqueris-with-zend-db/) +- [INSERT, UPDATE, DELETE statements with Zend_Db](http://www.dotkernel.com/best-practice/iud-statements-with-zend-d/) diff --git a/public/readme/best-practice/subqueries-with-zend-db.md b/public/readme/best-practice/subqueries-with-zend-db.md new file mode 100644 index 00000000..29f2c5cd --- /dev/null +++ b/public/readme/best-practice/subqueries-with-zend-db.md @@ -0,0 +1,74 @@ +--- +title: "Subqueries with Zend_Db" +description: "How to build a query combining COUNT, LEFT JOIN, and GROUP BY across multiple tables using Zend_Db, including a subquery embedded as a column." +author: "Teo" +date_published: "2010-06-15" +canonical_url: "https://new.dotkernel.com/best-practice/subqueries-with-zend-db/" +category: "Best Practice" +language: "en" +--- + +# Subqueries with Zend_Db + +## TL;DR + +Continuing the Zend_Db series, this article shows a more complex query — combining COUNT(), LEFT JOIN, and GROUP BY across 3 tables, with a count taken from 2 different tables — and how to build it, including a nested subquery, using Zend_Db. + +## The SQL query + +```sql +SELECT a.id, + a.title, + (SELECT COUNT(c.track_id) + FROM track_files AS c + WHERE c.track_id = a.id + ) AS `count_files`, + COUNT(b.track_id) AS count_courses +FROM tracks AS a +LEFT JOIN track_courses AS b ON (a.id = b.track_id) +GROUP BY a.id +``` + +## Connecting to the database + +```php +$db = Zend_Db::factory('Pdo_Mysql', $dbConnect); +``` + +## Building the query in Zend_Db + +```php +$db->select() + ->from(array('a'=>'tracks'), + array('id', + 'title', + 'count_files' => new Zend_Db_Expr( + '('.$db->select() + ->from(array('c'=>'track_files'), + array(new Zend_Db_Expr('COUNT(c.track_id)'))) + ->where('c.track_id = a.id').')' ) + ) + ) + ->joinLeft(array('b'=>'track_courses'), + 'a.id = b.track_id', + array('count_courses' => 'COUNT(b.track_id)') + ) + ->group('a.id'); +``` + +The `count_files` column is built by wrapping a nested `$db->select()` call inside a `Zend_Db_Expr`, correlated back to the outer table via `c.track_id = a.id`. + +## FAQ + +**Q: What SQL techniques does this subquery example combine?** +A: The example combines COUNT(), LEFT JOIN, and GROUP BY, selecting from 3 tables and counting rows from 2 different tables. + +**Q: How do you embed a subquery as a selected column in a Zend_Db select?** +A: Wrap a nested $db->select() call inside a Zend_Db_Expr, building the subquery string with the outer table's correlated WHERE condition (e.g. c.track_id = a.id), as shown for the count_files column. + +**Q: How is the LEFT JOIN with a COUNT expressed in Zend_Db?** +A: Use ->joinLeft(array('b'=>'track_courses'), 'a.id = b.track_id', array('count_courses' => 'COUNT(b.track_id)')) followed by ->group('a.id'). + +## Resources + +- [Zend_Db series](http://www.dotkernel.com/dotkernel/sql-select-zend-db/) diff --git a/public/readme/best-practice/svn-export-in-a-virtual-host.md b/public/readme/best-practice/svn-export-in-a-virtual-host.md new file mode 100644 index 00000000..12f9dfdf --- /dev/null +++ b/public/readme/best-practice/svn-export-in-a-virtual-host.md @@ -0,0 +1,71 @@ +--- +title: "SVN Export in a virtual host" +description: "How to export the contents of an SVN repository into a virtual host directory using the svn export command." +author: "Adrian" +date_published: "2011-05-30" +canonical_url: "https://new.dotkernel.com/best-practice/svn-export-in-a-virtual-host/" +category: "Best Practice" +language: "en" +--- + +# SVN Export in a virtual host + +## TL;DR + +`svn export` lets you export the contents of a repository into a virtual host directory. The commands should be run in a terminal (e.g. via Putty on Windows) on the target host, ideally using the domain's own user rather than root. + +## Steps + +1. Make sure Subversion is installed on the host by running `svn --version`. If you don't get a "command not found" message, it's installed; otherwise, install it. +2. Go to the directory where you want to export the contents of the repository (e.g. `cd /var/www/vhosts/example.com/httpdocs` or `cd /home/sitename/public_html`). +3. Run the export command: + +```shell +svn export repositoryUrl repositoryUrl +``` + +Where: + +| Parameter | Meaning | +|---|---| +| `-r revisionNumber` | Optional. Exports a specific revision. By default, the latest revision is used. | +| `repositoryUrl` | The repository URL (e.g. `http://example.com/repos/project-name/trunk/`). Remember to add `/trunk/`, or change it appropriately for a branch or tag. | +| `targetDirectory` — `./` | The current directory. | +| `targetDirectory` — `./project-name` | Exports to the `project-name` subdirectory. | +| `targetDirectory` — `/var/www/vhosts/example.com/httpdocs` | Exports to an absolute path. | +| `--force` | Optional. By default SVN will not export into an existing directory; this overrides that. **Be careful, this option can overwrite files.** | + +4. For more information, run `svn help export`. + +## Examples + +```shell +svn export http://v1.dotkernel.net/svn/trunk ./ --force +svn export -r 423 http://v1.dotkernel.net/svn/trunk ./ --force +svn export http://v1.dotkernel.net/svn/trunk /var/www/vhosts/domain.com/httpdocs/dk +``` + +## Fixing permissions afterward + +If the repository was exported using a different user (e.g. root), change the permissions back as root: + +```shell +chown -R siteuser.psacln /var/www/vhosts/example.com/httpdocs +``` + +## FAQ + +**Q: How do you check if Subversion is installed on the host?** +A: Run svn --version. If you don't get a "command not found" message, Subversion is installed; otherwise, you need to install it. + +**Q: What is the basic command to export a repository?** +A: The command is svn export repositoryUrl targetDirectory, run from the host where you want to export the repository, ideally using the domain's user rather than root. + +**Q: What does the -r option do?** +A: -r revisionNumber is optional and exports a specific revision; by default, the latest revision is used. + +**Q: What does the --force option do, and what is the risk?** +A: By default SVN will not export into an existing directory; --force overrides this. Be careful, since this option can overwrite files. + +**Q: How do you fix file permissions if you exported the repository as a different user?** +A: As root, run chown -R siteuser.psacln /var/www/vhosts/example.com/httpdocs to change the permissions back. diff --git a/public/readme/best-practice/svn-keywords-setup-in-php-ide-zend-studio.md b/public/readme/best-practice/svn-keywords-setup-in-php-ide-zend-studio.md new file mode 100644 index 00000000..7982c672 --- /dev/null +++ b/public/readme/best-practice/svn-keywords-setup-in-php-ide-zend-studio.md @@ -0,0 +1,77 @@ +--- +title: "SVN keywords setup in PHP IDE ( Zend Studio)" +description: "How to set SVN ignore, bug tracker, and svn:keywords properties per project in the Zend Studio PHP IDE." +author: "admin" +date_published: "2013-02-21" +canonical_url: "https://new.dotkernel.com/best-practice/svn-keywords-setup-in-php-ide-zend-studio/" +category: "Best Practice" +language: "en" +--- + +# SVN keywords setup in PHP IDE ( Zend Studio) + +## TL;DR + +For better integration between SVN, the Zend Studio PHP IDE, and a bug tracker, a set of SVN properties must be set for each project. This article lists which properties to set and how. + +## Steps + +1. Right click on the **project**. +2. Go to **Team -> Set Propriety**. +3. Set `svn:ignore` so local settings aren't committed to the main repository: + +``` +Name: svn:ignore +Propriety: +*.project +*.prefs +.project +cache +.settings +.buildpath +*.ini +``` + +4. Set up basic bug tracker integration: + +``` +Name: bugtracq:label +Propriety: Tracker ID: +``` + +``` +Name: bugtraq:message +Propriety: +``` + +5. If using a public bug tracker (e.g. Mantis), also set: + +``` +Name: bugtraq:url +Propriety: http://www.dotkernel.net/view.php?id=%BUGID% +``` + +For the properties above, apply them **only** to the project folder, **not** recursively. + +## Final step (svn:keywords only) + +1. Check **Apply property recursively to:**. +2. Select **All resources**. +3. Check **Use filtration by the resource name** and add mask: `*.php`. + +## FAQ + +**Q: Why set these SVN properties on each project?** +A: They provide better integration of SVN, your PHP IDE (Zend Studio), and a bug tracker of choice, and must be set for each project you have. + +**Q: What does the svn:ignore property do here?** +A: It tells SVN to ignore local settings files such as *.project, *.prefs, .project, cache, .settings, .buildpath, and *.ini, since you don't want to commit your local settings to the main repository. + +**Q: How do you set up basic bug tracker integration?** +A: Set the bugtracq:label property to "Tracker ID:" and bugtraq:message; if you have a public bug tracker such as Mantis, also set bugtraq:url to a URL pattern like http://www.dotkernel.net/view.php?id=%BUGID%. + +**Q: Should these properties be applied recursively?** +A: No. For the properties above, apply them only to the project folder, not recursively. + +**Q: How is the svn:keywords property applied differently?** +A: Check "Apply property recursively to:", select "All resources", then check "Use filtration by the resource name" and add the mask *.php. diff --git a/public/readme/best-practice/using-like-wildcards-with-zend-db.md b/public/readme/best-practice/using-like-wildcards-with-zend-db.md new file mode 100644 index 00000000..ffffb81a --- /dev/null +++ b/public/readme/best-practice/using-like-wildcards-with-zend-db.md @@ -0,0 +1,154 @@ +--- +title: "Using LIKE wildcards with Zend_Db" +description: "How to use the SQL LIKE condition and its _ and % wildcards, including NOT LIKE, with Zend_Db's quoteInto and quoteIdentifier methods." +author: "Teo" +date_published: "2010-09-10" +canonical_url: "https://new.dotkernel.com/best-practice/using-like-wildcards-with-zend-db/" +category: "Best Practice" +language: "en" +--- + +# Using LIKE wildcards with Zend_Db + +## TL;DR + +The LIKE condition allows pattern matching in the WHERE clause of SELECT, INSERT, UPDATE, or DELETE statements. The `_` wildcard matches a single character, and `%` matches any string of any length (including zero). This article shows how to use LIKE and NOT LIKE with both wildcards in Zend_Db. + +## Connecting to the database + +```php +$db = Zend_Db::factory('Pdo_Mysql', $dbConnect); +``` + +## LIKE _ + +Return all ids that start with '1' and whose second digit is between 0 and 9 (10, 11, 12, ..., 18, 19): + +```sql +SELECT * FROM `table` WHERE (`id` LIKE '1_' ) +``` + +```php +$col = $this->db->quoteIdentifier('id'); +$where = $this->db->quoteInto("$col LIKE ? ", '1_'); +$select = $this->db->select() + ->from('table') + ->where($where); +$result = $this->db->fetchAll($select); +``` + +Return all instances whose name is 4 characters long, starting with 'Fr' and ending with 'd' (Frad, Fred, Frod, etc.): + +```sql +SELECT * FROM `table` WHERE (`name` LIKE 'Fr_d' ) +``` + +```php +$col = $this->db->quoteIdentifier('name'); +$where = $this->db->quoteInto("$col LIKE ? ", 'Fr_d'); +$select = $this->db->select() + ->from('table') + ->where($where); +$result = $this->db->fetchAll($select); +``` + +## LIKE % + +Returns all instances that have the 'gallery' string in the `source` field: + +```sql +SELECT * FROM `table` WHERE (`source` LIKE '%gallery%' ) +``` + +```php +$col = $this->db->quoteIdentifier('source'); +$where = $this->db->quoteInto("$col LIKE ? ", '%gallery%'); +$select = $this->db->select() + ->from('table') + ->where($where); +$result = $this->db->fetchAll($select); +``` + +Returns all instances that have the 'gallery' or 'folder' strings in the `source` field: + +```sql +SELECT * FROM `table` WHERE (`source` LIKE '%gallery%' OR `source` LIKE ('%folder%') ) +``` + +```php +$col = $this->db->quoteIdentifier('source'); +$where = $this->db->quoteInto("$col LIKE ? ", '%gallery%'); +$where .= $this->db->quoteInto("OR $col LIKE (?) ", '%folder%'); +$select = $this->db->select() + ->from('table') + ->where($where); +$result = $this->db->fetchAll($select); +``` + +## NOT LIKE _ + +Returns all 2-digit ids that don't start with `1` (20->99) or that don't have exactly 2 digits (1, 2, ..., 8, 9, 100, 101, ...): + +```sql +SELECT * FROM `table` WHERE (`id` NOT LIKE '1_' ) +``` + +```php +$col = $this->db->quoteIdentifier('id'); +$where = $this->db->quoteInto("$col NOT LIKE ? ", '1_'); +$select = $this->db->select() + ->from('table') + ->where($where); +$result = $this->db->fetchAll($select); +``` + +## NOT LIKE % + +Returns all instances that don't have 'gallery', 'folder', or 'file' in the `source` field: + +```sql +SELECT * FROM `table` WHERE (`source` NOT LIKE ('%gallery%') AND `source` NOT LIKE ('%folder%') AND `source` NOT LIKE ('%file%') ) +``` + +```php +$col = $this->db->quoteIdentifier('source'); +$where = $this->db->quoteInto("$col NOT LIKE (?) ", '%gallery%'); +$where .= $this->db->quoteInto("AND $col NOT LIKE (?) ", '%folder%'); +$where .= $this->db->quoteInto("AND $col NOT LIKE (?) ", '%file%'); +$select = $this->db->select() + ->from('table') + ->where($where); +$result = $this->db->fetchAll($select); +``` + +## Other example + +```sql +SELECT * FROM `table` WHERE `number` LIKE '_6%' +``` + +```php +$col = $this->db->quoteIdentifier('number'); +$where = $this->db->quoteInto("$col LIKE ? ", '_6%'); +$select = $this->db->select() + ->from('table') + ->where($where); +$result = $this->db->fetchAll($select); +``` + +## FAQ + +**Q: What do the LIKE wildcards _ and % mean?** +A: The _ wildcard matches a single character, while % matches any string of any length, including zero length. + +**Q: Which SQL statements can use the LIKE condition?** +A: LIKE allows pattern matching in the WHERE clause and can be used in any valid SQL statement: SELECT, INSERT, UPDATE, or DELETE. + +**Q: How do you build a LIKE query with Zend_Db?** +A: Quote the column with $this->db->quoteIdentifier(), build the condition with $this->db->quoteInto("$col LIKE ? ", $pattern), and pass the resulting $where string into ->where() on a select, then run it with $this->db->fetchAll($select). + +**Q: How do you combine multiple LIKE conditions with OR?** +A: Build the first condition with quoteInto, then append further ones with quoteInto("OR $col LIKE (?) ", $pattern), as in the example matching 'gallery' or 'folder' in the source field. + +**Q: How does NOT LIKE differ from LIKE?** +A: NOT LIKE negates the pattern match — for example, id NOT LIKE '1_' returns ids that don't start with 1 or don't have exactly 2 digits, and NOT LIKE conditions can be chained with AND to exclude several patterns at once. diff --git a/public/readme/dotkernel-api/api-client-migration-from-postman-to-bruno.md b/public/readme/dotkernel-api/api-client-migration-from-postman-to-bruno.md new file mode 100644 index 00000000..198c32ca --- /dev/null +++ b/public/readme/dotkernel-api/api-client-migration-from-postman-to-bruno.md @@ -0,0 +1,89 @@ +--- +title: "API Client Migration: From Postman to Bruno" +description: "Why the Dotkernel team is considering switching their API testing client from Postman to the offline-focused, Git-native Bruno." +author: "Florin Bidirean" +date_published: "2026-03-25" +canonical_url: "https://new.dotkernel.com/dotkernel-api/api-client-migration-from-postman-to-bruno/" +category: "Dotkernel API" +language: "en" +--- + +# API Client Migration: From Postman to Bruno + +## TL;DR + +The team has used Postman for years but is considering switching to Bruno, a lightweight, offline-first alternative, reflecting a broader PHP community trend toward local-first, Git-native developer tools. Bruno wins on offline access, version control via Git, performance, and (arguably) security, while Postman still offers a broader feature set for larger, budget-having teams. + +## Why We Switched to the Offline-Focused Bruno + +Every API developer needs a reliable client for testing and interacting with the API — ideally free, able to store and share endpoint collections easily with a team, fast, and secure. Postman has been the team's go-to tool for years, but they are now considering Bruno, part of a general trend in the PHP community toward local-first, Git-native developer tools. + +## Comparing Postman to Bruno + +| Aspect | Postman | Bruno | +|---|---|---| +| Architecture | Free plan limited to one user account | Fully-offline experience via shared `.bru` files; no restriction on number of developers | +| Version control | Handled in the cloud; requires being online (export/import via UI possible) | `.bru` files saved directly in the Git repository, versioned via Git like any other file | +| Feature scope | Complete platform for the API lifecycle (mocking, documentation, CI/CD integration) | Focused mainly on interacting with the API, writing simple tests, and building local collections | +| Performance | Needs to regularly sync with the cloud and store advanced features in RAM, which can introduce delays | Uses much less RAM and is generally faster | +| Security | Offers Single Sign-On (SSO) and Role-Based Access Control (RBAC) | Local files never leave the dev environment, arguably more secure | +| Collection sharing | Limited sharing for multi-member dev teams | Can share via Git, `.zip` file, or a single `.yaml` file; Git is the preferred option | + +### Comparison Conclusion + +Postman is currently a better fit for larger teams willing to allocate a budget for a more feature-rich platform. Bruno stores collections in Git, so everything is offline, which the team considers more secure while also being generally faster. + +## Alternative API Clients + +Bruno is only one of several alternatives to Postman: + +- Hoppscotch — runs in the browser or as a PWA +- Insomnia — clear UI and large plugin ecosystem +- HTTPie — focuses on terminal-based workflows +- Thunder Client — built into Visual Studio Code +- Apidog — covers the whole API lifecycle +- Yaak — minimal and fast desktop client + +Any of them can get the job done; the decision comes down to choosing a simple, reliable tool for the foreseeable future. + +## Bruno for Dotkernel + +Bruno currently seems like the best match for the team, offering similar functionality to Postman plus the ability to work completely offline and save endpoint collections to their GitHub accounts. The offline feature weighed most heavily in the decision. + +### Tool Migration + +Since most of the team has only worked with Postman, switching tools can affect efficiency at first, and tool migration can have an emotional impact as developers relearn a new tool's ins and outs. Given Bruno's straightforward approach and reasonable learning curve, the team expects this to be mitigated easily, and views the switch as an expansion of their expertise that avoids getting tied to one tool. + +### How Long Will Bruno Last? + +The team expects Bruno may eventually restrict developers with paid plans too, just like Postman did, but plans to cross that bridge when they get to it. For now, Bruno is becoming their de facto API client, and the whole team is being encouraged to adopt it as soon as possible. + +## FAQ + +**Q: Why is the team considering a switch from Postman to Bruno?** +A: They want a reliable API testing client that is free, stores and shares endpoint collections easily with the team, and is fast and secure. This reflects a broader trend in the PHP community toward local-first, Git-native developer tools. + +**Q: What is the main architectural difference between Postman and Bruno?** +A: Postman's free plan now only allows one user account, while Bruno offers a fully-offline experience based on shared `.bru` files, so there is no restriction on the number of developers using them. + +**Q: How does version control differ between the two tools?** +A: Postman stores collections and handles version control in the cloud, forcing developers to stay online (though collections can be exported/imported via its UI). Bruno's `.bru` files can be saved directly in a Git repository and are version-controlled through Git like any other project file. + +**Q: How does performance compare between Postman and Bruno?** +A: Bruno is the clear winner on performance: it uses much less RAM and is generally faster. Postman needs to regularly synchronize with the cloud and store its advanced features in RAM, which can introduce delays. + +**Q: Is Bruno more secure than Postman?** +A: Postman offers Single Sign-On (SSO) and Role-Based Access Control (RBAC), which the team doesn't find useful for its workflow. Bruno's local files never leave the dev environment, which the article argues makes it more secure, especially for avoiding sharing client files with online tools. + +**Q: What's the overall conclusion on Postman versus Bruno?** +A: Postman is currently a better fit for larger teams willing to allocate a budget for a more feature-rich platform. Bruno stores collections in Git so everything works offline, which the team considers more secure while also being generally faster, and it has become their de facto API client. + +## Resources + +- [Bruno homepage](https://www.usebruno.com/) +- [Hoppscotch](https://hoppscotch.io/) +- [Insomnia](https://insomnia.rest/) +- [HTTPie](https://httpie.io/) +- [Thunder Client](https://www.thunderclient.com/) +- [Apidog](https://apidog.com/) +- [Yaak](https://yaak.app/) diff --git a/public/readme/dotkernel-api/api-endpoint-to-collect-client-errors.md b/public/readme/dotkernel-api/api-endpoint-to-collect-client-errors.md new file mode 100644 index 00000000..2ec5cc0e --- /dev/null +++ b/public/readme/dotkernel-api/api-endpoint-to-collect-client-errors.md @@ -0,0 +1,46 @@ +--- +title: "API Endpoint to Collect Client Errors" +description: "How Dotkernel API's error-report endpoint lets frontend clients submit and log errors that occur on the user's machine." +author: "kakapiciu" +date_published: "2022-11-07" +canonical_url: "https://new.dotkernel.com/dotkernel-api/api-endpoint-to-collect-client-errors/" +category: "Dotkernel API" +language: "en" +--- + +# API Endpoint to Collect Client Errors + +When a Frontend (e.g. Angular) sits on top of a Dotkernel API, errors can happen - the API's response may have changed overnight, or a variable may simply be `undefined`. Since the Frontend runs on the user's own client, there's little that can be done about it directly, so an endpoint was created to let clients submit the error message when something goes wrong. + +## Usage + +Send a POST request to your Dotkernel API on the route: + +``` +https://api.dotkernel.net/error-report +``` + +With a body: + +```shell +{ + "message": "My awesome error!!!" +} +``` + +Note: the error message is stored by default in `/log/error-report-endpoint-log.log`, a separate log for Client, and the message is saved together with a timestamp. + +## FAQ + +**Q: Why was this endpoint created?** +A: When a Frontend client (e.g. Angular) running on the user's machine hits an error against the Dotkernel API - whether from an overnight API response change or a simple undefined variable - there is little that can be done from the client side, so this endpoint lets clients "write down" the error instead. + +**Q: How do I submit an error from the client?** +A: Send a simple POST request to your Dotkernel API's `https://api.dotkernel.net/error-report` route, with a body such as `{ "message": "My awesome error!!!" }`. + +**Q: Where is the submitted error message stored?** +A: By default, it is stored in a separate log file for Client, `/log/error-report-endpoint-log.log`, with the message saved alongside a timestamp. + +## Resources + +- [Dotkernel API on GitHub](https://github.com/dotkernel/api) diff --git a/public/readme/dotkernel-api/content-negotiation-in-dotkernel-rest-api.md b/public/readme/dotkernel-api/content-negotiation-in-dotkernel-rest-api.md new file mode 100644 index 00000000..64420205 --- /dev/null +++ b/public/readme/dotkernel-api/content-negotiation-in-dotkernel-rest-api.md @@ -0,0 +1,98 @@ +--- +title: "Content Negotiation in Dotkernel REST API" +description: "How content negotiation works in RESTful APIs, who decides the data format, and how Dotkernel API implements it." +author: "Florin Bidirean" +date_published: "2024-11-27" +canonical_url: "https://new.dotkernel.com/dotkernel-api/content-negotiation-in-dotkernel-rest-api/" +category: "Dotkernel API" +language: "en" +--- + +# Content Negotiation in Dotkernel REST API + +## TL;DR + +Content negotiation lets clients and servers agree on the format and language of exchanged data. It can be handled server-side or client-side (the latter being more versatile), communicated through HTTP headers or URL patterns, and Dotkernel API implements it out of the box using the `Content-Type` and `Accept` headers. + +## What is the Purpose of Content Negotiation? + +RESTful resources can support multiple representations, and efficient client-server communication depends on both sides agreeing on the exchanged data format - this agreement is content negotiation. It ensures: + +- **Support for diverse clients**, e.g. `Accept: application/json` or `Accept: application/xml`. +- **Data format flexibility**, e.g. using `Accept: application/msgpack` (a binary serialization) instead of JSON for a smaller, easier-to-transfer response. +- **Language localization**, e.g. `Accept-Language: en-US`, to respond with content translated into the client's preferred language. + +## Who Decides the Data Format? + +Either the client or the server can decide: + +- **Server-side negotiation**: the server decides the format based on various factors. This can introduce erroneous assumptions and a more complex server-side implementation, and forces the client to adhere to the server's rules. +- **Client-side negotiation**: the client tells the server what format it prefers. This approach is more versatile and makes more sense. + +There are two ways to communicate the preferred data format: HTTP request headers, or resource URI patterns. + +### HTTP Request Headers + +The `Content-Type` and `Accept` headers determine the data format sent in the request and response. Examples of types include `text/plain`, `text/html`, `application/json`, `application/zip`, `image/gif`, and `image/jpeg`. + +```shell +Content-Type: application/json, text/plain +Accept: application/json +``` + +If the `Accept` header is not present, the server decides the response format. + +### Content Negotiation Using URL Patterns + +A preferred format can also be communicated via the URL extension: + +```shell +https://www.example-api.com/record/47.xml +https://www.example-api.com/record/47.json +``` + +or via an extra query parameter: + +```shell +https://www.example-api.com/record/47?format=xml +https://www.example-api.com/record/47?format=json +``` + +## Defining Preferences via a Quality Factor + +The `Accept` header can hold multiple values with an added quality value (`q`, between 0 and 1) that defines preference or priority: + +```shell +Accept: application/json,application/xml;q=0.9,*/*;q=0.8 +``` + +In this example, the client accepts both JSON and XML, with JSON preferred. If the server can only satisfy XML, it responds with that; if it can satisfy neither, it responds with whatever it can. + +## How Does Dotkernel API Handle Content Negotiation? + +Out of the box, Dotkernel API uses the `Content-Type` and `Accept` HTTP request headers to handle client-side content negotiation, supporting both `application/json` and `application/hal+json`. These can be changed as development progresses, and per-route content negotiation is also supported. Configuration lives in its own configuration file, validation is automatic, and several explicit errors are handled based on the supported format. + +## FAQ + +**Q: What is content negotiation?** +A: It's the act of a client and server agreeing on the format and language of the data they exchange, which is important for RESTful APIs since resources can support multiple representations. + +**Q: What does content negotiation ensure?** +A: It ensures support for diverse clients (e.g. `Accept: application/json` or `Accept: application/xml`), data format flexibility for smaller responses (e.g. `Accept: application/msgpack`, a binary serialization), and language localization via headers like `Accept-Language: en-US`. + +**Q: Who decides the data format, the client or the server?** +A: Either side technically can. In server-side negotiation, the server decides based on various factors, which can introduce erroneous assumptions and more complex implementation, forcing the client to adhere to server rules. In client-side negotiation, the client tells the server what format it prefers, which is more versatile and makes more sense. + +**Q: How can the preferred data format be communicated?** +A: Via HTTP request headers (`Content-Type` and `Accept`) or via resource URI patterns, such as a file extension in the URL (e.g. `/record/47.json`) or an extra query parameter (e.g. `/record/47?format=json`). If the `Accept` header is not present, the server decides the response format. + +**Q: How does the quality factor (q) work in the Accept header?** +A: The `Accept` header can list multiple accepted formats with a `q` value between 0 and 1 to express preference, e.g. `Accept: application/json,application/xml;q=0.9,*/*;q=0.8`. The server responds with the most preferred format it can satisfy, falling back further down the list if needed. + +**Q: How does Dotkernel API handle content negotiation?** +A: Out of the box, Dotkernel API uses the `Content-Type` and `Accept` HTTP request headers to handle client-side content negotiation, supporting both `application/json` and `application/hal+json`. These can be changed as needed, and per-route content negotiation is also supported. + +## Resources + +- [Content Negotiation in Dotkernel API](https://docs.dotkernel.org/api-documentation/v5/core-features/content-validation/) +- [Content types on iana.org](https://www.iana.org/assignments/media-types/media-types.xhtml) diff --git a/public/readme/dotkernel-api/dotkernel-api-1-0-0-released.md b/public/readme/dotkernel-api/dotkernel-api-1-0-0-released.md new file mode 100644 index 00000000..17654adc --- /dev/null +++ b/public/readme/dotkernel-api/dotkernel-api-1-0-0-released.md @@ -0,0 +1,72 @@ +--- +title: "DotKernel API 1.0.0 Released" +description: "Announcement of the DotKernel API 1.0.0 release, a Zend Expressive 3 application for quickly building APIs, including the libraries it uses and the features it offers out of the box." +author: "Alex Karajos" +date_published: "2019-12-15" +canonical_url: "https://new.dotkernel.com/dotkernel-api/dotkernel-api-1-0-0-released/" +category: "Dotkernel API" +language: "en" +--- + +# DotKernel API 1.0.0 Released + +> Note: Dotkernel API has come a long way since this post was created; a newer version is documented separately. + +DotKernel API 1.0.0 was just released. + +## What is DotKernel API? + +It is a Zend Expressive 3 application aiming to help developers quickly and efficiently develop an API. + +## How Does It Work? + +Under the hood, it uses the following libraries: + +- `ezimuel/zend-expressive-api` - skeleton application on which this API is based +- `dotkernel/dot-annotated-services` (^1.1) - for handling dependency injection in your services +- `dotkernel/dot-console` (^0.1.1) - for developing console applications +- `dotkernel/dot-errorhandler` (^1.0) - which provides customizable error logging +- `dotkernel/dot-mail` (^1.0) - for sending emails via SMTP +- `zendframework/zend-expressive-authentication-oauth2` (^1.0) - for OAuth2 authentication +- `zendframework/zend-expressive-authorization-rbac` (^1.0) - for role-based permissions +- `zendframework/zend-expressive-twigrenderer` (^2.4) - for composing email bodies +- `dasprid/container-interop-doctrine` (^1.1) - database abstraction layer +- `tuupola/cors-middleware` (^0.9.4) - for automatically sending CORS headers with each request +- `swagger-api/swagger-ui` (^3.22) - for creating OpenAPI 3 documentation + +## What Does It Offer? + +Out-of-the-box, DotKernel API provides the following features: + +- Secure authentication via OAuth2 +- Two user roles: admin and member +- Admin users are allowed to manage any user account +- Members are allowed to manage only their own accounts +- OpenAPI 3 documentation - also an interactive interface that developers can use to integrate your API + +## FAQ + +**Q: What is DotKernel API?** +A: It is a Zend Expressive 3 application aiming to help developers quickly and efficiently develop an API. + +**Q: What key libraries does DotKernel API 1.0.0 use?** +A: Among others, it's built on the `ezimuel/zend-expressive-api` skeleton, and uses `dotkernel/dot-annotated-services` for dependency injection, `dotkernel/dot-console` for console applications, `dotkernel/dot-errorhandler` for error logging, `dotkernel/dot-mail` for SMTP email, `zend-expressive-authentication-oauth2` for OAuth2 authentication, `zend-expressive-authorization-rbac` for role-based permissions, and `swagger-api/swagger-ui` for OpenAPI 3 documentation. + +**Q: What features does DotKernel API 1.0.0 offer out of the box?** +A: Secure authentication via OAuth2, two user roles (admin and member), where admins can manage any user account and members can manage only their own, plus OpenAPI 3 documentation with an interactive interface developers can use to integrate the API. + +## Resources + +- [DotKernel API 1.0.0 release on GitHub](https://github.com/dotkernel/api/releases/tag/v1.0.0) +- [ezimuel/zend-expressive-api](https://github.com/ezimuel/zend-expressive-api) +- [dotkernel/dot-annotated-services](https://github.com/dotkernel/dot-annotated-services) +- [dotkernel/dot-console](https://github.com/dotkernel/dot-console) +- [dotkernel/dot-errorhandler](https://github.com/dotkernel/dot-errorhandler) +- [dotkernel/dot-mail](https://github.com/dotkernel/dot-mail) +- [zendframework/zend-expressive-authentication-oauth2](https://github.com/zendframework/zend-expressive-authentication-oauth2) +- [zendframework/zend-expressive-authorization-rbac](https://github.com/zendframework/zend-expressive-authorization-rbac) +- [zendframework/zend-expressive-twigrenderer](https://github.com/zendframework/zend-expressive-twigrenderer) +- [dasprid/container-interop-doctrine](https://github.com/DASPRiD/container-interop-doctrine) +- [tuupola/cors-middleware](https://github.com/tuupola/cors-middleware) +- [swagger-api/swagger-ui](https://github.com/swagger-api/swagger-ui) +- [Newest version of Dotkernel API](https://www.dotkernel.com/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5/) diff --git a/public/readme/dotkernel-api/dotkernel-api-client-side-authorization.md b/public/readme/dotkernel-api/dotkernel-api-client-side-authorization.md new file mode 100644 index 00000000..be6a105e --- /dev/null +++ b/public/readme/dotkernel-api/dotkernel-api-client-side-authorization.md @@ -0,0 +1,58 @@ +--- +title: "DotKernel API Client Side Authorization" +description: "How a client application authorizes against a backend built with DotKernel API, from the authorization request to using the access token." +author: "admin" +date_published: "2019-08-05" +canonical_url: "https://new.dotkernel.com/dotkernel-api/dotkernel-api-client-side-authorization/" +category: "Dotkernel API" +language: "en" +--- + +# DotKernel API Client Side Authorization + +This article covers the basic authorization of a Client application which uses a backend built using DotKernel API. + +## Authorization Request + +Client application users send a POST request to the backend containing the following JSON object: + +```shell +{ +"grant_type": "password", +"client_id": "{API_CLIENT}", +"client_secret": "{API_CLIENT_SECRET}", +"scope": "{SCOPE}", +"username": "{USERNAME/EMAIL}", +"password": "{PASSWORD}" +} +``` + +## Authorization Response + +If the credentials are correct, the API will return a JSON object containing the authentication data: + +```shell +{ +"token_type": "Bearer", +"expires_in": 86400, +"access_token": "...", +"refresh_token": "..." +} +``` + +When sending API requests to an endpoint which requires authorization, an `Authorization` header must be present containing `"Bearer {access_token}"`, where `{access_token}` represents the content of the key with the same name found in the authorization response. + +## FAQ + +**Q: What does a client send to request authorization?** +A: The client application sends a POST request to the backend with a JSON object containing `grant_type` (set to "password"), `client_id`, `client_secret`, `scope`, `username`/email, and `password`. + +**Q: What does the API return when authorization succeeds?** +A: If the credentials are correct, the API returns a JSON object containing `token_type` ("Bearer"), `expires_in` (86400 seconds), an `access_token`, and a `refresh_token`. + +**Q: How do I use the access token in subsequent requests?** +A: When sending API requests to an endpoint that requires authorization, include an Authorization header containing `"Bearer {access_token}"`, where `{access_token}` is the value returned in the authorization response. + +## Resources + +- [DotKernel API on GitHub](https://github.com/dotkernel/api) diff --git a/public/readme/dotkernel/adding-a-cors-implementation-to-zend-expressive.md b/public/readme/dotkernel/adding-a-cors-implementation-to-zend-expressive.md new file mode 100644 index 00000000..642b6cd5 --- /dev/null +++ b/public/readme/dotkernel/adding-a-cors-implementation-to-zend-expressive.md @@ -0,0 +1,139 @@ +--- +title: "Adding a CORS implementation to Zend Expressive" +description: "A guide on how to add a CORS implementation to an existing DotKernel3 project using Tuupola's Cors Middleware package." +author: "Gabi DJ" +date_published: "2019-04-08" +canonical_url: "https://new.dotkernel.com/dotkernel/adding-a-cors-implementation-to-zend-expressive/" +category: "Dotkernel" +language: "en" +--- + +# Adding a CORS implementation to Zend Expressive + +## TL;DR +When a client-side request is blocked with a "No 'Access-Control-Allow-Origin' header" error, it's because the server isn't sending the header that allows a browser to access its data (most common when fetching JSON to process with JavaScript). This guide adds CORS support to a Zend Expressive / DotKernel3 project using Tuupola's Cors Middleware package. + +## The issue + +If you're facing the error: + +> "Access to XMLHttpRequest at 'url' has been blocked by cors policy. No 'Access-Control-Allow-Origin header is present on the requested resource." + +it means the server didn't send the header that lets you access its data through a local client (e.g. a browser). This issue is most common when trying to get data (usually JSON) that you want to process using JavaScript. + +## The solution + +A simple implementation uses [Tuupola's Cors Middleware](https://packagist.org/packages/tuupola/cors-middleware) package. (This article was inspired by [akrabat.com/implementing-tuupola-cors-in-expressive](https://akrabat.com/implementing-tuupola-cors-in-expressive/).) + +### 1. Add the package to your project + +```shell +composer require tuupola/cors-middleware +``` + +At the time of writing, the current package version is 0.9.4. + +### 2. Create the CORS config file + +Create a `cors.global.php` file in the `config/autoload` directory: + +```php +return [ + 'cors' => [ + "origin" => [], + "methods" => [], + "headers.allow" => [], + "headers.expose" => [], + "credentials" => false, + "cache" => 0, + ], + 'dependencies' => [], +]; +``` + +### 3. Create a factory for the middleware + +The factory extracts the config from the `cors` key (or initializes an empty array) and instantiates the Tuupola CORS middleware: + +```php +get('config')['cors'] ?? []; + return new CorsMiddleware($corsConfig); + } +} +``` + +### 4. Register the CORS middleware + +Back in `cors.global.php`, register the middleware so the factory above is used to create it: + +```php + [ + "origin" => [], + "methods" => [], + "headers.allow" => [], + "headers.expose" => [], + "credentials" => false, + "cache" => 0, + ], + 'dependencies' => [ + 'factories' => [ + CorsMiddleware::class => CorsMiddlewareFactory::class, + ] + ] +]; +``` + +### 5. Add the CorsMiddleware to the pipeline + +In `config/pipelines.php`: + +```php +// don't forget the use statement +use Tuupola\Middleware\CorsMiddleware; + +return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void { + // ... + $app->pipe(CorsMiddleware::class); + // ... +}; +``` + +Add the CORS middleware **after** the Error handler and **before** the middleware providing the data you want to access, to make sure everything runs smoothly. This should get your project working with CORS. + +## FAQ + +**Q: What causes the "No 'Access-Control-Allow-Origin' header" error?** +A: It means the server didn't send the header that lets a local client, such as a browser, access its data. This is most common when trying to fetch data (usually JSON) that you want to process using JavaScript. + +**Q: What package does the article use to add CORS support?** +A: Tuupola's Cors Middleware package, installed by running `composer require tuupola/cors-middleware` in the project. + +**Q: Where does the CORS configuration live?** +A: In a `cors.global.php` file created in the config/autoload directory, containing a "cors" key with settings like origin, methods, headers.allow, headers.expose, credentials, and cache. + +**Q: How is the CorsMiddleware wired into the container?** +A: A CorsMiddlewareFactory extracts the "cors" config array (or an empty array if it's not provided) and instantiates Tuupola's CorsMiddleware with it. That factory is registered under the "dependencies" > "factories" section of cors.global.php. + +**Q: Where should the CORS middleware be added in the pipeline?** +A: In config/pipelines.php via `$app->pipe(CorsMiddleware::class)`, placed after the Error handler and before the middleware that provides the data you want to access. + +## Resources + +- [Tuupola's Cors Middleware package](https://packagist.org/packages/tuupola/cors-middleware) +- [Implementing Tuupola CORS in Expressive (inspiration article)](https://akrabat.com/implementing-tuupola-cors-in-expressive/) diff --git a/public/readme/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.md b/public/readme/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.md new file mode 100644 index 00000000..a8a1ffef --- /dev/null +++ b/public/readme/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.md @@ -0,0 +1,53 @@ +--- +title: "Adding a second caching layer to WURFL in Dotkernel using APC" +description: "How adding a small, custom APC-based caching layer on top of WURFL's own cache cut response time by an order of magnitude." +author: "Adrian" +date_published: "2011-10-14" +canonical_url: "https://new.dotkernel.com/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc/" +category: "Dotkernel" +language: "en" +--- + +# Adding a second caching layer to WURFL in Dotkernel using APC + +## TL;DR +On a high-traffic project using WURFL, profiling showed WURFL's default filesystem cache was costing up to a few hundred milliseconds per request. Adding a small, custom second cache layer on top of WURFL, built on APC, cut response time by an order of magnitude, down to 20-30ms. + +## The problem + +On one recent project that used WURFL, response time was an important factor. Profiling revealed that the greatest chunk of response time (up to a few hundred milliseconds) was taken up by WURFL. The default filesystem cache turned out to be too slow for a relatively high-traffic application. + +## How WURFL's caching works + +1. The device data is stored at first in a large, zipped XML file, with one entry for each device. +2. When first called, WURFL unzips the file and reads each device entry. +3. It then serializes the data and writes it to the cache, using an MD5 signature for the file name (or key name if the cache is not on the filesystem). +4. When a user agent is looked up, its MD5 signature is computed and then searched in the cache. +5. Because the data is stored as a tree, with each device inheriting the properties of the nodes above it, **each look-up requires a number of files to be read and their capabilities merged** to get all the capabilities of the requested device. + +WURFL also has cache providers for APC and memcache, which were tried, but the results weren't impressive. + +## The solution + +The team realized their approach was wrong for their use case — the WURFL entry for a device has lots of fields that weren't actually used. + +The solution was adding a **second cache layer** on top of WURFL's own cache, which only cached the fields that were actually needed. This second layer used **APC**, storing arrays of data in **User Cache Entries**. + +This small change (under 10 lines of code) decreased response time by an **order of magnitude**, down to 20-30ms. + +## FAQ + +**Q: Why was WURFL's default caching too slow for this project?** +A: Profiling revealed that WURFL's default filesystem cache was taking up to a few hundred milliseconds of response time, which was too slow for a relatively high-traffic application. + +**Q: How does WURFL's caching work by default?** +A: Device data is stored in a large zipped XML file. On first use, WURFL unzips the file, serializes each device's data, and writes it to cache using an MD5 signature of the user agent as the key. Because devices are stored as a tree inheriting properties from parent nodes, each lookup requires reading and merging several files. + +**Q: Did WURFL's built-in APC or memcache cache providers solve the problem?** +A: No. The team tried WURFL's existing cache providers for APC and memcache, but the results weren't impressive. + +**Q: What was the actual solution?** +A: Adding a second cache layer on top of WURFL's own cache, using APC and storing arrays of only the specific fields they actually needed in User Cache Entries. The change was under 10 lines of code. + +**Q: What performance improvement did this bring?** +A: Response time decreased by an order of magnitude, down to about 20-30ms. diff --git a/public/readme/dotkernel/adding-composer-support-in-your-dotkernel-project.md b/public/readme/dotkernel/adding-composer-support-in-your-dotkernel-project.md new file mode 100644 index 00000000..650f24b9 --- /dev/null +++ b/public/readme/dotkernel/adding-composer-support-in-your-dotkernel-project.md @@ -0,0 +1,107 @@ +--- +title: "Adding Composer support in your DotKernel project" +description: "The steps needed to add Composer support to a DotKernel 1.x project, or 'composify' it." +author: "Gabi DJ" +date_published: "2016-04-04" +canonical_url: "https://new.dotkernel.com/dotkernel/adding-composer-support-in-your-dotkernel-project/" +category: "Dotkernel" +language: "en" +--- + +# Adding Composer support in your DotKernel project + +## TL;DR +Composer is an application-level package manager that auto-loads dependencies (and custom classes) on demand. This article covers the steps needed to add a composer.json file to a DotKernel project, run `composer update`, and safely require the generated autoloader so the project works whether or not Composer is present. + +## First things first + +The DotKernel project must have a **composer.json** file so that Composer can work. It should look like this: + +```json +{ + "require" : { + "zendframework/zendframework1" : "1.12.*", + "mobiledetect/mobiledetectlib" : "2.8.*" + }, + "require-dev" : { + "php" : ">=5.4.0" + } +} +``` + +Note: `zendframework/zendframework1` may not be necessary if you already have ZendServer running or the Zend Framework folder within `/usr/share/`. + +This file makes sure: + +- Zend Framework is present and at version > 1.12.* +- MobileDetect is present and at version > 2.8 +- The PHP executable is at least at version > 5.4.0 (only for development, because it is not present in the main `require`) + +The dependencies provided in the `require` section are also loaded for development purposes if not provided in `require-dev`. + +In order to have these components installed, run the following command in your DotKernel root path: + +```shell +composer update +``` + +If the `vendor` folder is present, Composer will check for updates and update the packages as needed. If the `vendor` folder does not exist, Composer will create it, containing all the requested packages, along with an autoload file used to load the dependencies/packages. + +## Adding Composer Support to DotKernel + +The autoload file created by Composer is used to load the packages: + +```php +$composerAutoLoaderPath = realpath(APPLICATION_PATH.'/vendor/autoload.php'); +require_once($composerAutoLoaderPath); +``` + +But what if the file does not exist, or Composer is not present? First make sure the Composer autoload path exists, and only load the dependencies if the autoload file was found: + +```php +$composerAutoLoaderPath = realpath('./vendor/autoload.php'); + +$composerEnabled = file_exists($composerAutoLoaderPath); + +if ($composerEnabled == true) { + require_once($composerAutoLoaderPath); +} else { + // handle the error gracefully + // or load fallbacks - if exist +} +``` + +The variable `$composerEnabled` will be true only if the Composer path exists, so the application behavior can be controlled if Composer is not present. + +Later on, the packages can be used like this: + +```php +use VendorName\PackageName\ClassName as MyDependency; + +$myDependency = new MyDependency($neededArguments); +$myDependency->doSomething(); +``` + +This article works for any **DotKernel 1.x** version if your server is running **PHP >5.4.0**. + +## FAQ + +**Q: What does Composer do?** +A: Composer is an application-level package manager. It auto-loads dependencies on demand and can also auto-load custom classes. + +**Q: What must a DotKernel project have before Composer can be used?** +A: A composer.json file, for example requiring zendframework/zendframework1 at 1.12.* and mobiledetect/mobiledetectlib at 2.8.*, plus PHP >=5.4.0 listed under require-dev. + +**Q: What happens when you run composer update?** +A: If the vendor folder already exists, Composer checks for and applies updates to the packages. If it doesn't exist, Composer creates the vendor folder containing all requested packages, along with an autoload file. + +**Q: How do you safely load the Composer autoloader in case Composer isn't present?** +A: Check whether vendor/autoload.php exists using file_exists() before calling require_once() on it, and handle the case gracefully (for example by loading fallbacks) if the path is missing. + +**Q: Which DotKernel versions does this apply to?** +A: The article states it works for any DotKernel 1.x version, as long as the server is running PHP greater than 5.4.0. + +## Resources + +- [Composer install / PHP dependency manager tutorial](https://www.codementor.io/php/tutorial/composer-install-php-dependency-manager) +- [Using DotKernel with Composer dependencies](http://www.dotkernel.com/dotkernel/using-dotkernel-…ser-dependencies/) diff --git a/public/readme/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.md b/public/readme/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.md new file mode 100644 index 00000000..bf75bbf8 --- /dev/null +++ b/public/readme/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.md @@ -0,0 +1,55 @@ +--- +title: "Adding Windows 10 OS and Browser detection in DotKernel projects" +description: "A guide to installing the patch that adds Windows 8, 8.1 and 10 OS icons and the Microsoft Edge browser icon in DotKernel." +author: "Gabi DJ" +date_published: "2015-09-08" +canonical_url: "https://new.dotkernel.com/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects/" +category: "Dotkernel" +language: "en" +--- + +# Adding Windows 10 OS and Browser detection in DotKernel projects + +## TL;DR +DotKernel added Windows 8, 8.1 and 10 OS icons and a Microsoft Edge browser icon, shown in the User and Admin login icons. This article is the upgrade guide for applying that icon patch. + +## Upgrade steps + +1. Make sure your project is running version **1.5.0** or **newer**. +2. Download the [patch](http://www.dotkernel.com/download/?did=46). +3. Extract the archive into a folder, e.g. `icons_patch`. +4. Create a backup of your project before continuing (recommended). +5. Copy all the files in the `icons_patch` folder into your DotKernel project. +6. You will be prompted to replace 2 files — replace them and agree to merge the folders' content (other files will be added, not replaced). +7. Clear the cache for changes to take effect, since the OS and browser XMLs are cached (see "DotKernel Reserved Variable Names for Caching", the "Browser & OS" section). +8. You can now delete the `icons_patch` folder, or keep it to patch another project. + +## Affected files + +``` +M /configs/useragent/browser.xml +M /configs/useragent/os.xml +A /images/browsers/edge.png +A /images/os/windows_metro.png +``` + +`M` stands for **modify**, `A` stands for **add**. + +## FAQ + +**Q: What DotKernel version is required before applying this patch?** +A: Your project must be running version 1.5.0 or newer. + +**Q: Which files does the patch modify or add?** +A: It modifies configs/useragent/browser.xml and configs/useragent/os.xml, and adds images/browsers/edge.png and images/os/windows_metro.png. + +**Q: Why do you need to clear the cache after applying the patch?** +A: Because the OS and browser XML files are cached, so the new icons won't show up until the cache is cleared. + +**Q: Will applying the patch overwrite existing files?** +A: You'll be prompted to replace 2 files (browser.xml and os.xml) and should agree, and also agree to merge the folders' contents since the other files listed are added rather than replaced. + +## Resources + +- [Icon patch download](http://www.dotkernel.com/download/?did=46) +- [DotKernel Reserved Variable Names for Caching](http://www.dotkernel.com/dotkernel/dotkernel-reserved-variable-names-for-caching) diff --git a/public/readme/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.md b/public/readme/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.md new file mode 100644 index 00000000..58fce21e --- /dev/null +++ b/public/readme/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.md @@ -0,0 +1,138 @@ +--- +title: "Autologin using Cookie / Remember Me in Dotkernel" +description: "A step-by-step guide to implementing a Remember Me / autologin feature in Dotkernel Frontend." +author: "SergiuB" +date_published: "2022-07-18" +canonical_url: "https://new.dotkernel.com/dotkernel/autologin-using-cookie-remember-me-in-dotkernel/" +category: "Dotkernel" +language: "en" +--- + +# Autologin using Cookie / Remember Me in Dotkernel + +## TL;DR +This feature automatically logs in a user who checks the "remember me" box at login. It has been implemented in [Dotkernel Frontend](https://github.com/dotkernel/frontend) starting from Release 3.3.0, and requires changes across the login form, a new entity/migration, a new middleware, config, and the user service/repository/controller. + +## Add remember me button to user interface + +Navigate to `src/User/templates/user/login.html.twig` and, under the password element, add: + +```twig +
                          + {% set rememberMe = form.get('rememberMe') %} + {{ formElement(rememberMe) }} +

                          Remember me

                          +
                          +``` + +Then navigate to `src/User/src/Form/LoginForm.php` and add the following element to your form: + +```php +$this->add([ + 'name' => 'rememberMe', + 'type' => 'checkbox', + 'attributes' => [ + 'class' => 'tooltips', + 'data-toggle' => 'tooltip', + 'title' => 'Remember me', + ], +]); +``` + +Then navigate to `src/User/src/InputFilter/LoginInputFilter.php` and add a filter for the new element: + +```php +$this->add([ + 'name' => 'rememberMe', + 'filters' => [ + ['name' => 'StringTrim'] + ], + 'validators' => [ + [ + 'name' => 'NotEmpty', + 'break_chain_on_failure' => true, + ] + ] + ]); +``` + +To style the button, navigate to `src/App/assets/scss/components/_profile.scss` and add: + +```scss +.remember-me-checkbox { + input { + display: block; + float: left; + margin: 4px 6px 10px 0; + width: auto; + height: auto; + } +} +``` + +After making the changes, compile the CSS so the button styling takes effect: + +```shell +npm run prod +``` + +## Add functionality to remember me button + +1. Navigate to `src/User/src/Entity`, create a new entity named `UserRememberMe.php`, modeled on [UserRememberMe](https://github.com/dotkernel/frontend/blob/3.0/src/User/src/Entity/UserRememberMe.php). +2. Create a migration file for the new table: + ```shell + vendor/bin/phinx create --configuration=config/migrations.php RememberUserSchema + ``` +3. Modify the generated migration file as in [user_remember_schema](https://github.com/dotkernel/frontend/blob/3.0/data/database/migrations/20220621062142_remember_user_schema.php), then run it against the database: + ```shell + vendor/bin/phinx migrate --configuration=config/migrations.php + ``` + The table generated by the migration is used to store data from the cookie, which helps log the user in automatically. +4. Create a new middleware at `src/App/src/Middleware/RememberMeMiddleware.php`, modeled on [RememberMeMiddleware](https://github.com/dotkernel/frontend/blob/3.0/src/App/src/Middleware/RememberMeMiddleware.php). +5. Register the middleware in `config/pipeline.php` as shown in [pipeline](https://github.com/dotkernel/frontend/blob/3.0/config/pipeline.php). +6. To generate the cookie, add a new key to `config/autoload/local.php`: + ```php + 'rememberMe' => [ + 'cookie' => [ + 'name' => 'rememberMe', + 'lifetime' => 3600 * 24 * 30, + 'samesite' => 'Lax', + 'secure' => false, + 'httponly' => true + ] + ], + ``` +7. Edit `src/User/src/Service/UserService.php`: add two new properties, `$defaultSessionManager` (to get config) and `$repository` (to get repository), then add the methods `getRepository()`, `addRememberMeToken()`, `deleteRememberMeCookie()` as in [UserService](https://github.com/dotkernel/frontend/blob/3.0/src/User/src/Service/UserService.php) (and add the new methods to the interface if needed). +8. Edit `src/User/src/Repository/UserRepository.php` and add the methods `saveRememberUser()`, `getRememberUser()`, `findRememberMeUser()`, `deleteExpiredCookies()`, `removeRememberUser()` as in [UserRepository](https://github.com/dotkernel/frontend/blob/3.0/src/User/src/Repository/UserRepository.php). +9. Edit `src/User/src/Controller/UserController.php`: add a new property called `$config`, and edit `loginAction()` and `logoutAction()` as in [UserController](https://github.com/dotkernel/frontend/blob/3.0/src/User/src/Controller/UserController.php). + +## FAQ + +**Q: Since which release is Remember Me implemented in Dotkernel?** +A: It's implemented in Dotkernel Frontend starting from Release 3.3.0. + +**Q: How do you add the Remember Me checkbox to the login UI?** +A: In src/User/templates/user/login.html.twig, under the password element, render the rememberMe element retrieved via form.get('rememberMe') with formElement(). + +**Q: What form and validation changes are needed?** +A: A 'rememberMe' checkbox element must be added to src/User/src/Form/LoginForm.php, and a StringTrim filter plus a NotEmpty validator must be added for it in src/User/src/InputFilter/LoginInputFilter.php. + +**Q: What backend pieces implement the actual remember-me functionality?** +A: A new UserRememberMe entity, a database migration (created and run with phinx) for its table, a new RememberMeMiddleware registered in config/pipeline.php, a rememberMe cookie configuration block added to config/autoload/local.php, and new methods added to UserService, UserRepository, and UserController (including loginAction() and logoutAction()). + +**Q: What does the UserRememberMe table store?** +A: It's used to store data from the cookie, which helps log the user in automatically. + +**Q: How do you apply the new remember-me button styles?** +A: Add the CSS to src/App/assets/scss/components/_profile.scss, then run npm run prod to compile the CSS so the button styling takes effect. + +## Resources + +- [Dotkernel Frontend on GitHub](https://github.com/dotkernel/frontend) +- [UserRememberMe entity example](https://github.com/dotkernel/frontend/blob/3.0/src/User/src/Entity/UserRememberMe.php) +- [Remember user schema migration example](https://github.com/dotkernel/frontend/blob/3.0/data/database/migrations/20220621062142_remember_user_schema.php) +- [RememberMeMiddleware example](https://github.com/dotkernel/frontend/blob/3.0/src/App/src/Middleware/RememberMeMiddleware.php) +- [pipeline.php example](https://github.com/dotkernel/frontend/blob/3.0/config/pipeline.php) +- [UserService example](https://github.com/dotkernel/frontend/blob/3.0/src/User/src/Service/UserService.php) +- [UserRepository example](https://github.com/dotkernel/frontend/blob/3.0/src/User/src/Repository/UserRepository.php) +- [UserController example](https://github.com/dotkernel/frontend/blob/3.0/src/User/src/Controller/UserController.php) diff --git a/public/readme/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.md b/public/readme/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.md new file mode 100644 index 00000000..3fa2a4b2 --- /dev/null +++ b/public/readme/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.md @@ -0,0 +1,37 @@ +--- +title: "Avoid routing through bootstrap of non existent files" +description: "How to stop missing static files from being routed through the bootstrap and logging out users whose session regenerates on each request." +author: "admin" +date_published: "2013-11-29" +canonical_url: "https://new.dotkernel.com/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files/" +category: "Dotkernel" +language: "en" +--- + +# Avoid routing through bootstrap of non existent files + +In some cases you may encounter missing files: images, CSS, or JS files. All those missing files are processed by the current bootstrap: `index.php`. + +If the session is set to regenerate on each request, as a normal security measure, the currently logged-in user is logged off, because the session ID is different now. + +To avoid this, below the following line: + +``` +RewriteEngine On +``` + +add the line: + +``` +RewriteCond %{REQUEST_FILENAME} (\.gif|\.jpg|\.png|\.css|\.js)$ +``` + +Save, and don't forget to test. + +## FAQ + +**Q: What problem does this fix address?** +A: When missing static files (images, CSS, JS) are routed through the bootstrap (index.php) and the session is set to regenerate on each request, the currently logged-in user gets logged off because the session ID changes. + +**Q: What is the fix?** +A: Below the "RewriteEngine On" line, add a RewriteCond matching file extensions like .gif, .jpg, .png, .css, and .js, so requests for those files are not routed through the bootstrap. diff --git a/public/readme/dotkernel/caching-in-dotkernel-using-zend-framework.md b/public/readme/dotkernel/caching-in-dotkernel-using-zend-framework.md new file mode 100644 index 00000000..e77b97ff --- /dev/null +++ b/public/readme/dotkernel/caching-in-dotkernel-using-zend-framework.md @@ -0,0 +1,72 @@ +--- +title: "Caching in DotKernel using Zend Framework" +description: "How DotKernel's upcoming 1.8 cache layer stores router, ACL, menu and other data between requests, using APC/APCU or file storage." +author: "Gabi DJ" +date_published: "2015-01-29" +canonical_url: "https://new.dotkernel.com/dotkernel/caching-in-dotkernel-using-zend-framework/" +category: "Dotkernel" +language: "en" +--- + +# Caching in DotKernel using Zend Framework + +## TL;DR +Loading configuration and settings from XML files on every request is expensive, both due to hard-drive latency and XML parsing overhead. DotKernel 1.8 implements a cache layer for router, acl_role, menu, options (including seo_xml), browser_xml, os_xml and test data, with a choice of APC/APCU or file-based storage. + +## 1. Configuring the cache + +The configuration is set from `/configs/application.ini`: whether caching is enabled, how long the cache stays valid, the cache namespace, and the storage provider (File or APC). The article recommends disabling the cache in development mode. See [Configuring the Cache in DotKernel](http://www.dotkernel.com/dotkernel/configuring-the-cache-in-dotkernel/) for more details. + +## 2. Using the cache + +The cache is automatically loaded during initialization and stored in the Registry — loading it manually is not needed because it's already loaded on kernel initialization (see `Dot_Kernel::initialize($startTime)`). If you want to use caching outside of that normal initialization, load it with: + +```php +Dot_Cache::loadCache(); +``` + +Note: the cache key must match a specific RegEx pattern. + +Example of object caching: + +```php +$id = 'MyCachedKey'; +$obj = new stdClass(); +$obj->text = 'I am a cached text'; + +// saving an object +Dot_Cache::save(obj, $id); + +// loading the object +$value = Dot_Cache::load($id); + +// checking if we have the object in cache +if ($value !== false) { + // assuming we only need the text value from the object + echo $value->text; +} else { + echo 'no value cached for '. $id ; +} +``` + +## FAQ + +**Q: What data does DotKernel's cache layer store?** +A: Router, acl_role, menu, options (including seo_xml), browser_xml, os_xml, and test data between requests. + +**Q: What storage providers are available for the cache?** +A: Two cache factories to choose from: APC (or APCU for newer PHP installations) and File. + +**Q: Where is the cache configured?** +A: In /configs/application.ini, where you can enable or disable caching, set how long the cache stays valid, choose the cache namespace, and pick the storage provider (File or APC). The article recommends disabling the cache in development mode. + +**Q: Do you need to manually load the cache engine?** +A: No, it's automatically loaded during kernel initialization (Dot_Kernel::initialize()). Manually calling Dot_Cache::loadCache() is only needed if you want to use caching outside of that normal initialization. + +**Q: Can you cache PHP objects, not just simple values?** +A: Yes, the article shows an example of saving and loading a stdClass object using Dot_Cache::save() and Dot_Cache::load(). + +## Resources + +- [DotKernel Reserved Variable Names for Caching](http://www.dotkernel.com/dotkernel/dotkernel-reserved-variable-names-for-caching) +- [Configuring the Cache in DotKernel](http://www.dotkernel.com/dotkernel/configuring-the-cache-in-dotkernel/) diff --git a/public/readme/dotkernel/camelcase-table-names-in-mysql-on-windows.md b/public/readme/dotkernel/camelcase-table-names-in-mysql-on-windows.md new file mode 100644 index 00000000..0737318c --- /dev/null +++ b/public/readme/dotkernel/camelcase-table-names-in-mysql-on-windows.md @@ -0,0 +1,33 @@ +--- +title: "camelCase Table Names in MySQL on Windows" +description: "How to fix MySQL on WAMP/XAMPP lowercasing camelCase table names by setting lower_case_table_names=2 in my.cnf." +author: "admin" +date_published: "2010-03-12" +canonical_url: "https://new.dotkernel.com/dotkernel/camelcase-table-names-in-mysql-on-windows/" +category: "Dotkernel" +language: "en" +--- + +# camelCase Table Names in MySQL on Windows + +If you are using a WAMP stack, like WAMP or XAMPP, and try to create a table in camelCase (example: `adminLogin`), you will notice that camelCase is not working — the table name will be lowercase: `adminlogin`. + +In order to fix this, add the following line to your `my.cnf` file: + +``` +lower_case_table_names=2 +``` + +and restart MySQL. + +## FAQ + +**Q: What happens when you create a camelCase table name on WAMP or XAMPP?** +A: A table created with a camelCase name, for example adminLogin, ends up stored as all lowercase, e.g. adminlogin, instead. + +**Q: How do you fix it?** +A: Add the line `lower_case_table_names=2` to your my.cnf file and restart MySQL. + +## Resources + +- [MySQL lower_case_table_names documentation](http://dev.mysql.com/doc/refman/4.1/en/server-system-variables.html#sysvar_lower_case_table_names) diff --git a/public/readme/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.md b/public/readme/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.md new file mode 100644 index 00000000..25287dda --- /dev/null +++ b/public/readme/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.md @@ -0,0 +1,24 @@ +--- +title: "Commitment to PHP - new Zend Certified Engineers - ZCE - in our team" +description: "Two more team members passed the ZCE exam, bringing the team's total to 5 Zend Certified Engineers." +author: "admin" +date_published: "2012-04-20" +canonical_url: "https://new.dotkernel.com/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team/" +category: "Dotkernel" +language: "en" +--- + +# Commitment to PHP - new Zend Certified Engineers - ZCE - in our team + +Another 2 of our team members passed the ZCE exam. Now we are 5. That means we are really taking PHP into serious consideration, and at the very least we have good technical skills. + +See the [Zend Yellow Pages](http://www.zend.com/store/education/certification/yellow-pages.php#list-cid=0&sid=&certtype_zf=1&certtype_php=1&certtype=&firstname=&lastname=&company=Dotboost%20Technologies&ClientCandidateID=). + +## FAQ + +**Q: How many Zend Certified Engineers does the team have?** +A: According to the article, 2 more team members passed the ZCE exam, bringing the team's total to 5 Zend Certified Engineers. + +## Resources + +- [Zend Yellow Pages listing](http://www.zend.com/store/education/certification/yellow-pages.php#list-cid=0&sid=&certtype_zf=1&certtype_php=1&certtype=&firstname=&lastname=&company=Dotboost%20Technologies&ClientCandidateID=) diff --git a/public/readme/dotkernel/dotkernel-1-2-2-release.md b/public/readme/dotkernel/dotkernel-1-2-2-release.md new file mode 100644 index 00000000..23da5fdb --- /dev/null +++ b/public/readme/dotkernel/dotkernel-1-2-2-release.md @@ -0,0 +1,51 @@ +--- +title: "DotKernel 1.2.2 release" +description: "DotKernel 1.2.2 is a bug-fix release closing five issues, including captcha error handling, a pagination bug, and a copyright line update that touched every PHP file." +author: "Teo" +date_published: "2010-07-30" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-1-2-2-release/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel 1.2.2 release + +## TL;DR + +DotKernel 1.2.2 is a bug-fix release that closes five tracked issues. Because one of the fixes updated the copyright line, every PHP file in the codebase changed, so the full release or the incremental upgrade package is needed. + +## Bug fixes in 1.2.2 + +- **31** - captcha errors try/catch +- **32** - pagination issue +- **33** - admin wrong link +- **34** - Acunetix scan results from July 24th (notices and one fatal error) +- **35** - update copyright line in files + +**Note:** because of bug 35, all PHP files changed in this release. + +## Upgrading + +To get only the changed files from 1.2.1 to 1.2.2, download the upgrade package (linked in the post) instead of the full distribution. Full details are available in the ChangeLog 1.2.2, and further changes can be tracked on the DotKernel Tracker or DotKernel WebSVN. + +Note also that DotKernel 1.2.1 had been released a few days earlier, on July 22, 2010, with its own ChangeLog and upgrade package. + +## FAQ + +**Q: What does the DotKernel 1.2.2 release include?** +A: It's a bug-fix release that closes five issues: captcha error handling (try/catch), a pagination issue, a wrong admin link, notices and a fatal error found by an Acunetix scan, and an update to the copyright line in files. + +**Q: Why did all PHP files change in the 1.2.2 release?** +A: Because of the fix for bug 35, which updated the copyright line, every PHP file in the codebase was touched, which is why the note in the post warns that all PHP files have changed. + +**Q: How can I upgrade from a previous version to 1.2.2?** +A: You can download just the changed files from 1.2.1 to 1.2.2 using the upgrade package linked in the post, or check the ChangeLog 1.2.2 for full details of what changed. + +## Resources + +- ChangeLog 1.2.2 (linked in the original post as `../changelog/1-2-2/`) +- Upgrade package for 1.2.2 (linked in the original post as `../download/?did=17`) +- DotKernel Tracker: http://www.dotkernel.net/ +- DotKernel WebSVN: http://websvn.dotkernel.net/listing.php?repname=DotKernel+ver.+1 +- ChangeLog 1.2.1 (linked in the original post as `../changelog/1-2-1/`) +- Upgrade package for 1.2.1 (linked in the original post as `../download/?did=14`) diff --git a/public/readme/dotkernel/dotkernel-1-3-0-release.md b/public/readme/dotkernel/dotkernel-1-3-0-release.md new file mode 100644 index 00000000..9043c588 --- /dev/null +++ b/public/readme/dotkernel/dotkernel-1-3-0-release.md @@ -0,0 +1,64 @@ +--- +title: "DotKernel 1.3.0 release" +description: "DotKernel 1.3.0 adds an admin skin switcher, a way to protect member-only links, and reorganizes resource.xml into route.xml and dots.xml, at the cost of backward compatibility." +author: "Teo" +date_published: "2010-10-15" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-1-3-0-release/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel 1.3.0 release + +## TL;DR + +DotKernel 1.3.0 brings a switchable admin skin, a way to protect member-only pages, a rename of Dot_Sessions, and a reorganization of resource.xml into route.xml and dots.xml. Because of that XML reorganization, 1.3.0 is not backward compatible with earlier versions. + +## Highlights + +### Admin skin switcher + +The admin skin can now be customized. Several ready-made skins are available: blue, brown, gray, and green. Set the skin by changing the `settings.admin.skin` value (e.g. `settings.admin.skin = green`). + +### Protecting member-only links + +To protect a link so only logged-in members can access it, add this line in the controller file above the code that should require login: + +```php +Dot_Auth::checkIdentity(); +``` + +### XML reorganization + +Some XML files from the configs folder were changed. `resource.xml` was deleted and its content was split between two new files, `route.xml` and `dots.xml`. + +### Other closed issues + +The release also closed a number of other tracked issues, covering: the Dot_Sessions rename, menu issues in Admin and frontend, URL casing consistency, an XSS issue in the forgot-password flow, several security scan results, admin listing/UI fixes, a GeoIP extension listing feature, and formatting cleanup (blank lines, brace placement) across the frontend files. + +## Compatibility note + +Because of the XML file reorganization, this release is **not compatible** with previous versions. Further details on what changed are available on the DotKernel Tracker or DotKernel WebSVN. + +## FAQ + +**Q: What is new in the admin interface in DotKernel 1.3.0?** +A: 1.3.0 adds a skin switcher for the admin, with several ready-made skins (blue, brown, gray, green) that can be set via the settings.admin.skin configuration value. + +**Q: How do I protect a page so only logged-in members can access it?** +A: Add the line Dot_Auth::checkIdentity(); in the controller file above the code you want to protect - everything below that line requires the visitor to be logged in. + +**Q: What happened to resource.xml in this release?** +A: resource.xml was deleted and its content split between two new files, route.xml and dots.xml. + +**Q: Is DotKernel 1.3.0 backward compatible with earlier versions?** +A: No. Because of the XML file reorganization (bug 69), 1.3.0 is not compatible with previous versions. + +## Resources + +- DotKernel 1.3.0 download (linked in the original post as `../download/?did=23`) +- ChangeLog 1.3.0 (linked in the original post as `../changelog/1-3-0/`) +- route.xml documentation (linked in the original post as `../docs/router-xml/`) +- dots.xml documentation (linked in the original post as `../docs/dots-xml/`) +- DotKernel Tracker: http://www.dotkernel.net/ +- DotKernel WebSVN: http://websvn.dotkernel.net/listing.php?repname=DotKernel diff --git a/public/readme/dotkernel/dotkernel-1-3-2-release.md b/public/readme/dotkernel/dotkernel-1-3-2-release.md new file mode 100644 index 00000000..b7b321a3 --- /dev/null +++ b/public/readme/dotkernel/dotkernel-1-3-2-release.md @@ -0,0 +1,51 @@ +--- +title: "DotKernel 1.3.2 release" +description: "DotKernel 1.3.2 is a maintenance release with many bug fixes, a couple of minor features, and some refactoring, released just before the winter holidays." +author: "Teo" +date_published: "2010-12-23" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-1-3-2-release/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel 1.3.2 release + +## TL;DR + +Released just before the winter holidays, DotKernel 1.3.2 is mainly a maintenance release: it contains many bug fixes, some refactoring, and a few minor features. + +## Bug fixes + +- CSS issue on the admin phpinfo page +- Warning in the admin dashboard for a file +- WURFL cache issue in admin +- WURFL version issue +- Dot_Paginator bug +- Zend Paginator double-query issue +- Database naming convention issue +- Database normalisation/refactor + +## Minor features + +- Refactor of validIP in the Dot_Kernel class +- WURFL date and API version shown in admin + +## Refactoring + +- Zend_Paginator refactoring +- Added a dojo dijit theme to DotKernel + +## FAQ + +**Q: What kind of release is DotKernel 1.3.2?** +A: It's mainly a maintenance release, containing many bug fixes, some refactoring, and a few minor features. + +**Q: What bugs were fixed in 1.3.2?** +A: Fixes include a CSS issue on the admin phpinfo page, a warning in the admin dashboard, a WURFL cache issue and WURFL version issue in admin, a Dot_Paginator bug, a Zend Paginator double-query issue, and a database naming convention issue. + +**Q: What minor features and refactoring were included?** +A: Minor features include a refactor of validIP in Dot_Kernel and showing the WURFL date and API version in admin. Refactoring covered Zend_Paginator and added a dojo dijit theme to DotKernel. + +## Resources + +- DotKernel 1.3.2 download: http://www.dotkernel.com/download/?did=27 diff --git a/public/readme/dotkernel/dotkernel-1-5-0-released.md b/public/readme/dotkernel/dotkernel-1-5-0-released.md new file mode 100644 index 00000000..a806ed9b --- /dev/null +++ b/public/readme/dotkernel/dotkernel-1-5-0-released.md @@ -0,0 +1,68 @@ +--- +title: "DotKernel 1.5.0 Released" +description: "DotKernel 1.5.0 skips version 1.4 entirely and brings a switch from Dojo to jQuery, redesigned admin and frontend, model inheritance via Dot_Model, dashed controller support, and a reorganized Zend Registry." +author: "Adrian" +date_published: "2011-06-15" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-1-5-0-released/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel 1.5.0 Released + +## TL;DR + +After a longer wait than usual and around 250 commits, DotKernel 1.5.0 was released, skipping 1.4 entirely due to the scale of changes. Highlights include switching from Dojo to jQuery, a redesigned admin and frontend, model inheritance through a new Dot_Model class, support for dashed controller names, and a reorganized Zend Registry. + +## Why skip straight to 1.5.0? + +Due to the large amount of changes and the long time spent in development, the team chose to skip 1.4 and go straight to 1.5.0. + +## Highlights of 1.5.0 + +### Switched from Dojo to jQuery + +Starting with 1.5.0, DotKernel switched from using Dojo to jQuery. Dojo can still be used in your own projects, but only jQuery is used and maintained in the DotKernel distribution itself. + +### New designs + +The admin site was redesigned, with new themes and a dropdown menu, along with a new and simpler design for the front-end. + +### Model inheritance + +Previously there was a lot of code duplication in models — for example, a `getUserById` function might exist separately in both the admin and frontend User models. To solve this, a `Dot_Model` class was introduced along with a way to define global models inherited by both admin and frontend. A `User` class in the admin only holds admin-specific methods, a `User` class in the frontend only holds frontend-specific methods, and both inherit a shared `Dot_Model_User` class containing the common code. + +### Dashed controllers + +The way controller names are parsed was changed so that controllers with multiple words, split with dashes, work without breaking the coding standard. For example, `www.example.com/search-article` calls `SearchArticleController.php`. + +### Zend Registry reorganization + +The structure of the registry was changed; more details are covered in a separate blog post on Zend Registry usage in DotKernel. + +## Scale of the release + +There were about 250 commits in the SVN repository since the previous release, so the blog post could not cover every change. + +## FAQ + +**Q: Why did DotKernel jump from 1.3 straight to 1.5.0?** +A: Because of the large amount of changes and the long time spent in development, the team chose to skip version 1.4 and go straight to 1.5.0. + +**Q: Did DotKernel switch from Dojo to jQuery in 1.5.0?** +A: Yes. Starting with 1.5.0, DotKernel switched from Dojo to jQuery for its own distribution, though Dojo can still be used in your own projects. + +**Q: What is Dot_Model and why was it introduced?** +A: Dot_Model is a base class introduced to reduce code duplication between admin and frontend models. Both admin- and frontend-specific model classes (such as User) inherit from a shared Dot_Model_User class that holds the common code. + +**Q: How does the "dashed controllers" feature work?** +A: The controller name parsing was changed so a URL like www.example.com/search-article correctly calls SearchArticleController.php, allowing multi-word controller names split with dashes without breaking the coding standard. + +**Q: How much changed in the 1.5.0 release?** +A: About 250 commits went into the SVN repository since the previous release, so the blog post only covers the highlights - the full DotKernel 1.5.0 download is available to try out. + +## Resources + +- Intro to jQuery: http://www.dotkernel.com/javascript/intro-to-jquery/ +- Zend Registry usage in DotKernel: http://www.dotkernel.com/dotkernel/zend-registry-usage-in-dotkernel/ +- DotKernel 1.5.0 download: http://www.dotkernel.com/download/?did=33 diff --git a/public/readme/dotkernel/dotkernel-1-8-0-lts-released.md b/public/readme/dotkernel/dotkernel-1-8-0-lts-released.md new file mode 100644 index 00000000..5649f0fa --- /dev/null +++ b/public/readme/dotkernel/dotkernel-1-8-0-lts-released.md @@ -0,0 +1,88 @@ +--- +title: "DotKernel 1.8.0 LTS Released" +description: "DotKernel 1.8.0 (LTS) introduces a plugin architecture, a redesigned mobile-friendly admin and frontend, APC/File caching for speed, a new Dot_Request class, and several security and alerting improvements." +author: "Gabi DJ" +date_published: "2015-06-08" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-1-8-0-lts-released/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel 1.8.0 LTS Released + +## TL;DR + +DotKernel 1.8.0 (LTS) was released with a new Plugin Architecture, a redesigned and mobile-friendly frontend, APC/File caching for faster response times, a new Dot_Request class, and multiple security and alerting improvements. Some features (WURFL integration, multiple SMTP transporters) were removed from core and made available as plugins instead. + +## What is LTS? + +Long-term support (LTS) is a type of special version or edition of software designed to be supported for a longer than normal period. It's particularly applicable to open-source software projects. The 1.8.0 LTS release itself contains many bug fixes, some refactoring, and a few minor features. + +## Highlights of 1.8.0 (LTS) + +### Plugin Architecture + +Starting with 1.8.0, DotKernel uses Plugins to make extending the framework easier. + +### New design + +The admin module was redesigned and the frontend module is now mobile-friendly, while the separate mobile module remains available. + +### Loads faster + +The framework now supports APC and File Caching, with all XML and config files cached in order to maximize response speed. + +### Easier request handling + +A new class, `Dot_Request`, gives control over the request data before use — for example, so that `$_SERVER`, `$_GET`, and `$_POST` are only accessed from within controllers. + +### Features added + +- API with Rate Limit — a simple API with single-key authentication and a basic rate limit implementation (configurable in `/configs/application.ini`, section `params.api`) +- Cache System — built on Zend_Cache backends, providing caching within DotKernel and in library code + +### Other changes + +- Removed WURFL integration — mobile device detection is now handled separately; WURFL can be added as a plugin +- Removed support for multiple SMTP transporters — it can be added as a plugin +- Security scan in the Admin Dashboard, showing recommended (especially security-related) settings +- Admin failed-login notifications are now sent to all developers listed in `devEmails` (within the `settings` table), not just the first admin +- Alert System — alerts can be sent to all developers to notify them if something goes wrong + +### Bug fixes + +- `seo.xml` caused an error when two modules used the same variable name instead of overwriting it +- Emails were sent twice +- A wrong "unwritable" warning appeared on nginx + +## Scale of the release + +There were a lot of commits in the SVN repository since the previous release, so the blog post only covers the highlights. + +## FAQ + +**Q: What does LTS mean for DotKernel 1.8.0?** +A: LTS stands for Long-term support, a type of special version designed to be supported for longer than normal, which is particularly common for open-source software projects. + +**Q: What is the Plugin Architecture introduced in 1.8.0?** +A: Starting with 1.8.0, DotKernel uses Plugins to make extending the framework easier. + +**Q: How does 1.8.0 load faster than previous versions?** +A: It supports APC and File Caching within the framework, so XML files and config files are cached to maximize response speed. + +**Q: What is Dot_Request?** +A: Dot_Request is a new class that gives you control over the request data before you use it, so that the variables $_SERVER, $_GET and $_POST are only accessed within controllers. + +**Q: What was removed from DotKernel in 1.8.0?** +A: WURFL integration was removed (mobile device detection is now handled separately, and WURFL can be added as a plugin), and support for multiple SMTP transporters was removed (it can also be added as a plugin). + +**Q: What security-related additions does 1.8.0 include?** +A: A security scan in the Admin Dashboard shows recommended settings, admin failed-login notifications are sent to all developers listed in devEmails (not just the first admin), and a new Alert System can notify developers if something goes wrong. + +## Resources + +- What is LTS: http://www.dotkernel.com/long-term-support +- Caching in DotKernel using Zend Framework: http://www.dotkernel.com/dotkernel/caching-in-dotkernel-using-zend-framework/ +- DotKernel reserved variable names for caching: http://www.dotkernel.com/dotkernel/dotkernel-reserved-variable-names-for-caching/ +- How to use alerts in DotKernel: http://www.dotkernel.com/dotkernel/how-to-use-alerts-in-dotkernel/ +- DotKernel 1.8.0 (LTS) download: http://www.dotkernel.com/download/?did=41 diff --git a/public/readme/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.md b/public/readme/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.md new file mode 100644 index 00000000..dd2d0467 --- /dev/null +++ b/public/readme/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.md @@ -0,0 +1,39 @@ +--- +title: "DotKernel 1.8.1 + Upgrade from 1.8.0 Released" +description: "DotKernel 1.8.1 adds Enhanced Cache Support with cache tagging, and ships with a dedicated upgrade package for users coming from 1.8.0." +author: "Gabi DJ" +date_published: "2015-06-11" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel 1.8.1 + Upgrade from 1.8.0 Released + +## TL;DR + +DotKernel 1.8.1 was released with Enhanced Cache Support, allowing cache tags to be used if the hosting environment supports them. A dedicated upgrade package is available for users coming from 1.8.0. + +## What's new + +- Enhanced Cache Support — you can use tags in your cache system if the host supports it + +## Download links + +- DotKernel 1.8.1 (full package) +- Upgrade from DotKernel 1.8.0 +- DotKernel 1.8.0 (LTS) + +## FAQ + +**Q: What's new in DotKernel 1.8.1?** +A: The main change is Enhanced Cache Support, which means you can use tags in your cache system if the host supports it. + +**Q: How do I upgrade from 1.8.0 to 1.8.1?** +A: The post provides a dedicated "Upgrade from DotKernel 1.8.0" download link, separate from the full DotKernel 1.8.1 package and the original DotKernel 1.8.0 (LTS) download. + +## Resources + +- DotKernel 1.8.1: http://www.dotkernel.com/download/?did=42 +- Upgrade from DotKernel 1.8.0: http://www.dotkernel.com/download/?did=43 +- DotKernel 1.8.0 (LTS): http://www.dotkernel.com/download/?did=41 diff --git a/public/readme/dotkernel/dotkernel-coding-standard.md b/public/readme/dotkernel/dotkernel-coding-standard.md new file mode 100644 index 00000000..5ec7ed7f --- /dev/null +++ b/public/readme/dotkernel/dotkernel-coding-standard.md @@ -0,0 +1,78 @@ +--- +title: "DotKernel Coding Standard" +description: "DotKernel borrows the Zend Framework coding standard with a few exceptions, covering indentation, class/interface/file naming, and curly brace placement for control statements." +author: "admin" +date_published: "2008-03-28" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-coding-standard/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel Coding Standard + +## TL;DR + +DotKernel is a "skeleton" of Zend Framework and borrows its coding standard from the ZF Coding Standard, with a small number of exceptions covering indentation, naming conventions, and brace placement. + +## Indentation + +Indentation is made with tabs, not spaces (per section B.2.2 of the Zend Framework Coding Standard). + +## Naming conventions + +DotKernel uses camel naming conventions, with these DotKernel-specific rules: + +| Element | Convention | Example | +|---|---|---| +| Classes | Start with `Dot_` | `Dot_Templates` | +| Interfaces | End with the string "Interface" | `Dot_Db_Interface` | +| Filenames | Always use the `.php` extension, no fancy extensions | `.php`, not `.inc` | + +## Control statements — brace placement + +Every opening curly brace `{` starts on its own new line after the statement, and its matching closing brace `}` is also placed on its own new line, aligned in the same column as the opening brace, for better indentation of the code. + +Example: + +```php +if ($a != 2) +{ + $a = 2; +} +``` + +```php +if ($a != 2) +{ + $a = 2; + if($a == 2) + { + $c = 3; + } +} +``` + +## FAQ + +**Q: What coding standard does DotKernel follow?** +A: DotKernel borrows its coding standard from the Zend Framework Coding Standard, with some exceptions described in this article. + +**Q: Tabs or spaces for indentation?** +A: DotKernel indents with tabs, not spaces. + +**Q: How should classes, interfaces, and filenames be named?** +A: Classes start with the prefix Dot_ (e.g. Dot_Templates), interfaces end with the string "Interface" (e.g. Dot_Db_Interface), and all PHP files use the ".php" extension, with no fancy extensions like ".inc". + +**Q: How should curly braces be placed for control statements?** +A: Every opening curly brace starts on its own new line after the statement, and its matching closing brace also goes on a new line, aligned in the same column as the opening brace, for better indentation of the code. + +## Resources + +- Zend Framework: http://framework.zend.com/ +- ZF Coding Standard: http://framework.zend.com/manual/en/coding-standard.php-file-formatting.html +- ZF Coding Standard — Indentation: http://framework.zend.com/manual/en/coding-standard.php-file-formatting.html#coding-standard.php-file-formatting.indentation +- ZF Coding Standard — Naming Conventions: http://framework.zend.com/manual/en/coding-standard.naming-conventions.html +- ZF Coding Standard — Classes: http://framework.zend.com/manual/en/coding-standard.naming-conventions.html#coding-standard.naming-conventions.classes +- ZF Coding Standard — Interfaces: http://framework.zend.com/manual/en/coding-standard.naming-conventions.html#coding-standard.naming-conventions.interfaces +- ZF Coding Standard — Filenames: http://framework.zend.com/manual/en/coding-standard.naming-conventions.html#coding-standard.naming-conventions.filenames +- ZF Coding Standard — Control Statements: http://framework.zend.com/manual/en/coding-standard.coding-style.html#coding-standard.coding-style.control-statements diff --git a/public/readme/dotkernel/dotkernel-database-naming-conventions-for-mysql.md b/public/readme/dotkernel/dotkernel-database-naming-conventions-for-mysql.md new file mode 100644 index 00000000..ac1edd49 --- /dev/null +++ b/public/readme/dotkernel/dotkernel-database-naming-conventions-for-mysql.md @@ -0,0 +1,77 @@ +--- +title: "DotKernel Database Naming Conventions for MySQL" +description: "DotKernel borrows its database naming conventions from FaZend, covering singular table names, auto-incrementing id columns, foreign key and constraint naming patterns, and camelLetter casing." +author: "admin" +date_published: "2010-03-10" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-database-naming-conventions-for-mysql/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel Database Naming Conventions for MySQL + +## TL;DR + +DotKernel's database naming conventions are borrowed from FaZend's "Rules of naming of database tables and columns." Tables use singular, camelLetter names, every table has an auto-increment id, foreign keys are named after the referenced table and column, and SQL keywords are capitalized. + +## Database naming conventions for tables and columns + +- Singular table names only (e.g. `user`, `category`, `product`, `order`, `orderProduct`) +- Every table must have an auto-incrementing integer column `id` +- ZF-like names of columns and tables (e.g. `user::isAdmin`, `orderProduct::product`) +- Foreign keys must have the same name as the referenced table plus the name of the referenced column. + Example: table referenced is `admin`, column name `Id`, so the foreign key column will be `adminId`. +- Pattern for CONSTRAINT name: `FK_referencedTableName_tableName`. + Example: `CONSTRAINT FK_admin_adminLogin`. +- SQL keywords are capitalized (e.g. `SELECT`, `INT`) + +## Example of proper SQL file formatting and naming + +```sql +CREATE TABLE IF NOT EXISTS `user` + ( + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `username` VARCHAR(255) NOT NULL, + `password` VARCHAR(25) NOT NULL, + `email` VARCHAR(100) NOT NULL, + `firstName` VARCHAR(255) NOT NULL, + `lastName` VARCHAR(255) NOT NULL, + `dateCreated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `userType` INT(11) NOT NULL AUTO_INCREMENT + `isActive` ENUM('0','1') NOT NULL DEFAULT '1', + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`), + UNIQUE KEY `email` (`email`) + CONSTRAINT `FK_user_userType` FOREIGN KEY(`userTypeId`) REFERENCES `userType`(`id`) + ON UPDATE CASCADE + ON DELETE CASCADE + ) + ENGINE=InnoDB + DEFAULT CHARSET=latin1 + AUTO_INCREMENT=1 ; +``` + +## Conclusion + +The names of database tables and columns must follow camelLetter naming conventions. + +## FAQ + +**Q: Where do DotKernel's database naming conventions come from?** +A: They are borrowed from FaZend's "Rules of naming of database tables and columns," an open-source PHP framework based on Zend Framework. + +**Q: Should table names be singular or plural?** +A: Singular table names only, for example user, category, product, order, orderProduct. + +**Q: How should foreign key columns be named?** +A: A foreign key column takes the name of the referenced table plus the name of the referenced column. For example, referencing table admin's Id column produces a column named adminId. + +**Q: What naming pattern is used for CONSTRAINT names?** +A: The pattern is FK_referencedTableName_tableName, for example CONSTRAINT `FK_admin_adminLogin`. + +**Q: What casing convention applies to table/column names and to SQL keywords?** +A: Table and column names must follow camelLetter naming conventions, while SQL keywords such as SELECT and INT are capitalized. + +## Resources + +- FaZend: Rules of naming of database tables and columns: http://fazend.com/a/2009-11-DataNaming.html diff --git a/public/readme/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.md b/public/readme/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.md new file mode 100644 index 00000000..f6e91def --- /dev/null +++ b/public/readme/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.md @@ -0,0 +1,93 @@ +--- +title: "Dotkernel Light - Starting with Mezzio microframework and Laminas components" +description: "Dotkernel Light is a stripped-down version of Dotkernel Frontend built on Mezzio and Laminas components, keeping only routing, templating, error handling, and tests, for a gentler learning curve." +author: "Florin Bidirean" +date_published: "2024-10-03" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components/" +category: "Dotkernel" +language: "en" +--- + +# Dotkernel Light - Starting with Mezzio microframework and Laminas components + +## TL;DR + +Dotkernel Light is a version of Dotkernel Frontend that includes only the bare-bones essentials. It's built on the Mezzio microframework using Laminas components, and is designed as a presentation site, a fast-start introduction to Mezzio, or a clean starting point for a project where you want full control over functionality. + +## Goal + +Dotkernel Light is designed to be a fast-start example of using the Mezzio microframework, as well as an entry-level version of Dotkernel Frontend. Its purpose is to present the newbie developer with as few moving parts as possible, while also giving the more advanced developer a starting point with full control of the platform's functionality. + +Light retains the modern architecture of Mezzio microframework and several Laminas components used in Dotkernel Frontend. The low number of out-of-the-box components encourages active exploration of the functionality required by your application — you add only the packages your application needs. + +## Components and functionality + +Dotkernel Light is a stripped-down version of Dotkernel Frontend. Like Frontend, it is built on top of Mezzio microframework using Laminas components, but with limited features and a lower number of packages, which makes the learning curve of working with the repo considerably gentler. + +### Functionality retained + +- Routing +- Templating +- Error handling +- Tests and code quality checks + +### Items removed compared to Frontend + +- Doctrine and all database-related stuff +- Sessions/Cookies/Flash messages +- Authentication/Authorization +- Dependency Injection +- Mail-related stuff +- Navigation +- CORS +- Forms/Validators/InputFilters +- User module +- Contact module +- Plugin module +- CSS/JS code no longer in use due to the above module removals +- Instructions from README.md that are no longer needed + +### Packages no longer required + +- dotkernel/dot-authorization +- dotkernel/dot-data-fixtures +- dotkernel/dot-dependency-injection +- dotkernel/dot-flashmessenger +- dotkernel/dot-mail +- dotkernel/dot-navigation +- dotkernel/dot-rbac-guard +- dotkernel/dot-response-header +- dotkernel/dot-session +- laminas/laminas-form +- laminas/laminas-i18n +- mezzio/mezzio-authorization-rbac +- mezzio/mezzio-cors +- ramsey/uuid-doctrine +- roave/psr-container-doctrine +- mezzio/mezzio-tooling +- rector/rector + +## FAQ + +**Q: What is Dotkernel Light?** +A: Dotkernel Light is a version of Dotkernel Frontend that includes only the bare-bones essentials. It's suitable as a presentation site, an introduction to the Mezzio microframework architecture, or a starting point for a more complex project where you want full control over functionality. + +**Q: What is the goal of Dotkernel Light?** +A: It's designed to be a fast-start example of using the Mezzio microframework as well as an entry-level version of Dotkernel Frontend, presenting the beginner developer with as few moving parts as possible while still letting the more advanced developer have full control of the platform's functionality. + +**Q: What functionality does Dotkernel Light retain?** +A: It keeps routing, templating, error handling, and tests and code quality checks. + +**Q: What was removed compared to Dotkernel Frontend?** +A: Items removed include Doctrine and all database related stuff, sessions/cookies/flash messages, authentication/authorization, dependency injection, mail related stuff, navigation, CORS, forms/validators/input filters, the User module, the Contact module, the Plugin module, unused CSS/JS code, and outdated README instructions. + +**Q: Which packages are no longer required in Dotkernel Light?** +A: Packages such as dotkernel/dot-authorization, dotkernel/dot-mail, dotkernel/dot-session, dotkernel/dot-navigation, dotkernel/dot-flashmessenger, laminas/laminas-form, mezzio/mezzio-cors, and several others used by Frontend are not required. + +## Resources + +- Dotkernel Light GitHub repository: https://github.com/dotkernel/light +- Working demo of Dotkernel Light: https://light.dotkernel.net/ +- Dotkernel Light documentation: https://docs.dotkernel.org/light-documentation/ +- Laminas Project: https://getlaminas.org/ +- Documentation of Mezzio: https://docs.mezzio.dev/ diff --git a/public/readme/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.md b/public/readme/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.md new file mode 100644 index 00000000..daac2362 --- /dev/null +++ b/public/readme/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.md @@ -0,0 +1,153 @@ +--- +title: "Dotkernel Light: the best choice for your presentation site" +description: "A walkthrough of using Dotkernel Light to build a simple presentation site: adding new pages, managing assets, and configuring Twitter/OpenGraph cards, the top menu, and the footer." +author: "Florin Bidirean" +date_published: "2024-10-14" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site/" +category: "Dotkernel" +language: "en" +--- + +# Dotkernel Light: the best choice for your presentation site + +## TL;DR + +Dotkernel Light is a lightweight starting point for a project when you want full control over its functionality, and it grows into something more complex as you add packages. It comes with routing, templating, error handling, and tests/code quality checks out of the box, but strips out everything a presentation site doesn't need — database, sessions/cookies/flash messages, auth, dependency injection, mail, navigation, CORS, forms, the user/contact/plugin modules. + +## What's included vs. removed + +| Included out of the box | Removed (not needed for a presentation site) | +|---|---| +| Routing | Everything related to the database | +| Templating | Sessions/Cookies/Flash messages | +| Error handling | Authentication/Authorization | +| Tests and code quality checks | Dependency Injection | +| | Mail related stuff | +| | Navigation | +| | CORS | +| | Forms/Validators/InputFilters | +| | User module | +| | Contact module | +| | Plugin module | + +## Adding new pages + +1. Add an `Action` function for the page in `src/Page/src/Controller/PageController.php`, for example: + +```php +public function examplePageAction(): ResponseInterface +{ + return new HtmlResponse( + $this->template->render('page::example-template') + ); +} +``` + + The URL for this example page would be `/page/example-page`. + +2. Create the matching template in `src/Page/templates/page/` — for the example above, `src/Page/templates/page/example-template.html.twig`. Put the page copy inside the `content` block: + +```twig +{% extends '@layout/default.html.twig' %} + +{% block title %}Page Title{% endblock %} + +{% block page_title %}{% endblock %} + +{% block content %} +
                          +
                          +

                          Add title here!

                          +
                          +
                          + +
                          + Add cool content here! +
                          +{% endblock %} +``` + + Make sure to check the header for any fonts your content requires. + +3. Place assets under `src/App/assets/`, in the default folders: + - `src/App/assets/fonts` + - `src/App/assets/images` + - `src/App/assets/js` + - `src/App/assets/scss` + + Make sure `npm` is installed and running during updates with `npm run watch`, or run `npm run prod` after edits are completed. + +## Optional items + +### Twitter and OpenGraph cards + +To promote pages on other platforms, edit the header section in `src/App/templates/layout/default.html.twig`, where the Twitter (X) and OpenGraph cards are placed. Update all items based on your page content. + +- `{{ url('home') }}` generates the homepage URL, and the same pattern is used for other pages, as in the canonical URL block: `{% block canonical %}{{ url(routeName ?? null) }}{% endblock %}` (the `block` is present to handle not-found pages, e.g. mistyped URLs). +- An image referenced as `{{ url('home') }}images/app/My-image.png` is found at `public/images/app/My-image.png`, copied there by the `npm` script from `src/App/assets/images/PHP-REST-API.png`. + +```html + + + + + + + + + + + + + + +``` + +### Top menu + +This menu is displayed on all pages, in the header. Edit it in `src/App/templates/layout/default.html.twig`, under `id="navbarHeader"`: + +```html + +``` + +You can replace the `nav-item` class for the `li` elements with `button-border` for a link that looks more like a button. + +### Footer + +To edit the footer on all pages, search for `
                          ` in `src/App/templates/layout/default.html.twig`. Its content is usually basic HTML and CSS with twig elements already covered above. + +## FAQ + +**Q: What is Dotkernel Light designed for?** +A: It's a good starting point for a project when you want full control over its functionality, and it easily grows into something more complex by integrating packages based on your requirements. + +**Q: What functionality does Dotkernel Light include out of the box?** +A: Routing, Templating, Error handling, and Tests and code quality checks. + +**Q: What features are removed from Dotkernel Light compared to Dotkernel Frontend?** +A: Everything related to the database, Sessions/Cookies/Flash messages, Authentication/Authorization, Dependency Injection, Mail related stuff, Navigation, CORS, Forms/Validators/InputFilters, the User module, the Contact module, and the Plugin module. + +**Q: How do you add a new page to a Dotkernel Light site?** +A: Add an Action function for the page (e.g. examplePageAction()) in src/Page/src/Controller/PageController.php that renders a template, then create the matching template file in src/Page/templates/page/ (e.g. example-template.html.twig), with the page copy placed in its content block. + +**Q: Where should new page assets like fonts, images, JS, and CSS be placed?** +A: Under src/App/assets/, in its default folders: src/App/assets/fonts, src/App/assets/images, src/App/assets/js, and src/App/assets/scss. Run npm run watch during edits, or npm run prod once the edits are completed. + +**Q: Where do you edit the top menu and footer that appear on every page?** +A: Both live in src/App/templates/layout/default.html.twig: the top menu items are under id="navbarHeader", and the footer content is inside the footer element with class "app-footer". + +## Resources + +- [dotkernel.org](https://www.dotkernel.org) — a working example +- [Dotkernel Light](https://github.com/dotkernel/light) +- [More from Dotkernel](https://github.com/dotkernel) diff --git a/public/readme/dotkernel/dotkernel-on-nginx.md b/public/readme/dotkernel/dotkernel-on-nginx.md new file mode 100644 index 00000000..eb5d61d5 --- /dev/null +++ b/public/readme/dotkernel/dotkernel-on-nginx.md @@ -0,0 +1,81 @@ +--- +title: "DotKernel on Nginx" +description: "A walkthrough of testing whether DotKernel runs out of the box on Nginx, and how the Nginx configuration was set up as a substitute for Apache's .htaccess." +author: "admin" +date_published: "2013-06-25" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-on-nginx/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel on Nginx + +## TL;DR + +Since Nginx was becoming the buzzword instead of Apache, this article tests DotKernel on Nginx and documents the configuration needed: server block settings, a `try_files` directive in place of `.htaccess`, PHP-FPM handling, and protecting the `configs` folder. + +## Setup steps + +1. Installed on a clean CentOS 6.3 VPS: Nginx 1.4.1, PHP 5.4.16, PHP-FPM. +2. Installed PHP modules: APC, GeoIP, and others. +3. Installed PEAR and Zend Framework from the PEAR ZF channel (`http://code.google.com/p/zend/`). +4. Fine-tuned `php.ini`: date, default charset, include path, etc. +5. Created a MySQL database on a remote server and allowed permissions from the Nginx server's IP to connect to it. +6. Edited the Nginx config (`/etc/nginx/conf.d/default.conf`): + - Set the server name: + ``` + server_name nginx.dotkernel.net; + ``` + - Set the document root, in the `location /` area: + ``` + root /var/www/html; + ``` + - The equivalent of the main `.htaccess` file, sending all requests to `index.php`: + ``` + try_files $uri $uri/ /index.php; + ``` + - Set the location for the PHP directive: + ``` + location ~ \.php$ { + root /var/www/html; + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name; + fastcgi_param APPLICATION_ENV staging; + include fastcgi_params; + } + ``` + `APPLICATION_ENV` can be changed to `production` or `development`. + - Protect the `configs` folder from web access: + ``` + #protect folders + location ~ ^/configs/ { + deny all; + } + ``` +7. Exported a DotKernel copy from SVN: + ```shell + svn export --force http://v1.dotkernel.net/svn/trunk/ /var/www/html + ``` +8. Edited `application.ini` to reflect the current settings, and checked that it was protected. +9. Result: the site was running on Nginx. Admin login was disabled on the demo for security reasons. + +## FAQ + +**Q: What server stack was used to test Dotkernel on Nginx?** +A: A clean CentOS 6.3 VPS running Nginx 1.4.1, PHP 5.4.16, and PHP-FPM, with the APC and GeoIP PHP modules installed. + +**Q: What is the Nginx equivalent of the main .htaccess file?** +A: The directive `try_files $uri $uri/ /index.php;`, which sends all requests to the index.php file. + +**Q: How is the configs folder protected from web access in Nginx?** +A: By adding a location block matching `^/configs/` that returns `deny all;`. + +**Q: How was the DotKernel codebase deployed onto the server?** +A: It was exported directly from SVN using `svn export --force http://v1.dotkernel.net/svn/trunk/ /var/www/html`. + +## Resources + +- [PEAR ZF Channel](http://code.google.com/p/zend/) +- [application.ini example](http://nginx.dotkernel.net/configs/application.ini) +- [Live Nginx demo](http://nginx.dotkernel.net/) diff --git a/public/readme/dotkernel/dotkernel-reserved-variable-names-for-caching.md b/public/readme/dotkernel/dotkernel-reserved-variable-names-for-caching.md new file mode 100644 index 00000000..55466e3f --- /dev/null +++ b/public/readme/dotkernel/dotkernel-reserved-variable-names-for-caching.md @@ -0,0 +1,53 @@ +--- +title: "DotKernel Reserved Variable Names for Caching" +description: "A reference of the variables DotKernel caches — router, ACL role, menu, options, and browser/OS data — and the cache keys they use." +author: "Gabi DJ" +date_published: "2015-01-29" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-reserved-variable-names-for-caching/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel Reserved Variable Names for Caching + +## TL;DR + +This article is a follow-up to "Caching in DotKernel Using Zend Framework Cache" and lists the variables DotKernel caches, along with the exact cache key each one uses. + +## Cached variables + +| Item | Source | Cache key(s) | +|---|---|---| +| Router | Loads routes (modules, controllers, actions) settings from `router.xml` | `router` | +| Auth / ACL Role | `Dot_Auth`'s `/configs/acl/role.xml`, defining user rights | `acl_role` | +| Menu | `menu.xml` from the current module | `admin_menu`, `frontend_menu` | +| Options | Files found in `/configs/dots/` | `option_MODULE_CONTROLLER`, e.g. `option_admin_Admin`, `option_frontend_Page`, `option_admin_seo` (seo is lowercase) | +| Browser & OS | `browser.xml` and `os.xml` in `/configs/useragent/`, identifying browser/OS name, icon, and type | `browser_xml`, `os_xml` | + +## Important note + +Be careful when changing the XML files or values that get cached, as they remain cached. If a change appears to have no effect, you must clear the cache or rewrite that value in the cache. + +## FAQ + +**Q: What cache key is used for the router?** +A: The router, which loads routes settings from router.xml, is cached as `router`. + +**Q: What cache key stores the ACL role definitions?** +A: The role.xml file (used by Dot_Auth, from /configs/acl/role.xml, to define user rights) is cached as `acl_role`. + +**Q: How is the menu cached?** +A: The menu.xml from the current module is cached as `admin_menu` and `frontend_menu`. + +**Q: What naming pattern do cached options use?** +A: `option_MODULE_CONTROLLER`, where MODULE is the current module and CONTROLLER is the current controller or "seo" — for example `option_admin_Admin`, `option_frontend_Page`, and `option_admin_seo` (seo is lowercase). + +**Q: What happens if you change browser.xml, os.xml, or other cached config files?** +A: The change won't take effect on its own, since values like browser_xml and os_xml remain cached — you must clear the cache or rewrite that value in cache to see the change take effect. + +## Resources + +- [Caching in DotKernel Using Zend Framework Cache](http://www.dotkernel.com/dotkernel/caching-in-dotkernel-using-zend-framework) +- [router.xml documentation](http://www.dotkernel.com/docs/router-xml/) +- [Module Structure](http://www.dotkernel.com/docs/module-structure/) +- [menu.xml documentation](http://www.dotkernel.com/docs/menu-xml/) diff --git a/public/readme/dotkernel/dotkernel-template-engine.md b/public/readme/dotkernel/dotkernel-template-engine.md new file mode 100644 index 00000000..2e49f5d9 --- /dev/null +++ b/public/readme/dotkernel/dotkernel-template-engine.md @@ -0,0 +1,21 @@ +--- +title: "DotKernel Template Engine" +description: "DotKernel Template Engine is an implementation of the PHPLib Template engine for PHP5, separating application code from the presentation layer." +author: "admin" +date_published: "2009-10-02" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-template-engine/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel Template Engine + +DotKernel Template Engine is an implementation of the PHPLib Template engine for PHP5. It has an amazing ability to separate the application code from the presentation layer, separating the manipulation of data (in the database as well as in PHP) from its final format, whether that format is HTML, XML, or a formatted e-mail. The big advantage is that it allows changing the look and feel of a site quickly without having to delve immediately into a lot of PHP variable assignments and print statements. + +## FAQ + +**Q: What is the DotKernel Template Engine based on?** +A: It's an implementation of the PHPLib Template engine for PHP5. + +**Q: What is the main benefit of using it?** +A: It separates application code from the presentation layer, and separates data manipulation from its final format (HTML, XML, or a formatted e-mail), letting you change a site's look and feel quickly without delving into a lot of PHP variable assignments and print statements. diff --git a/public/readme/dotkernel/dotkernel-version-1-0-in-action.md b/public/readme/dotkernel/dotkernel-version-1-0-in-action.md new file mode 100644 index 00000000..007d3a09 --- /dev/null +++ b/public/readme/dotkernel/dotkernel-version-1-0-in-action.md @@ -0,0 +1,48 @@ +--- +title: "DotKernel version 1.0 in action" +description: "An overview of DotKernel 1.0, DotBoost's in-house framework built on Zend Framework, its simplified MVC architecture, and the specific Zend Framework classes it relies on." +author: "admin" +date_published: "2009-10-02" +canonical_url: "https://new.dotkernel.com/dotkernel/dotkernel-version-1-0-in-action/" +category: "Dotkernel" +language: "en" +--- + +# DotKernel version 1.0 in action + +## TL;DR + +DotKernel is DotBoost's in-house developed framework, built on top of Zend Framework and released under the Open Software License (OSL 3.0). It uses a simplified MVC architecture, easy to learn for beginner and intermediate programmers, by eliminating much of Zend Framework's complexity through a different approach to handling web requests. It relies on only a handful of Zend Framework classes. + +## Zend Framework classes DotKernel relies on + +| Class | Purpose | +|---|---| +| `Zend_Config` | Provides multiple backends for configuration storage and a simple, intuitive, object-oriented interface for accessing it. DotKernel stores configuration as simple PHP arrays, wrapped by Zend_Config. | +| `Zend_Db` and `Zend_Db_Table` | A classic implementation of the Table Data Gateway and Row Data Gateway design patterns, for easy and intuitive access to database tables and rows, and an entry point for custom business logic. | +| `Zend_Mail` | Generalized functionality to compose and send text and MIME-compliant multipart e-mail, via the default Zend_Mail_Transport_Sendmail transport or via Zend_Mail_Transport_Smtp. | +| `Zend_Registry` | A container for storing objects and values in the application space, so the same object is always available throughout the application — an alternative to global storage. | +| `Zend_Validate` | Provides a set of commonly needed validators, plus a simple validator chaining mechanism so multiple validators can be applied to a single datum in a user-defined order. | + +## FAQ + +**Q: Who developed DotKernel and what is it built on?** +A: DotKernel is DotBoost's in-house developed framework, built on top of Zend Framework. At the time of this article it was at version 1.0, released under the Open Software License (OSL 3.0). + +**Q: What architecture does DotKernel use?** +A: A simplified MVC (Model-View-Controller) architecture that's easy to learn for beginner and intermediate level programmers, achieved by eliminating much of Zend Framework's complexity through a different approach to handling web requests. + +**Q: What does Zend_Config provide in DotKernel?** +A: It provides multiple backends for configuration storage and a simple, intuitive, object-oriented interface for accessing it. DotKernel stores configuration as simple PHP arrays, which are then wrapped by Zend_Config. + +**Q: What role do Zend_Db and Zend_Db_Table play?** +A: Zend_Db_Table is a classic implementation of the Table Data Gateway and Row Data Gateway design patterns, allowing easy and intuitive access to database tables and rows, as well as an entry point for custom business logic surrounding the data. + +**Q: What are Zend_Mail and Zend_Registry used for?** +A: Zend_Mail composes and sends both text and MIME-compliant multipart e-mail, via the default Zend_Mail_Transport_Sendmail transport or via Zend_Mail_Transport_Smtp. Zend_Registry is a container for storing objects and values in the application space, so the same object is always available throughout the application, as an alternative to using global storage. + +## Resources + +- [DotBoost](http://www.dotboost.com) +- [Open Software License (OSL 3.0)](http://opensource.org/licenses/osl-3.0.php) +- [Zend Framework](http://framework.zend.com/) diff --git a/public/readme/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.md b/public/readme/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.md new file mode 100644 index 00000000..6d1cc8d4 --- /dev/null +++ b/public/readme/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.md @@ -0,0 +1,48 @@ +--- +title: "Forcing UTF8 connections and character set in MySQL" +description: "How to force the MySQL server's character set and collation to UTF8 via my.cnf, so every connecting script uses it regardless of client." +author: "admin" +date_published: "2012-05-09" +canonical_url: "https://new.dotkernel.com/dotkernel/forcing-utf8-connections-and-character-set-in-mysql/" +category: "Dotkernel" +language: "en" +--- + +# Forcing UTF8 connections and character set in MySQL + +## TL;DR + +In some situations it may be necessary to force the MySQL server's collation and character set to UTF8, since you can't control all the scripts connecting to your database (for instance the mysql command line or mysqldump). This is done by editing `my.cnf`. + +## Steps + +1. Open the `my.cnf` file. +2. Add the following lines to force UTF8 for all connections: + +```shell +character_set_server=utf8 +skip-character-set-client-handshake +``` + +3. If you're interested in better performance, also add: + +```shell +collation_server=utf8_general_ci +``` + +4. If you're interested in better sorting order instead, add this line rather than the one above: + +```shell +collation_server=utf8_unicode_ci +``` + +## FAQ + +**Q: Why force UTF8 at the MySQL server level instead of relying on each client?** +A: Because you can't control all the scripts that connect to your database (for instance the mysql command line or mysqldump), so forcing the server's collation and character set to UTF8 in my.cnf guarantees it regardless of the connecting client. + +**Q: What two lines enable UTF8 for all connections in my.cnf?** +A: `character_set_server=utf8` and `skip-character-set-client-handshake`. + +**Q: What's the difference between the two suggested collation settings?** +A: `collation_server=utf8_general_ci` is recommended for better performance, while `collation_server=utf8_unicode_ci` is recommended instead if better sorting order matters more. diff --git a/public/readme/dotkernel/geoip-city-removed-from-dotkernel.md b/public/readme/dotkernel/geoip-city-removed-from-dotkernel.md new file mode 100644 index 00000000..dc44c863 --- /dev/null +++ b/public/readme/dotkernel/geoip-city-removed-from-dotkernel.md @@ -0,0 +1,80 @@ +--- +title: "GeoIP City Removed From DotKernel" +description: "Why the GeoIP City integration was removed from DotKernel due to a segmentation fault with GeoIP extension 1.1.0+, and how to remove the affected code if you don't need GeoIP City." +author: "Gabi DJ" +date_published: "2015-02-25" +canonical_url: "https://new.dotkernel.com/dotkernel/geoip-city-removed-from-dotkernel/" +category: "Dotkernel" +language: "en" +--- + +# GeoIP City Removed From DotKernel + +## TL;DR + +The newest DotKernel version removed the GeoIP City integration because the City database on GeoIP extension version 1.1.0+ was causing a segmentation fault, crashing requests or outputting an error instead of executing the PHP code. Users on an older DotKernel version combined with GeoIP >=1.1.0 may hit this. If you don't need GeoIP City, the affected code can be removed. + +## The error + +This error occurs because of an incompatibility between the GeoIP extension and the database (dat) file. On some operating systems, Apache still responds with HTTP Status Code 200 (OK), but with a page containing this text: + +``` +The server encountered an internal error or +misconfiguration and was unable to complete +your request. +``` + +The error might not be logged, since it's generated by the GeoIP extension itself rather than by your PHP code. + +## How to remove GeoIP City if you don't need it + +1. In `DotKernel/admin/System.php` (the `Admin/System` model), comment or remove the following lines: + +```php +if(geoip_db_avail(GEOIP_CITY_EDITION_REV0)) +{ + $info = explode(" ",geoip_database_info(GEOIP_CITY_EDITION_REV0)); + $return = $info.' '.Dot_Kernel::TimeFormat($info); +} +``` + +2. In `library/Dot/GeoIp.php` (the `Dot_GeoIp` class), comment or remove the following lines: + +```php +if(function_exists('geoip_db_avail') && geoip_db_avail(GEOIP_CITY_EDITION_REV0) +&& 'unknown' == $country) +{ + //if GeoIPCity.dat file exists + $record = geoip_record_by_name($ip); + if(!empty($record)) + { + $countryCode = $record; + $countryName = $record; + $country = $countryCode != false ? $countryCode : 'unknown'; + $country = $countryName != false ? $countryName : 'NA'; + } +} +``` + +These changes were also made in the latest version of DotKernel. Use this fix only if you don't intend to use GeoIP City. + +## Recommendation + +Special coding is recommended for using GeoIP City and the GeoIP extension in general. Note that GeoIP is an obsolete extension — MaxMind recommends using GeoIP2 instead. + +## FAQ + +**Q: Why was GeoIP City removed from DotKernel?** +A: The City database on GeoIP extension version 1.1.0 and above was causing a segmentation fault, which resulted in crashing or outputting an error instead of executing the PHP code. + +**Q: Who is affected by this error?** +A: Users running an older DotKernel version together with GeoIP extension version 1.1.0 or newer, since the error comes from the extension's incompatibility with the database (dat) file. + +**Q: How does the error show up to visitors?** +A: On some operating systems, Apache still responds with HTTP Status Code 200 (OK) but with a page containing a generic "internal error or misconfiguration" message, and the error might not be logged since it's generated by the GeoIP extension rather than by the PHP code. + +**Q: How can you get rid of the error if you don't need GeoIP City?** +A: Comment out or remove the relevant lines in DotKernel/admin/System.php (the Admin/System model) and in library/Dot/GeoIp.php (the Dot_GeoIp class). These changes were also made in the latest version of DotKernel, but this fix should only be used if you don't intend to use GeoIP City. + +**Q: What does the article recommend going forward?** +A: It recommends special coding for GeoIP City and for the GeoIP extension in general, and notes that GeoIP is an obsolete extension, with Maxmind recommending GeoIP2 instead. diff --git a/public/readme/dotkernel/geoip-ip-address-location-in-dotkernel.md b/public/readme/dotkernel/geoip-ip-address-location-in-dotkernel.md new file mode 100644 index 00000000..07605bb4 --- /dev/null +++ b/public/readme/dotkernel/geoip-ip-address-location-in-dotkernel.md @@ -0,0 +1,110 @@ +--- +title: "GeoIP: Ip Address Location In DotKernel" +description: "How DotKernel's getCountryByIp function in library/Dot/Geoip.php uses MaxMind's GeoIP technology and its .dat files to determine a visitor's country." +author: "Teo" +date_published: "2010-10-19" +canonical_url: "https://new.dotkernel.com/dotkernel/geoip-ip-address-location-in-dotkernel/" +category: "Dotkernel" +language: "en" +--- + +# GeoIP: Ip Address Location In DotKernel + +## TL;DR + +GeoIP is MaxMind's proprietary technology for IP geolocation. DotKernel uses it to get user statistics by country, determining a visitor's country, region, city, postal code, or area code in real time. The logic lives in `library/Dot/Geoip.php`, inside the `getCountryByIp` function, which branches over four cases depending on whether the `mod_geoip` PECL extension and its `.dat` files are available. + +## What getCountryByIp does + +The function first reads the session variable used to remember possible error messages, and initializes the country as 'unknown' in case it isn't found. It then runs through four if/else branches: + +1. **`mod_geoip` PECL extension is not installed** — it uses the existing `externals/geoip/GeoIP.dat` file bundled with DotKernel (downloadable from MaxMind if you don't have it). +2. **`mod_geoip` is installed and `GeoIP.dat` exists** (`geoip_db_avail(GEOIP_COUNTRY_EDITION)`) — it uses the built-in PHP functions `geoip_country_code_by_name` and `geoip_country_name_by_name` to get the country code and name. +3. **`mod_geoip` is installed, `GeoIP.dat` doesn't exist, but `GeoIpCity.dat` exists** — it uses the PHP function `geoip_record_by_name` to get the country code and name. +4. **`mod_geoip` is installed, but neither `GeoIP.dat` nor `GeoIPCity.dat` exist** — same behavior as case 1: it uses `externals/geoip/GeoIP.dat` from the DotKernel framework. + +```php +/** + * Get the country by IP + * Return an array with : short name, like 'us' and long name, like 'United States' + * @access public + * @param string $ip + * @return array + */ +public function getCountryByIp($ip) +{ + $session = Zend_Registry::get('session'); + $country = array(0 => 'unknown',1 => 'NA'); + if(extension_loaded('geoip') == FALSE) + { + // GeoIp extension is not active + $api = new Dot_Geoip_Country(); + $geoipPath = 'externals/geoip/GeoIP.dat'; + if(file_exists($geoipPath)) + { + $country = $api->getCountryByAddr($geoipPath, $ip); + } + else + { + $session->message = $this->option->warningMessage->modGeoIp; + $session->message = 'warning'; + } + } + elseif(geoip_db_avail(GEOIP_COUNTRY_EDITION)) + { + //if GeoIP.dat file exists + $country = geoip_country_code_by_name ($ip); + $country = geoip_country_name_by_name($ip); + } + elseif(geoip_db_avail(GEOIP_CITY_EDITION_REV0)) + { + //if GeoIPCity.dat file exists + $record = geoip_record_by_name($ip); + if(!empty($record)) + { + $country = $record; + $country = $record; + } + } + else + { + // GeoIp extension is not active + $api = new Dot_Geoip_Country(); + $geoipPath = 'externals/geoip/GeoIP.dat'; + if(file_exists($geoipPath)) + { + $country = $api->getCountryByAddr($geoipPath, $ip); + } + else + { + $session->message = $this->option->warningMessage->modGeoIp;; + $session->message = 'warning'; + } + } + return $country; +} +``` + +## FAQ + +**Q: What is GeoIP and what does it let DotKernel do?** +A: GeoIP is MaxMind's proprietary technology for IP geolocation data. DotKernel uses it to get user statistics by country, determining a visitor's country, region, city, postal code, or area code in real time. + +**Q: Where does the GeoIP logic live in DotKernel?** +A: In library/Dot/Geoip.php, inside the getCountryByIp function. + +**Q: What does getCountryByIp do when the mod_geoip PECL extension isn't installed?** +A: It falls back to the existing externals/geoip/GeoIP.dat file bundled with DotKernel (downloadable from MaxMind if not present). + +**Q: What happens when mod_geoip is installed and GeoIP.dat exists?** +A: It uses the built-in PHP functions geoip_country_code_by_name and geoip_country_name_by_name to get the country code and name. + +**Q: What if GeoIP.dat is missing but GeoIPCity.dat exists, or neither file exists?** +A: If GeoIPCity.dat exists, it uses the PHP function geoip_record_by_name to get the country code and name. If neither .dat file exists, it behaves the same as when mod_geoip isn't installed, falling back to externals/geoip/GeoIP.dat. + +## Resources + +- [MaxMind's IP geolocation](http://www.maxmind.com/app/ip-location) +- [library/Dot/Geoip.php source](http://websvn.dotkernel.net/filedetails.php?repname=DotKernel&path=%2Ftrunk%2Flibrary%2FDot%2FGeoip.php) +- [GeoLite Country database](http://www.maxmind.com/app/geolitecountry) +- [PHP GeoIP functions](http://php.net/manual/en/book.geoip.php) diff --git a/public/readme/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.md b/public/readme/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.md new file mode 100644 index 00000000..a824d0ee --- /dev/null +++ b/public/readme/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.md @@ -0,0 +1,87 @@ +--- +title: "Handling and Logging errors with dot-errorhandler and dot-log" +description: "A guide to using dotkernel/dot-errorhandler alongside dot-log (or zend-log) to log errors in Zend Expressive applications, covering its two handler types, configuration, and how it was tested." +author: "Gabi DJ" +date_published: "2018-11-27" +canonical_url: "https://new.dotkernel.com/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log/" +category: "Dotkernel" +language: "en" +--- + +# Handling and Logging errors with dot-errorhandler and dot-log + +## TL;DR + +This article is a follow-up to "Logging with dot-log in Zend Expressive and DotKernel" and explains how to use `dotkernel/dot-errorhandler` together with `dotkernel/dot-log` or `zendframework/zend-log` to log errors in Zend Expressive applications. It covers how dot-errorhandler was built, how to configure it, and how it was tested. + +## The two error handlers + +- **The plain `ErrorHandler`** — a copy of Zend Expressive's `Zend\Stratigility\Middleware\ErrorHandler` (copied because that class is `final`). +- **The logging `LogErrorHandler`** — the same as above, but with added logging support via the container. + +Both error handlers have factories registered for easier use with the Container. + +Install it with: + +```shell +composer require dotkernel/dot-errorhandler +``` + +## The Config Provider + +When the dot-errorhandler config provider is invoked, both error handlers' factories are registered, and an alias is added to switch between them. As a fallback, the plain error handler is selected by default, and this can be overwritten through the config file. + +## Configuration + +Important notes: + +- This assumes the project already has a configured logger, as described in the "Logging with dot-log in Zend Expressive and DotKernel" article, with the logger named `default_logger` (as used in the package's config example). +- Although the config key is `dot_log`, the dot-log abstract factory responds to the `dot-log` selector — the container key to ask for is `dot-log.default_logger`. +- dot-errorhandler is meant to be a silent logger for staging and production environments. To test it, development mode should be disabled, otherwise whoops will catch the errors and show them to the developer instead. Any custom error handler can be used as long as it implements the provided `ErrorHandlerInterface`. + +Steps to configure: + +1. Add `Dot\ErrorHandler\ConfigProvider` to the project's `config/config.php` file. +2. Write the error handler config. + +To use the logging error handler, this config is needed, in `config/autoload/dot-errorhandler.global.php`: + +```php +use Dot\ErrorHandler\ErrorHandlerInterface; +use Dot\ErrorHandler\LogErrorHandler; +``` + +The `logger` key in the dot-errorhandler config should reflect your logger configuration in `config/autoload/log.global.php`. An out-of-the-box config was provided within the error handler's config directory for using the default logger. + +## Usage / Triggering errors + +Errors were triggered in the following ways to test the handlers: + +- Throwing Exceptions — the most common case. +- Raising errors, such as triggering warning/error messages, for example: + - dividing numbers by zero (e.g. `16/0`) + - casting arrays to strings (e.g. `$string = 'hello' . $array`) + +## FAQ + +**Q: What two kinds of error handlers does dot-errorhandler provide?** +A: A plain ErrorHandler, which is a copy of Zend Expressive's Zend\Stratigility\Middleware\ErrorHandler (copied because that class is final), and a logging LogErrorHandler, which is the same but with added logging support via the container. Both have factories registered for easier use with the Container. + +**Q: How do you install dot-errorhandler?** +A: Run `composer require dotkernel/dot-errorhandler`. + +**Q: Which error handler is used by default?** +A: The plain error handler is selected by default as a fallback, and this can be overwritten through the config file, which also registers factories for both handlers plus an alias to switch between them. + +**Q: What must already be in place before configuring the logging error handler?** +A: A configured logger as described in the "Logging with dot-log in Zend Expressive and DotKernel" article, with the logger named default_logger as used in the package's config example. + +**Q: Why might whoops interfere when testing dot-errorhandler?** +A: dot-errorhandler is meant to be a silent logger for staging and production environments. To actually test it, development mode should be disabled, otherwise whoops will catch the errors and show them to the developer instead. + +**Q: How were errors triggered to test the error handlers?** +A: By throwing Exceptions, the most common case, and by raising warnings/errors such as dividing a number by zero (e.g. 16/0) or casting an array to a string. + +## Resources + +- [Logging with dot-log in Zend Expressive and DotKernel](https://www.dotkernel.com/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel/) diff --git a/public/readme/dotkernel/migration-of-zend-framework-1-pear-channel.md b/public/readme/dotkernel/migration-of-zend-framework-1-pear-channel.md new file mode 100644 index 00000000..90432efd --- /dev/null +++ b/public/readme/dotkernel/migration-of-zend-framework-1-pear-channel.md @@ -0,0 +1,68 @@ +--- +title: "Migration of Zend Framework 1 PEAR channel" +description: "The unofficial PEAR channel for Zend Framework 1 moved from Google Code to a new dedicated server, with instructions on how to switch to the new channel." +author: "admin" +date_published: "2016-05-11" +canonical_url: "https://new.dotkernel.com/dotkernel/migration-of-zend-framework-1-pear-channel/" +category: "Dotkernel" +language: "en" +--- + +# Migration of Zend Framework 1 PEAR channel + +## TL;DR + +The unofficial PEAR channel for Zend Framework 1 was hosted on Google Code, and once Google Code closed, it had to move. Because the repository is over 1 GB, it could not be migrated to GitHub, so a dedicated server was built to host the PEAR channel long-term at pear.dotkernel.com. + +## Why the move was needed + +- Zend Framework 1 is still used by a lot of projects in production, is still a viable library collection, and runs on PHP7, even though it is only in maintenance/security-patch mode, so cancelling it completely was not an option. +- The team was unable to migrate the project to github.com because the repository is more than 1 GB in size. +- A special server was built to host only the PEAR channel for Zend Framework 1, with a commitment to keep it live and running for the long term. + +## PEAR Channel Migration Guide + +Use the PEAR installer to switch to the new channel: + +1. Remove the installed pear package: + +```shell +pear uninstall zend/zend +``` + +2. Remove the old Google Code channel: + +```shell +pear channel-delete zend.googlecode.com/svn +``` + +3. Discover the new channel: + +```shell +pear channel-discover pear.dotkernel.com/zf1/svn +``` + +4. Install the package: + +```shell +pear install zend/zend +``` + +## FAQ + +**Q: Why did the PEAR channel for Zend Framework 1 need to move?** +A: The unofficial PEAR channel for Zend Framework 1 was hosted on Google Code, and once Google Code was closed the channel was forced to move to a new location. + +**Q: Why wasn't the project moved to GitHub instead?** +A: The team was unable to migrate the project to github.com because the repository is more than 1 GB in size, so they built a special server dedicated to hosting only the PEAR channel for Zend Framework 1. + +**Q: Is Zend Framework 1 still worth using?** +A: Zend Framework 1 is still used by a lot of projects in production, is still a viable library collection, and runs on PHP7, even though it is only in maintenance/security-patch mode. + +**Q: How do I switch to the new PEAR channel?** +A: Using the PEAR installer: remove the installed package with `pear uninstall zend/zend`, remove the old Google Code channel with `pear channel-delete zend.googlecode.com/svn`, discover the new channel with `pear channel-discover pear.dotkernel.com/zf1/svn`, and then reinstall the package with `pear install zend/zend`. + +## Resources + +- [PEAR channel for Zend Framework 1](http://pear.dotkernel.com/) +- [@dotkernel on Twitter](https://twitter.com/dotkernel) diff --git a/public/readme/dotkernel/new-features-in-zend-framework-1-12.md b/public/readme/dotkernel/new-features-in-zend-framework-1-12.md new file mode 100644 index 00000000..dbcf7c47 --- /dev/null +++ b/public/readme/dotkernel/new-features-in-zend-framework-1-12.md @@ -0,0 +1,58 @@ +--- +title: "New Features in Zend Framework 1.12" +description: "Overview of Zend Framework 1.12.0RC1, covering new components back ported from ZF2, the removal of the WurflApi adapter, and over 200 bug fixes." +author: "admin" +date_published: "2012-06-18" +canonical_url: "https://new.dotkernel.com/dotkernel/new-features-in-zend-framework-1-12/" +category: "Dotkernel" +language: "en" +--- + +# New Features in Zend Framework 1.12 + +## TL;DR + +Per Matthew Weier O'Phinney's announcement, the Zend Framework team made available the first release candidate of the Zend Framework 1.12 series, 1.12.0RC1. It back ports several ZF2 components to ZF1, removes the WurflApi adapter due to licensing changes, and fixes over 200 reported issues. + +## New Features + +| Feature | Description | +|---|---| +| Zend_Loader changes | A number of autoloaders and autoloader facilities were back ported from ZF2, including `Zend_Loader_StandardAutoloader` (improves on `Zend_Loader_Autoloader` by allowing a specific path to be associated with a vendor prefix or namespace), `Zend_Loader_ClassMapAutoloader` (lookup-table based autoloading, typically the fastest method), and `Zend_Loader_AutoloaderFactory` (can create and update autoloaders and register them with `spl_autoload_register()`). Back ported by Matthew Weier O'Phinney. | +| Zend_EventManager | A component that lets you attach and detach listeners to named events, per-instance or via shared collections, trigger events, and interrupt execution of listeners. Back ported by Matthew Weier O'Phinney. | +| Zend_Http_UserAgent_Features_Adapter_Browscap | A features adapter that calls `get_browser()` to discover mobile device capabilities for injection into UserAgent device instances, relying on the Browscap project's `php_browscap.ini` file. Created by Matthew Weier O'Phinney. | +| Zend_Mobile_Push | A component for implementing push notifications across the three major platforms: Apple (APNs), Google (C2DM), and Microsoft (MPNS). Contributed by Mike Willbanks. | +| Zend_Gdata_Analytics | An extension to Zend_Gdata for interacting with Google's Analytics Data Export API; does not change the overall operation of Zend_Gdata components. Contributed by Daniel Hartmann. | + +## Removed features + +- `Zend_Http_UserAgent_Features_Adapter_WurflApi` was removed due to changes in WURFL's licensing (announced previously). The team planned to provide the WurflApi adapter directly to ScientiaMobile so WURFL users would still have that option. + +## Bug Fixes + +Over 200 reported issues in the tracker were fixed, with particular thanks to Adam Lundrigan, Frank Brückner and Martin Hujer, as well as everyone who ran the ZF1 unit tests and reported results. + +## FAQ + +**Q: What was announced for Zend Framework 1.12?** +A: Matthew Weier O'Phinney announced the immediate availability of the first release candidate of the Zend Framework 1.12 series, 1.12.0RC1. + +**Q: What changed in Zend_Loader?** +A: A number of autoloaders and autoloader facilities were back ported from ZF2, including Zend_Loader_StandardAutoloader, Zend_Loader_ClassMapAutoloader, and Zend_Loader_AutoloaderFactory, providing performant alternatives to the autoloading facilities already in the 1.X releases. + +**Q: What is Zend_EventManager?** +A: Zend_EventManager is a component, also back ported from ZF2, that lets you attach and detach listeners to named events (per-instance or via shared collections), trigger events, and interrupt execution of listeners. + +**Q: Why was the WurflApi adapter removed?** +A: Zend_Http_UserAgent_Features_Adapter_WurflApi was removed due to changes in the licensing of WURFL. The team planned to provide the WurflApi adapter to ScientiaMobile so WURFL users would still have that option. + +**Q: How many bugs were fixed in this release?** +A: Over 200 reported issues in the tracker were fixed, with particular thanks credited to Adam Lundrigan, Frank Brückner and Martin Hujer, as well as everyone who ran the ZF1 unit tests and reported results. + +## Resources + +- [Matthew Weier O'Phinney's announcement](http://devzone.zend.com/2366/zend-framework-1-12-series-1-12-0rc1-now-available/) +- [Browscap project](http://browsers.garykeith.com/) +- [php_browscap.ini download](http://browsers.garykeith.com/stream.asp?PHP_BrowsCapINI) +- [Prior WurflApi removal announcement](http://www.dotkernel.com/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter/) +- [Complete issue tracker list](http://framework.zend.com/issues/secure/IssueNavigator.jspa?requestId=12877) diff --git a/public/readme/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.md b/public/readme/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.md new file mode 100644 index 00000000..a1f2041d --- /dev/null +++ b/public/readme/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.md @@ -0,0 +1,30 @@ +--- +title: "PHP Formatter and Templates for Zend Studio 10.1" +description: "An updated PHP Formatter plugin and a DotKernel coding standard templates file for Zend Studio 10.1, along with the required Formatter plugin change." +author: "admin" +date_published: "2013-07-02" +canonical_url: "https://new.dotkernel.com/dotkernel/php-formatter-and-templates-for-zend-studio-10-1/" +category: "Dotkernel" +language: "en" +--- + +# PHP Formatter and Templates for Zend Studio 10.1 + +Because Zend donated the Zend Studio Formatter upstream to the PDT project, the Formatter plugin for Zend Studio 10.1 needs to be changed: replace "com.zend.php" with "org.eclipse.php". The updated file is available as the DotKernel PHP Formatter for Zend Studio 10.1. + +DotKernel also created a templates file for Zend Studio 10.1, built according to the DotKernel coding standard. + +## FAQ + +**Q: Why does the PHP Formatter plugin for Zend Studio 10.1 need to be changed?** +A: Because Zend donated the Zend Studio Formatter upstream to the PDT project, the Formatter plugin needs to be changed by replacing "com.zend.php" with "org.eclipse.php". + +**Q: Where can I get DotKernel's updated Formatter and Templates files for Zend Studio 10.1?** +A: The updated PHP Formatter file for Zend Studio 10.1 and a templates file built according to the DotKernel coding standard are both available as downloads linked in the article. + +## Resources + +- [DotKernel PHP Formatter for Zend Studio 10.1](http://www.dotkernel.com/download/?did=37) +- [About PHP Formatter (Zend Studio docs)](http://files.zend.com/help/Zend-Studio-10/zend-studio.htm#formatter.htm) +- [Templates for Zend Studio](http://www.dotkernel.com/download/?did=38) +- [About Templates in Zend Studio and how to import/export (Zend Studio docs)](http://files.zend.com/help/Zend-Studio-10/zend-studio.htm#templates.htm) diff --git a/public/readme/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.md b/public/readme/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.md new file mode 100644 index 00000000..0909747c --- /dev/null +++ b/public/readme/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.md @@ -0,0 +1,40 @@ +--- +title: "Protecting admin folder with .htaccess in Plesk" +description: "How to protect the /admin folder with HTTP Basic Auth in a Plesk vhost.conf file, and how to apply the change and finish setup with .htpasswd." +author: "admin" +date_published: "2011-06-15" +canonical_url: "https://new.dotkernel.com/dotkernel/protecting-admin-folder-with-htaccess-in-plesk/" +category: "Dotkernel" +language: "en" +--- + +# Protecting admin folder with .htaccess in Plesk + +## Steps + +1. In `/var/www/vhosts/exampledomain.com/conf/vhost.conf`, add a Location block for /admin: + +```shell + + AuthType Basic + AuthName "My Site Admin" + AuthUserFile /var/www/vhosts/exampledomain.com/conf/.htpasswd + Require valid-user + +``` + +2. Notify the server that vhost.conf has been added/changed: + +```shell +/usr/local/psa/admin/sbin/websrvmng -a +``` + +3. Create the `.htpasswd` file and upload it to `/var/www/vhosts/exampledomain.com/conf/`. + +## FAQ + +**Q: How do I protect the /admin folder with .htaccess in Plesk?** +A: Add a Location block for /admin to the vhost.conf file (e.g. /var/www/vhosts/exampledomain.com/conf/vhost.conf) using AuthType Basic, an AuthName, an AuthUserFile pointing to a .htpasswd file, and Require valid-user. + +**Q: After editing vhost.conf, how do I apply the change and finish setup in Plesk?** +A: Notify the server that vhost.conf has changed by running `/usr/local/psa/admin/sbin/websrvmng -a`, then create the .htpasswd file and upload it to the same conf directory referenced in the AuthUserFile directive. diff --git a/public/readme/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.md b/public/readme/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.md new file mode 100644 index 00000000..67a993e8 --- /dev/null +++ b/public/readme/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.md @@ -0,0 +1,98 @@ +--- +title: "Replacing laminas-mail with Symfony mailer in dot-mail" +description: "Why and how Dotkernel replaced the abandoned laminas/laminas-mail package with symfony/mailer inside dotkernel/dot-mail, including configuration changes and the upgrade path to version 5." +author: "Florin Bidirean" +date_published: "2025-01-08" +canonical_url: "https://new.dotkernel.com/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail/" +category: "Dotkernel" +language: "en" +--- + +# Replacing laminas-mail with Symfony mailer in dot-mail + +## TL;DR + +The Laminas Technical Steering Committee decided on 2023-12-04 to abandon laminas/laminas-mail. Dotkernel responded by replacing it with symfony/mailer inside the dotkernel/dot-mail package (version 5), aiming for minimal impact on existing projects — calls to send mail stay the same, though mime and imap related functionality is removed. + +## What prompted the change + +According to the Laminas Technical Steering Committee minutes of 2023-12-04, laminas/laminas-mail was going to be abandoned: nobody was available to maintain it, and alternatives already existed in the ecosystem: + +| Purpose | Package | +|---|---| +| Interacting with IMAP | [ddeboer/imap](https://github.com/ddeboer/imap) | +| Parsing MIME messages | [zbateson/mail-mime-parser](https://github.com/zbateson/mail-mime-parser) | +| Sending mail | [symfony/mailer](https://github.com/symfony/mailer) | + +## How Dotkernel handles the issue + +The Dotkernel team opted to replace laminas/laminas-mail in the [dotkernel/dot-mail](https://github.com/dotkernel/dot-mail) package. This meant revising the code, including configuration files, while trying to have minimal impact on existing projects: the calls to send mail remain the same, even though some lesser-used functionality like mime and imap is lost. + +## Technical approach + +The switch from laminas/laminas-mail to symfony/mailer in dotkernel/dot-mail is covered across these pull requests: + +- [dot-mail PR 65](https://github.com/dotkernel/dot-mail/pull/65/files) +- [dot-mail PR 66](https://github.com/dotkernel/dot-mail/pull/66/files) +- [dot-mail PR 67](https://github.com/dotkernel/dot-mail/pull/67/files) +- [dot-mail PR 69](https://github.com/dotkernel/dot-mail/pull/69/files) + +> Function definition changes are not covered in the article. + +The `mail.global.php` configuration file was revised to remove features that are no longer available and to make it easier to configure. The mail transport can be any class implementing `Symfony\Component\Mailer\Transport\TransportInterface`; standard aliases are `sendmail` (`Symfony\Component\Mailer\Transport\SendmailTransport`) and `esmtp` (`Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport`), defaulting to `sendmail`. `smtp_options` are used only when the esmtp adapter is used, and there is a `log` option to log sent emails. + +Use ONE of the below transporters, based on your server configuration: + +```php +'transport' => 'sendmail', +``` + +OR + +```php +'transport' => 'esmtp', +``` + +Sendmail is set as the default mail transport. + +## How to update dotkernel/dot-mail from version 3 or 4 to version 5 + +1. Download the new [mail configuration file](https://github.com/dotkernel/dot-mail/blob/5.0/config/mail.global.php.dist). +2. Add the values you configured for your project, focusing on `transport`, `message_options` and `smtp_options`, then replace your old configuration file. +3. In your `composer.json`, update to `"dotkernel/dot-mail": "^5.0.0"` and run `composer update`. + +At this point, `mime` and `imap` related functionality is removed. + +## FAQ + +**Q: Why was laminas/laminas-mail replaced?** +A: The Laminas Technical Steering Committee decided on 2023-12-04 that laminas/laminas-mail would be abandoned because there was nobody to maintain it, and several alternatives were already available in the ecosystem. + +**Q: What alternatives were identified for the abandoned laminas-mail package?** +A: The alternatives mentioned are ddeboer/imap for interacting with IMAP, zbateson/mail-mime-parser for parsing MIME messages, and symfony/mailer for sending mail. + +**Q: How did Dotkernel handle the removal of laminas-mail in dot-mail?** +A: The Dotkernel team replaced laminas/laminas-mail with symfony/mailer inside the dotkernel/dot-mail package, revising the code and configuration files while aiming for minimal impact on existing projects, so the calls used to send mail stay the same, even though some lesser-used functionality like mime and imap is lost. + +**Q: Which mail transport does dot-mail use by default?** +A: The revised mail.global.php configuration defaults the transport to sendmail. You should use ONE of the transporters, either 'transport' => 'sendmail' or 'transport' => 'esmtp', based on your server configuration; Sendmail was set as the default. + +**Q: How do I update dotkernel/dot-mail from version 3 or 4 to version 5?** +A: Download the new mail.global.php.dist configuration file, add the values you configured for your project (focusing on transport, message_options and smtp_options) to replace your old configuration file, then update "dotkernel/dot-mail" to "^5.0.0" in composer.json and run composer update. + +**Q: What functionality is lost after switching to symfony/mailer?** +A: At the time of the article, mime and imap related functionality is removed from dot-mail as a result of the switch. + +## Resources + +- [Laminas Technical Steering Committee minutes, 2023-12-04](https://github.com/laminas/technical-steering-committee/blob/main/meetings/minutes/2023-12-04-TSC-Minutes.md#maintainers-for-laminas-mime-and-laminas-mail) +- [laminas/laminas-mail](https://github.com/laminas/laminas-mail) +- [ddeboer/imap](https://github.com/ddeboer/imap) +- [zbateson/mail-mime-parser](https://github.com/zbateson/mail-mime-parser) +- [symfony/mailer](https://github.com/symfony/mailer) +- [dotkernel/dot-mail](https://github.com/dotkernel/dot-mail) +- [dot-mail PR 65](https://github.com/dotkernel/dot-mail/pull/65/files) +- [dot-mail PR 66](https://github.com/dotkernel/dot-mail/pull/66/files) +- [dot-mail PR 67](https://github.com/dotkernel/dot-mail/pull/67/files) +- [dot-mail PR 69](https://github.com/dotkernel/dot-mail/pull/69/files) +- [New mail.global.php.dist configuration file](https://github.com/dotkernel/dot-mail/blob/5.0/config/mail.global.php.dist) diff --git a/public/readme/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.md b/public/readme/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.md new file mode 100644 index 00000000..23f64bd9 --- /dev/null +++ b/public/readme/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.md @@ -0,0 +1,48 @@ +--- +title: "Scientia Mobile licensed its Wurfl Cloud PHP library to DotKernel 1.6" +description: "DotKernel 1.6.0 removed the obsolete GPL'ed WURFL PHP library and replaced it with Scientia Mobile's WURFL Cloud PHP library, under a special license limited to use with the Dotkernel framework." +author: "admin" +date_published: "2012-05-25" +canonical_url: "https://new.dotkernel.com/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6/" +category: "Dotkernel" +language: "en" +--- + +# Scientia Mobile licensed its Wurfl Cloud PHP library to DotKernel 1.6 + +## TL;DR + +In DotKernel 1.6.0, released on May 16th, 2012, the GPL'ed WURFL PHP library was removed because its code was obsolete and the XML file structure had changed. It was replaced by Scientia Mobile's WURFL Cloud PHP library, made available to DotKernel under a special, restrictive license. + +## What changed + +- The GPL'ed WURFL PHP library was removed in DotKernel 1.6.0 (released May 16th, 2012) because the code was obsolete and the structure of the XML file had changed. +- Scientia Mobile's WURFL Cloud PHP library was implemented instead. +- A future plan mentioned in the article was to create a template in the Wurfl Cloud API control panel at Scientia Mobile, ready to be used in DotKernel implementations. +- Instructions on signing up for a free account and setting up an API key are covered in a separate article, "Detecting Mobile Devices in DotKernel 1.6". + +## License terms + +DotKernel received a special license for the WURFL Cloud PHP library, documented in `dotkernel/library/WurflCloud/LICENSE.txt`: + +> License is non-transferable, conditioned, limited to the version received and to the use and distribution of this software only in connection with the Dotkernel software framework, and does not allow making derivative works even when the source code is provided or the language is an interpreted one and regardless of whether the code is not obfuscated. + +## FAQ + +**Q: Why was the WURFL PHP library removed from DotKernel 1.6.0?** +A: In DotKernel 1.6.0, released on May 16th, 2012, the GPL'ed WURFL PHP library was removed because the code was obsolete anyway and the structure of the XML file had changed. + +**Q: What replaced the old WURFL PHP library?** +A: DotKernel implemented Scientia Mobile's WURFL Cloud PHP library instead. + +**Q: Where can I find instructions to set up the WURFL Cloud API key?** +A: Instructions on how to sign up for a free account and set up your API key are linked in the article, pointing to the "Detecting Mobile Devices in DotKernel 1.6" article. + +**Q: What are the terms of DotKernel's special WURFL Cloud license?** +A: The license is non-transferable, conditioned, limited to the version received, and restricted to use and distribution only in connection with the Dotkernel software framework. It does not allow making derivative works, even when source code is provided or the code is not obfuscated. + +## Resources + +- [Scientia Mobile](http://www.scientiamobile.com/) +- [Detecting Mobile Devices in DotKernel 1.6](http://www.dotkernel.com/dotkernel/detecting-mobile-devices-in-dotkernel-1-6/) +- [dotkernel/library/WurflCloud/LICENSE.txt](http://websvn.dotkernel.net/filedetails.php?repname=DotKernel&path=%2Ftrunk%2Flibrary%2FWurflCloud%2FLICENSE.txt) diff --git a/public/readme/dotkernel/sending-emails-using-dot-email-component-and-zend-email.md b/public/readme/dotkernel/sending-emails-using-dot-email-component-and-zend-email.md new file mode 100644 index 00000000..1debd09b --- /dev/null +++ b/public/readme/dotkernel/sending-emails-using-dot-email-component-and-zend-email.md @@ -0,0 +1,83 @@ +--- +title: "Sending emails using Dot_Email component and Zend_Email" +description: "Dot_Email extends Zend_Mail with two additional methods, setContent() and send(), and this article covers how to use it and which Zend_Mail methods are inherited." +author: "admin" +date_published: "2010-04-28" +canonical_url: "https://new.dotkernel.com/dotkernel/sending-emails-using-dot-email-component-and-zend-email/" +category: "Dotkernel" +language: "en" +--- + +# Sending emails using Dot_Email component and Zend_Email + +## TL;DR + +Dot_Email extends Zend_Mail, so all of Zend_Mail's methods are available in it. Beyond its constructor, Dot_Email itself adds only two methods: setContent() and send(). To send an email you must always call addTo(), setSubject(), one of setBodyText()/setBodyHtml()/setContent(), and finally send(). + +## Dot_Email overview + +Dot_Email is a simple class composed of only two methods (besides the constructor); every other method is inherited from Zend_Mail. + +| Method | Description | +|---|---| +| `setContent($content, $format = 'text/plain')` | Sets the body of the email by calling `setBodyText()` if format is "text/plain", or `setBodyHtml()` if format is "text/html". | +| `send()` | Sets the transporter and calls the parent's `send()` method to send the email. | + +## Sending an email + +For sending the email you MUST use these methods: + +```php +$dotEmail = new Dot_Email(); +$dotEmail->addTo($email); +$dotEmail->setSubject('Forgot Password'); +$dotEmail->setBodyText('Your password is '.$password); +$succeed = $dotEmail->send(); +if($succeed) +{ + echo "Message sent!"; +} +else +{ + echo "Message failed, not sent!"; +} +``` + +Only if you want to overwrite the default Email and Name, add: + +```php +$dotEmail->setFrom($fromEmail, $fromName); +$dotEmail->setReplyTo($fromEmail, $fromName); +``` + +**NOTE:** you always have to use addTo(), setSubject(), setBodyText() or setBodyHtml() or setContent(), and finally the send() method. + +## Methods inherited from Zend_Mail + +1. `setBodyText($txt, $charset=null, $encoding=Zend_Mime::ENCODING_QUOTEDPRINTABLE)` +2. `setBodyHtml($html, $charset=null, $encoding=Zend_Mime::ENCODING_QUOTEDPRINTABLE)` +3. `addTo($email, $name='')` +4. `addCc($email, $name='')` +5. `addBcc($email)` +6. `setFrom($email, $name = null)` +7. `setReplyTo($email, $name = null)` +8. `setReturnPath($email)` +9. `setSubject($subject)` +10. `addHeader($name, $value, $append = false)` + +## FAQ + +**Q: What is Dot_Email?** +A: Dot_Email is a class that extends Zend_Mail, so all methods from Zend_Mail are available in it. Aside from its constructor, Dot_Email itself is composed of only two methods, with everything else inherited from Zend_Mail. + +**Q: What are the two methods that Dot_Email adds on top of Zend_Mail?** +A: setContent($content, $format = 'text/plain') sets the body of the email by calling setBodyText() when the format is "text/plain" or setBodyHtml() when the format is "text/html". send() sets the transporter and calls the parent::send() method to send the email. + +**Q: What is the minimal sequence of calls needed to send an email with Dot_Email?** +A: You must always use addTo(), setSubject(), and setBodyText() or setBodyHtml() or setContent(), and finally call send(), as shown in the article's example that creates a new Dot_Email, adds a recipient, sets the subject and body text, then sends it and checks the returned success value. + +**Q: How do I override the default From and Reply-To addresses?** +A: Only if you want to overwrite the default Email and Name, add calls to setFrom($fromEmail, $fromName) and setReplyTo($fromEmail, $fromName). + +**Q: Which Zend_Mail methods are inherited and usable through Dot_Email?** +A: Inherited methods listed in the article include setBodyText(), setBodyHtml(), addTo(), addCc(), addBcc(), setFrom(), setReplyTo(), setReturnPath(), setSubject(), and addHeader(). diff --git a/public/readme/dotkernel/templating-in-dotkernel3.md b/public/readme/dotkernel/templating-in-dotkernel3.md new file mode 100644 index 00000000..63053b73 --- /dev/null +++ b/public/readme/dotkernel/templating-in-dotkernel3.md @@ -0,0 +1,49 @@ +--- +title: "Templating in DotKernel3" +description: "DotKernel3 replaces its 10-year-old templating engine with Twig, gaining layouts, loops, variables, and escaping while keeping the familiarity of HTML." +author: "Jesper" +date_published: "2017-12-20" +canonical_url: "https://new.dotkernel.com/dotkernel/templating-in-dotkernel3/" +category: "Dotkernel" +language: "en" +--- + +# Templating in DotKernel3 + +## TL;DR + +DotKernel3 moved from its previous, 10-year-old templating engine to the popular Twig Templating Engine, gaining layouts, loops, variables, and escaping, while giving developers the familiarity of HTML with the overview and convenience of PHP. + +## Why the switch was made + +One of the pain points in the previous version of DotKernel was the templating engine. Although it was solid and robust, it was also 10 years old and used techniques that had become slightly outdated. + +## What Twig brings to DotKernel3 + +Templates can now be written as HTML code with extra Twig tags. Twig is powerful and includes features such as: + +- Layouts +- Loops +- Variables +- Escaping + +The templating engine in DotKernel3 gives the familiarity of HTML with the overview and convenience of PHP. + +## FAQ + +**Q: Which templating engine does DotKernel3 use?** +A: DotKernel3 moved to the popular Twig Templating Engine. + +**Q: Why did DotKernel move away from the previous templating engine?** +A: The previous templating engine, although solid and robust, was also 10 years old and used techniques that had become slightly outdated, and it was one of the pain points in the previous version of DotKernel. + +**Q: What features does Twig bring to DotKernel3 templates?** +A: Templates can now be written as HTML code with extra Twig tags, and Twig includes features such as layouts, loops, variables, and escaping, giving the familiarity of HTML with the overview and convenience of PHP. + +**Q: Where can I read the official Twig templating documentation for DotKernel?** +A: The official documentation is linked in the article at docs.dotkernel.com/Prerequisites/Templates. + +## Resources + +- [Twig Templating Engine](https://twig.symfony.com/) +- [DotKernel Templates documentation](https://docs.dotkernel.com/Prerequisites/Templates) diff --git a/public/readme/dotkernel/using-dotkernel-with-composer-dependencies.md b/public/readme/dotkernel/using-dotkernel-with-composer-dependencies.md new file mode 100644 index 00000000..a30e0198 --- /dev/null +++ b/public/readme/dotkernel/using-dotkernel-with-composer-dependencies.md @@ -0,0 +1,123 @@ +--- +title: "Using DotKernel with Composer Dependencies" +description: "How to use external Composer dependencies in DotKernel, demonstrated by rendering a barcode with both the non-namespaced Zend Framework 1 and the namespaced Zend Framework 2." +author: "Gabi DJ" +date_published: "2016-04-04" +canonical_url: "https://new.dotkernel.com/dotkernel/using-dotkernel-with-composer-dependencies/" +category: "Dotkernel" +language: "en" +--- + +# Using DotKernel with Composer Dependencies + +## TL;DR + +This article covers using external dependencies via Composer within DotKernel applications. Composer autoloads dependencies automatically, so there is no need to include/require them. The example renders a Barcode using Zend Framework 1 (non-namespaced) and Zend Framework 2 (namespaced), and applies to any DotKernel 1.x version running PHP greater than 5.4.0. + +## The Composer Dependencies + +The ZendFramework 1 Barcode module can only be loaded with ZF1 itself: + +```shell +composer require 'zendframework/zendframework1' +``` + +The ZendFramework 2 Barcode module can be loaded separately: + +```shell +composer require 'zendframework/zend-barcode' +``` + +## Important note + +These dependencies can be used anywhere after the `Dot_Kernel::initialize()` function was called. + +## Using Non-Namespaced Dependencies (Zend Framework 1) + +The class is loaded [PSR-0](http://www.php-fig.org/psr/psr-0/) style, meaning the class name looks like `VendorName_PackageName_ClassName`: + +```php +// Only the text to draw is required +$barcodeOptions = array('text' => 'ZEND-FRAMEWORK'); + +// No required options +$rendererOptions = array(); + +// Draw the barcode in a new image, +// send the headers and the image +Zend_Barcode::factory( + 'code39', 'image', $barcodeOptions, $rendererOptions +)->render(); +``` + +## Using Namespaced Dependencies (Zend Framework 2) + +The class is loaded [PSR-4](http://www.php-fig.org/psr/psr-4/) style, meaning the class name looks like `\VendorName\PackageName\ClassName`: + +```php +use Zend\Barcode\Barcode; + +// Only the text to draw is required +$barcodeOptions = array('text' => 'ZEND-FRAMEWORK'); + +// No required options +$rendererOptions = array(); + +// Draw the barcode in a new image, +// send the headers and the image +Barcode::factory( + 'code39', 'image', $barcodeOptions, $rendererOptions +)->render(); +``` + +### The result + +Both examples render the same barcode. + +### Tip + +The first (ZF1-style) example will work for both namespaced and non-namespaced dependencies if you add this as the first line: + +```php +use Zend\Barcode\Barcode as Zend_Barcode; +``` + +You can then use any of the following to access ZF2's Barcode module: + +- `Zend\Barcode\Barcode` +- `Barcode` +- `Zend_Barcode` + +## Compatibility + +This article works for any DotKernel 1.x version if your server is running PHP greater than 5.4.0. + +## FAQ + +**Q: Do I need to manually include or require Composer dependencies in DotKernel?** +A: No. Composer automatically loads dependencies, so there is no need to include or require them yourself. + +**Q: What example does the article use to demonstrate Composer dependencies?** +A: The article renders a Barcode using Zend Framework 1 as the Non-Namespaced dependency (installed with `composer require 'zendframework/zendframework1'`) and Zend Framework 2 as the Namespaced dependency (installed with `composer require 'zendframework/zend-barcode'`). + +**Q: When can these Composer dependencies be used in the application?** +A: These dependencies can be used anywhere after the Dot_Kernel::initialize() function has been called. + +**Q: How are non-namespaced (Zend Framework 1) classes loaded compared to namespaced (Zend Framework 2) classes?** +A: Non-namespaced ZF1 classes are loaded PSR-0 style, meaning the class name looks like VendorName_PackageName_ClassName (e.g. Zend_Barcode). Namespaced ZF2 classes are loaded PSR-4 style, meaning the class name looks like \VendorName\PackageName\ClassName (e.g. Zend\Barcode\Barcode). + +**Q: Can the same code work for both namespaced and non-namespaced barcode dependencies?** +A: Yes. If you add "use Zend\Barcode\Barcode as Zend_Barcode;" as the first line, the ZF1-style example will work for both, and you can then reference the module as Zend\Barcode\Barcode, Barcode, or Zend_Barcode. + +**Q: What DotKernel and PHP versions does this article apply to?** +A: This article works for any DotKernel 1.x version if your server is running PHP greater than 5.4.0. + +## Resources + +- [Adding Composer support in your DotKernel project](http://www.dotkernel.com/dotkernel/adding-composer-support-in-your-dotkernel-project) +- [Zend Framework 1 manual](http://framework.zend.com/manual/1.12/en/manual.html) +- [Zend Framework 2 manual](http://framework.zend.com/manual/current/en/index.html) +- [PSR-0](http://www.php-fig.org/psr/psr-0/) +- [PSR-4](http://www.php-fig.org/psr/psr-4/) +- [Zend Framework 1 - Rendering a barcode](http://framework.zend.com/manual/1.12/en/zend.barcode.creation.html#zend.barcode.creation.renderering) +- [Zend Framework 2 - Rendering a barcode](http://framework.zend.com/manual/current/en/modules/zend.barcode.creation.html#rendering-a-barcode) diff --git a/public/readme/dotkernel/using-utf8-charset-in-dotkernel.md b/public/readme/dotkernel/using-utf8-charset-in-dotkernel.md new file mode 100644 index 00000000..b2913ca9 --- /dev/null +++ b/public/readme/dotkernel/using-utf8-charset-in-dotkernel.md @@ -0,0 +1,56 @@ +--- +title: "Using UTF8 charset in DotKernel" +description: "How to enable UTF8 encoding in a DotKernel-based system, covering both database collation and the application.ini charset setting." +author: "admin" +date_published: "2012-04-07" +canonical_url: "https://new.dotkernel.com/dotkernel/using-utf8-charset-in-dotkernel/" +category: "Dotkernel" +language: "en" +--- + +# Using UTF8 charset in DotKernel + +## TL;DR + +To use UTF8 encoding in a DotKernel-based system, changes are needed in both the database structure and the application.ini file. These changes were committed into the DotKernel 1.6.0 dev codebase. + +## Database + +Set a proper collation for all tables and columns, either **utf8_general_ci** or **utf8_bin**. + +## application.ini + +Add the following line to your application.ini file, in the database area: + +```ini +database.params.charset = utf8 +``` + +## Differences between utf8_general_ci and utf8_bin + +| Collation | Behavior | +|---|---| +| utf8_bin | Compares strings by the binary value of each character in the string | +| utf8_general_ci | Compares strings using general language rules and case-insensitive comparisons | + +For example, the following evaluates as true with utf8_general_ci collation, but **not** with utf8_bin collation: + +- Ä = A +- Ö = O +- Ü = U + +These differences only happen at the **MySQL** level (for instance, in queries using the LIKE operator) and **not** at the **PHP** level (for instance, in `str_replace()` calls). + +## FAQ + +**Q: What needs to change to use UTF8 encoding in a DotKernel-based system?** +A: Both the database structure and the application.ini file need changes: all tables and columns must have a proper UTF8 collation (utf8_general_ci or utf8_bin), and application.ini must load the utf8 charset. + +**Q: What line should I add to application.ini to enable UTF8?** +A: Add the line `database.params.charset = utf8` to your application.ini file. + +**Q: What is the difference between utf8_general_ci and utf8_bin collation?** +A: utf8_bin compares strings by the binary value of each character, while utf8_general_ci compares strings using general language rules with case-insensitive comparisons. For example, Ä = A, Ö = O, and Ü = U evaluate as true under utf8_general_ci but not under utf8_bin. + +**Q: Does the collation choice affect PHP string functions too?** +A: No. These collation differences only happen at the MySQL level, for instance in queries using the LIKE operator, and not at the PHP level, for instance in str_replace() calls. diff --git a/public/readme/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.md b/public/readme/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.md new file mode 100644 index 00000000..729625ff --- /dev/null +++ b/public/readme/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.md @@ -0,0 +1,52 @@ +--- +title: "Wurfl Cloud Integration in DotKernel 1.6.0" +description: "How WURFL Cloud, WURFL's cloud-based device detection service, was integrated as the default mobile detection method in DotKernel 1.6.0." +author: "deddu" +date_published: "2012-05-18" +canonical_url: "https://new.dotkernel.com/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0/" +category: "Dotkernel" +language: "en" +--- + +# Wurfl Cloud Integration in DotKernel 1.6.0 + +## TL;DR + +DotKernel 1.6.0 integrates Wurfl Cloud, WURFL's (Wireless Universal Resource FiLe) new cloud-based way of delivering device detection services, as its default method for detecting mobile devices. + +## Where to find it + +The Wurfl Cloud library can be found in the library folder of DotKernel. + +## Quick steps to a functional device detection setup + +1. Register for a Wurfl Cloud account [here](https://www.scientiamobile.com/register). +2. Choose **device_os** and **mobile_browser** as your capabilities. +3. Copy your API key into application.ini in DotKernel: + +```ini +resources.useragent.wurflcloud.api_key = 000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +``` + +## Testing it + +Change the user agent in your browser to a mobile device and access your project. You should be redirected to the mobile module. + +## FAQ + +**Q: What is WURFL Cloud?** +A: WURFL Cloud is a new way that WURFL (Wireless Universal Resource FiLe) delivers its device detection services. DotKernel integrated it as the default detection method for mobile devices starting with version 1.6.0. + +**Q: How do I get a functional device detection setup with WURFL Cloud?** +A: Register for a Wurfl Cloud account with Scientia Mobile, choose device_os and mobile_browser as your capabilities, then copy your API key into application.ini in DotKernel. + +**Q: What line do I add to application.ini for the API key?** +A: Add a line such as `resources.useragent.wurflcloud.api_key = 000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`, using your own API key. + +**Q: How can I test that mobile detection is working?** +A: Change the user agent in your browser to a mobile device and access your project; you should be redirected to the mobile module. + +## Resources + +- Register for a Wurfl Cloud account: https://www.scientiamobile.com/register +- Related post — Detecting Mobile Devices in DotKernel 1.6.0: http://www.dotkernel.com/?p=1465 diff --git a/public/readme/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.md b/public/readme/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.md new file mode 100644 index 00000000..c37ecca4 --- /dev/null +++ b/public/readme/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.md @@ -0,0 +1,50 @@ +--- +title: "WURFL PHP API license incompatible with DotKernel" +description: "The WURFL PHP API's license changed from GNU/GPL to AGPL in version 1.3.0, making it a trial-only library incompatible with keeping DotKernel free." +author: "admin" +date_published: "2011-06-08" +canonical_url: "https://new.dotkernel.com/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel/" +category: "Dotkernel" +language: "en" +--- + +# WURFL PHP API license incompatible with DotKernel + +## TL;DR + +The WURFL PHP API was integrated into DotKernel long ago under a GNU/GPL license, compatible with Zend Framework's new BSD license and DotKernel's OSL 3.0 license. On June 6th, 2011, WURFL PHP API version 1.3.0 changed its license to AGPL, turning it into a "trial only" library for product evaluation. DotKernel had updated to this version in the 1.5.0 release candidate without noticing the license change. + +## What happened + +We integrated the WURFL PHP API into the DotKernel code base a long time ago. At that time its license was GNU/GPL, perfectly compatible with the Zend Framework license (new BSD) and DotKernel (OSL 3.0). On June 6th, 2011, the WURFL PHP API library was updated to version 1.3.0, and DotKernel followed suit in its 1.5.0 release candidate, without noticing that the license had changed from GNU/GPL to AGPL — suddenly turning it into a "trial only" library for "Product evaluation" only. + +See the [official announcement](http://www.scientiamobile.com/site/page/view/products#licenses). + +## What DotKernel plans to do + +We respect the work of Luca Passani and Steve Kamerman, but we must keep [DotKernel Application Framework](http://www.dotkernel.com) free, without such license limitations. The only things to do at this stage are: + +- Remove WURFL PHP API library version 1.3.0 from the DotKernel code base. +- Integrate back version 1.2.1, which is still GNU/GPL. +- Keep collecting device information and contributing to the WURFL XML data, which remains free — this data file is and must be the industry standard. +- Consider forking WURFL PHP API 1.2.1 to a new library. +- Consider changing the library's name to avoid trademark issues. + +*Later edit: the correct link to the announcement is [here](http://tech.groups.yahoo.com/group/wmlprogramming/message/34031).* + +## FAQ + +**Q: Why did the WURFL PHP API license become incompatible with DotKernel?** +A: The WURFL PHP API used to be licensed under GNU/GPL, compatible with both Zend Framework's new BSD license and DotKernel's OSL 3.0 license. On June 6th, 2011, version 1.3.0 of the library changed its license to AGPL, turning it into a trial-only library for product evaluation, which is incompatible with keeping DotKernel free. + +**Q: Which version introduced the license change, and when?** +A: Version 1.3.0 of the WURFL PHP API, released June 6th, 2011, changed the license from GNU/GPL to AGPL. DotKernel had updated to this version in its 1.5.0 release candidate without noticing the license change. + +**Q: What did DotKernel do in response to the license change?** +A: DotKernel planned to remove WURFL PHP API version 1.3.0 from its codebase and reintegrate version 1.2.1, which was still GNU/GPL. It also intended to keep contributing to the free WURFL XML data, and considered forking the 1.2.1 library and renaming it to avoid trademark issues. + +## Resources + +- Official announcement of the license change: http://www.scientiamobile.com/site/page/view/products#licenses +- Corrected announcement link: http://tech.groups.yahoo.com/group/wmlprogramming/message/34031 +- DotKernel Application Framework: http://www.dotkernel.com diff --git a/public/readme/dotkernel/wurfl-zend-framework-integration-into-dotkernel.md b/public/readme/dotkernel/wurfl-zend-framework-integration-into-dotkernel.md new file mode 100644 index 00000000..4de49cd4 --- /dev/null +++ b/public/readme/dotkernel/wurfl-zend-framework-integration-into-dotkernel.md @@ -0,0 +1,73 @@ +--- +title: "WURFL Zend Framework Integration into DotKernel" +description: "Step-by-step tutorial on integrating WURFL into DotKernel using the Zend_Http_UserAgent class from Zend Framework 1.11.0rc1." +author: "Teo" +date_published: "2010-10-27" +canonical_url: "https://new.dotkernel.com/dotkernel/wurfl-zend-framework-integration-into-dotkernel/" +category: "Dotkernel" +language: "en" +--- + +# WURFL Zend Framework Integration into DotKernel + +## TL;DR + +[WURFL](http://wurfl.sourceforge.net/) is integrated into DotKernel using the [Zend_Http_UserAgent](http://framework.zend.com/manual/1.11/en/zend.http.user-agent.html) class from [ZF 1.11.0rc1](http://framework.zend.com/download/latest) (the beta release at the time of the post). This post walks through the required folders, config files, and code to wire it up. + +## Installation steps + +1. Download the [WURFL PHP API](http://sourceforge.net/projects/wurfl/files/WURFL%20PHP/1.1/wurfl-php-1.1.tar.gz/download) and unzip it into a folder named `wurfl-php-1.1`. +2. Create the following folders and make them writable by the web server: + - `cache\wurfl\FILE_CACHE_PROVIDER` + - `cache\wurfl\FILE_PERSISTENCE_PROVIDER` +3. In the `configs` folder: + - Copy `wurfl-config.xml` from `wurfl-php-1.1\examples\resources`. + - Rename `wurfl-config.xml` to `wurfl.xml`. +4. Edit the `configs\application.ini` file and add these lines: + +```ini +resources.useragent.wurflapi.wurfl_api_version = "1.1" +resources.useragent.wurflapi.wurfl_lib_dir = APPLICATION_PATH "/library/Wurfl/" +resources.useragent.wurflapi.wurfl_config_file = APPLICATION_PATH "/configs/wurfl.xml" +``` + +5. Create the folder `externals\wurfl`, and copy the following files into it: + - `wurfl-php-1.1\examples\resources\web_browsers_patch.xml` + - `wurfl-php-1.1\examples\resources\wurfl-regression.zip`, renamed to `wurfl.zip` (or download the [latest wurfl zip](http://sourceforge.net/projects/wurfl/files/WURFL/) database and rename it `wurfl.zip`). +6. Copy the contents of the folder `wurfl-php-1.1\WURFL` to `library\Wurfl`. + +## Using WURFL in DotKernel + +[WURFL](http://wurfl.sourceforge.net/) is integrated into DotKernel in the mobile module. To access WURFL configuration: + +```php +$userAgent = new Zend_Http_UserAgent($config->resources->useragent); +$device = $userAgent->getDevice(); +``` + +`$userAgent->getDevice()` returns all the relevant information about the current user agent (`$_SERVER`). + +## FAQ + +**Q: Which Zend Framework class is used to integrate WURFL into DotKernel?** +A: WURFL is integrated using the Zend_Http_UserAgent class from Zend Framework 1.11.0rc1 (the beta release at the time of the post). + +**Q: What is the first step to integrate WURFL into DotKernel?** +A: Download the WURFL PHP API and unzip it into a folder named wurfl-php-1.1. + +**Q: Which folders need to be created and made writable?** +A: Two folders must be created and made writable by the web server: cache/wurfl/FILE_CACHE_PROVIDER and cache/wurfl/FILE_PERSISTENCE_PROVIDER. + +**Q: What needs to be added to application.ini?** +A: Three lines need to be added: resources.useragent.wurflapi.wurfl_api_version = "1.1", resources.useragent.wurflapi.wurfl_lib_dir pointing to APPLICATION_PATH "/library/Wurfl/", and resources.useragent.wurflapi.wurfl_config_file pointing to APPLICATION_PATH "/configs/wurfl.xml". + +**Q: How do you access WURFL configuration in code?** +A: Instantiate a Zend_Http_UserAgent with the useragent config, then call getDevice() on it. getDevice() returns all the relevant information about the current user agent. + +## Resources + +- WURFL: http://wurfl.sourceforge.net/ +- Zend_Http_UserAgent manual: http://framework.zend.com/manual/1.11/en/zend.http.user-agent.html +- Zend Framework latest download: http://framework.zend.com/download/latest +- WURFL PHP API 1.1 download: http://sourceforge.net/projects/wurfl/files/WURFL%20PHP/1.1/wurfl-php-1.1.tar.gz/download +- Latest WURFL zip database: http://sourceforge.net/projects/wurfl/files/WURFL/ diff --git a/public/readme/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.md b/public/readme/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.md new file mode 100644 index 00000000..709a9bde --- /dev/null +++ b/public/readme/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.md @@ -0,0 +1,93 @@ +--- +title: "Zend_Auth and Zend_Acl integrated in DotKernel" +description: "How Zend_Auth and Zend_Acl were integrated into DotKernel 1.5.0 through the Dot_Auth and Dot_Acl classes for user authentication and access control." +author: "Teo" +date_published: "2011-06-16" +canonical_url: "https://new.dotkernel.com/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel/" +category: "Dotkernel" +language: "en" +--- + +# Zend_Auth and Zend_Acl integrated in DotKernel + +## TL;DR + +[Zend_Auth](http://framework.zend.com/manual/en/zend.auth.html) and [Zend_Acl](http://framework.zend.com/manual/en/zend.acl.html) have been integrated into DotKernel starting with version 1.5.0. The User and Admin models were completely refactored using the new `Dot_Auth` and `Dot_Acl` classes for authentication and access control. + +## Dot_Auth + +The `Dot_Auth` class authenticates the user by checking the database, using `Zend_Auth_Adapter_DbTable`: + +```php +private function _getAuthAdapter($who) + { + $dbAdapter = Zend_Registry::get('database'); + $authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter); + $authAdapter->setTableName($who) + ->setIdentityColumn('username') + ->setCredentialColumn('password'); + return $authAdapter; + } +``` + +## ACL roles and resources + +ACL roles (user types) and permissions are configured in the `configs/acl/role.xml` file. In DotKernel there are 3 roles: `guest`, `user`, and `admin`. The resources for ACL are taken from `configs/router.xml` — see the `controllers` tag. + +## Dot_Acl + +The `Dot_Acl` class: + +- Controls user access (ACL — Access Controller Layer). +- Is used for setting and checking the permissions of a user. +- Uses Zend_Acl for checking if a role (user) has access to a resource (controller). + +```php +// instantiate Zend_Acl +$this->acl = new Zend_Acl(); +``` + +```php +public function isAllowed($role) + { + $resource = $this->requestControllerProcessed; + $privillege = $this->requestAction; + if(!$this->acl->has($resource)) + { + return FALSE; + } + else + { + return $this->acl->isAllowed($role, $resource, $privillege); + } + } +``` + +`Dot_Auth` calls the `isAllowed` method from `Dot_Acl` which authenticates the user. `IndexController.php` calls `Dot_Auth`: + +```php +$dotAuth = Dot_Auth::getInstance(); +$dotAuth->checkIdentity('user'); +``` + +## FAQ + +**Q: When were Zend_Auth and Zend_Acl integrated into DotKernel?** +A: Zend_Auth and Zend_Acl were integrated starting with DotKernel version 1.5.0, as part of a refactor of the User and Admin models using the new Dot_Auth and Dot_Acl classes. + +**Q: How does Dot_Auth authenticate a user?** +A: Dot_Auth authenticates the user by checking the database using Zend_Auth_Adapter_DbTable, setting the table name, the identity column to username, and the credential column to password. + +**Q: What roles exist for ACL in DotKernel?** +A: DotKernel defines 3 ACL roles (user types): guest, user, and admin. These roles and their permissions are configured in the configs/acl/role.xml file. + +**Q: Where do the ACL resources come from?** +A: The resources for ACL are taken from configs/router.xml, specifically the controllers tag. + +**Q: What does the Dot_Acl class do?** +A: Dot_Acl controls user access (Access Controller Layer), is used for setting and checking a user's permissions, and uses Zend_Acl to check whether a role has access to a resource. Dot_Auth calls the isAllowed method from Dot_Acl to authenticate the user. + +## Resources + +- Zend_Auth manual: http://framework.zend.com/manual/en/zend.auth.html +- Zend_Acl manual: http://framework.zend.com/manual/en/zend.acl.html diff --git a/public/readme/dotkernel/zend-console-implementation-in-dotkernel.md b/public/readme/dotkernel/zend-console-implementation-in-dotkernel.md new file mode 100644 index 00000000..b4880518 --- /dev/null +++ b/public/readme/dotkernel/zend-console-implementation-in-dotkernel.md @@ -0,0 +1,59 @@ +--- +title: "Zend_Console implementation in DotKernel" +description: "How DotKernel's Console bootstrap lets you run PHP scripts from the command line, including its arguments and bundled example actions." +author: "Adrian" +date_published: "2011-10-13" +canonical_url: "https://new.dotkernel.com/dotkernel/zend-console-implementation-in-dotkernel/" +category: "Dotkernel" +language: "en" +--- + +# Zend_Console implementation in DotKernel + +## TL;DR + +Starting with version 1.5, DotKernel has a Console bootstrap to easily run PHP scripts from the command line. The most common use for this is running cron jobs without using wget or going through Apache. + +## Where it lives + +The bootstrap can be found in `Console/index.php`, and it has one controller in `Controller.php` with two example actions. + +## Usage + +There are two command line arguments: + +| Argument | Meaning | +|---|---| +| `-a` | The name of the action that will be executed | +| `-e` | The environment, as defined in application.ini. Possible values are development, staging, and production. Optional, defaults to production | + +Any other arguments set when calling the script will be available in the controller in the `$registry->arguments` array. + +The controller bundled with DotKernel has two example actions: `count-users`, which demonstrates how to interact with models, and `send-newsletter`, which reads the newsletter from the command line. + +## Examples + +```shell +/var/www/vhosts/example.com/httpdocs/Cron/index.php -e staging -a count-users +``` + +```shell +/var/www/vhosts/example.com/httpdocs/Cron/index.php -e staging -a send-newsletter "test newsletter" +``` + +## FAQ + +**Q: What is the purpose of the Console bootstrap in DotKernel?** +A: Starting with version 1.5, DotKernel has a Console bootstrap to easily run PHP scripts from the command line, most commonly used to run cron jobs without using wget or going through Apache. + +**Q: Where is the Console bootstrap located?** +A: The bootstrap is found in Console/index.php, and it has one controller in Controller.php with two example actions. + +**Q: What command line arguments does the Console bootstrap support?** +A: There are two arguments: -a, the name of the action to execute, and -e, the environment as defined in application.ini (development, staging, or production), which is optional and defaults to production. + +**Q: What happens to extra arguments passed to the script?** +A: Any other arguments set when calling the script will be available in the controller in the registry's arguments array. + +**Q: What example actions are bundled with the Console controller?** +A: The bundled controller has two example actions: count-users, which demonstrates how to interact with models, and send-newsletter, which reads the newsletter from the command line. diff --git a/public/readme/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.md b/public/readme/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.md new file mode 100644 index 00000000..cdf2d7a1 --- /dev/null +++ b/public/readme/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.md @@ -0,0 +1,33 @@ +--- +title: "Zend Framework dropped integration of WURFL adapter" +description: "Zend Framework 1.12.0 drops the WURFL adapter from Zend_Http_UserAgent due to WURFL's licensing change to AGPL; DotKernel is unaffected since it uses its own WURFL adapter." +author: "admin" +date_published: "2012-03-22" +canonical_url: "https://new.dotkernel.com/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter/" +category: "Dotkernel" +language: "en" +--- + +# Zend Framework dropped integration of WURFL adapter + +## What happened + +According to Matthew Weier O'Phinney, Zend Framework Project Leader, the WURFL adapter will be dropped from `Zend_Http_UserAgent` in the next release of ZF, the 1.12.0 branch: + +> due to a change in licensing by the WURFL project -- the free version is now AGPL -- we're dropping the WURFL adapter from Zend_Http_UserAgent. In discussions with Zend and the CR Team, we feel the AGPL raises too many concerns for end users with regards to how their products must be licensed in order to comply. (This is a similar rationale as to why we did not consider ExtJS when looking at JS toolkits to partner with.) +> +> As such, if you relied on the WURFL adapter in the past, you _will_ need to change code when upgrading to ZF 1.12.0, or grab the WURFL adapter from a previous version. + +So it's a good moment to check old or current projects, and proceed accordingly. + +## Impact on DotKernel + +DotKernel framework uses its own WURFL adapter, so this change will not affect projects built with it. + +## FAQ + +**Q: Why did Zend Framework drop the WURFL adapter?** +A: According to Matthew Weier O'Phinney, Zend Framework Project Leader, the WURFL adapter was dropped from Zend_Http_UserAgent in the upcoming 1.12.0 branch due to a change in licensing by the WURFL project: the free version became AGPL, which raised too many concerns for end users regarding how their products must be licensed to comply. + +**Q: Does this change affect projects built with DotKernel?** +A: No. DotKernel uses its own WURFL adapter, so the removal of the WURFL adapter from Zend_Http_UserAgent in ZF 1.12.0 will not affect projects built with DotKernel. diff --git a/public/readme/dotkernel/zend-registry-usage-in-dotkernel.md b/public/readme/dotkernel/zend-registry-usage-in-dotkernel.md new file mode 100644 index 00000000..be240271 --- /dev/null +++ b/public/readme/dotkernel/zend-registry-usage-in-dotkernel.md @@ -0,0 +1,67 @@ +--- +title: "Zend Registry usage in DotKernel" +description: "The variables stored in Zend_Registry in DotKernel and how to read them, either as a full instance or one value at a time." +author: "Adrian" +date_published: "2011-06-01" +canonical_url: "https://new.dotkernel.com/dotkernel/zend-registry-usage-in-dotkernel/" +category: "Dotkernel" +language: "en" +--- + +# Zend Registry usage in DotKernel + +## TL;DR + +In DotKernel, `Zend_Registry` holds a fixed set of request-scoped variables — from timing and configuration to the database adapter and session object — and can be read either as a full instance or one value at a time. + +## Variables stored in Zend_Registry + +| Variable | Contents | +|---|---| +| `startTime` | The result of [microtime()](http://php.net/manual/en/function.microtime.php) at the beginning of the request | +| `configuration` | The configuration options loaded from `configs/application.ini` | +| `router` | Routing settings loaded from `configs/router.xml` | +| `database` | The database adapter | +| `settings` | The settings loaded from the database | +| `requestModule, requestController, requestAction` | The module, controller, and action of the current request | +| `request` | Additional request variables | +| `seo` | SEO information loaded from `configs/dots/seo.xml` (site name, default description, keywords, etc.) | +| `option` | The options for the current dot loaded from `configs/dots/.xml` | +| `session` | The session object | + +## Reading values from the registry + +To use the variables in the registry, first get an instance of the registry object: + +```php +$registry = Zend_Registry::getInstance(); +//... +echo $registry->startTime; +//... +echo $registry->requestAction; +``` + +Or, if you only need one variable, get it directly: + +```php +$action = Zend_Registry::get('requestAction'); +``` + +## FAQ + +**Q: What variables does Zend_Registry contain in DotKernel?** +A: It contains startTime (the result of microtime() at the beginning of the request), configuration (loaded from configs/application.ini), router (loaded from configs/router.xml), database (the database adapter), settings (loaded from the database), requestModule/requestController/requestAction, request (additional request variables), seo (loaded from configs/dots/seo.xml), option (loaded from configs/dots/.xml), and session (the session object). + +**Q: How do you get an instance of the registry?** +A: Use `$registry = Zend_Registry::getInstance();` and then access variables such as `$registry->startTime` or `$registry->requestAction`. + +**Q: How do you retrieve just one variable from the registry?** +A: If you only need one variable, you can get it directly using `$action = Zend_Registry::get('requestAction');`. + +**Q: What does the "seo" entry in the registry contain?** +A: It contains seo information loaded from configs/dots/seo.xml, such as site name, default description, and keywords. + +## Resources + +- microtime() PHP manual: http://php.net/manual/en/function.microtime.php +- Zend Framework Documentation on Zend_Registry: http://framework.zend.com/manual/en/zend.registry.using.html diff --git a/public/readme/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.md b/public/readme/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.md new file mode 100644 index 00000000..f688a64b --- /dev/null +++ b/public/readme/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.md @@ -0,0 +1,60 @@ +--- +title: "Zend_Session usage in DotKernel - Refactor of Dot_Session class" +description: "A session cookie bug found in IE8/IE9 on DotKernel 1.5.0, traced to redundant regenerateID()/rememberMe() calls in Dot_Session, and the fix shipped in 1.5.1." +author: "admin" +date_published: "2011-07-28" +canonical_url: "https://new.dotkernel.com/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class/" +category: "Dotkernel" +language: "en" +--- + +# Zend_Session usage in DotKernel - Refactor of Dot_Session class + +## TL;DR + +A strange session bug was found on a project running DotKernel 1.5.0: in IE8 and IE9, the session cookie was sometimes not saved, forcing repeated logins. Investigation traced it to the `Dot_Session` class calling both `regenerateID()` and `rememberMe()` unnecessarily, generating the session cookie 3 times. The fix, shipped in DotKernel 1.5.1, removed the `regenerateID()` call and added two new application.ini settings. + +## The bug + +We found a strange behaviour of sessions in one of our projects, running DotKernel version 1.5.0 — similar to [one described here](http://trac.elgg.org/ticket/2677). In unknown circumstances, and only in IE 8 and IE9, the session cookie is not saved on the client machine, and the user needs to log in over and over again. It was reproduced once on the staging server, and the only way to fix it at the time was to open a new tab with the same page. + +## The investigation + +Investigating the `Dot_Session` class showed that the session cookie is generated **3 times**. See this [bug report](http://www.dotkernel.net/view.php?id=184). + +The code used both **regenerateID()** and **rememberMe()** methods of Zend_Session, which is **not necessary**. Quote from the [ZF documentation](http://framework.zend.com/manual/1.11/en/zend.session.global_session_management.html): + +> If you call the rememberMe() function, then don't use regenerateId(), since the former calls the latter. If a user has successfully logged into your website, use rememberMe() instead of regenerateId(). + +## The fix + +The **regenerateID()** call was removed, and 2 new settings were added in application.ini related to session: + +- **use_only_cookies** — must be **ON** at all times in order to avoid session fixation. +- **remember_me_seconds**. + +These bug fixes were included in the new DotKernel version 1.5.1. + +## Tip + +If you encounter the same issue in IE8 and IE9, then with all regret, you need to deactivate the **rememberMe()** and **regenerateId()** methods calls. + +## FAQ + +**Q: What session bug was found in DotKernel 1.5.0?** +A: In unknown circumstances, and only in IE8 and IE9, the session cookie was not saved on the client machine, forcing the user to log in over and over again. The only workaround found was to open a new tab with the same page. + +**Q: What caused the session cookie to be generated multiple times?** +A: Investigation of the Dot_Session class showed the session cookie was generated 3 times, because the code called both regenerateID() and rememberMe() methods of Zend_Session. According to the Zend Framework documentation, this is unnecessary: if you call rememberMe(), you should not also call regenerateId(), since rememberMe() already calls it internally. + +**Q: What was the fix, and where was it released?** +A: The regenerateID() call was removed, and two new application.ini settings were added: use_only_cookies, which must be ON at all times to avoid session fixation, and remember_me_seconds. These fixes were included in DotKernel version 1.5.1. + +**Q: What should I do if I still see this issue in IE8/IE9?** +A: If you encounter the same issue in IE8 and IE9, the tip given is to deactivate the rememberMe() and regenerateId() method calls. + +## Resources + +- Similar issue described on Elgg: http://trac.elgg.org/ticket/2677 +- DotKernel bug report: http://www.dotkernel.net/view.php?id=184 +- Zend Framework documentation on global session management: http://framework.zend.com/manual/1.11/en/zend.session.global_session_management.html diff --git a/public/readme/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.md b/public/readme/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.md new file mode 100644 index 00000000..8f41e3ec --- /dev/null +++ b/public/readme/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.md @@ -0,0 +1,31 @@ +--- +title: "Zend Studio PHP Formatter file for DotKernel coding standard." +description: "A downloadable XML file for Zend Studio 9.x that configures the PHP code formatter to follow DotKernel's coding standard." +author: "admin" +date_published: "2012-03-02" +canonical_url: "https://new.dotkernel.com/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard/" +category: "Dotkernel" +language: "en" +--- + +# Zend Studio PHP Formatter file for DotKernel coding standard. + +An XML file is provided for download, ready to be imported into Zend Studio, version 9.x. This file follows DotKernel's coding standard. + +## Installation steps + +1. Unzip the file. +2. Go to Zend Studio -> Preferences -> PHP -> Code Style -> Formatter. +3. Import the XML file. + +## FAQ + +**Q: What is this file for?** +A: It is an XML file, downloadable from the linked page, that can be imported into Zend Studio (version 9.x) to set up the PHP code formatter so it follows DotKernel's coding standard. + +**Q: How do I import the formatter file into Zend Studio?** +A: Unzip the downloaded file, then in Zend Studio go to Preferences -> PHP -> Code Style -> Formatter and import the XML file. + +## Resources + +- [DotKernel PHP Formatter download](http://www.dotkernel.com/download/?did=34) diff --git a/public/readme/dotkernel3/php-8-3-support-in-dotkernel-api.md b/public/readme/dotkernel3/php-8-3-support-in-dotkernel-api.md new file mode 100644 index 00000000..05c5c2cd --- /dev/null +++ b/public/readme/dotkernel3/php-8-3-support-in-dotkernel-api.md @@ -0,0 +1,94 @@ +--- +title: "PHP 8.3 support in Dotkernel API" +description: "Dotkernel API (now v4.2.1) was updated to support PHP 8.2 and PHP 8.3, dropping PHP 8.1, updating dependencies, and replacing the removed PhpFileCache with the new dot-cache package." +author: "Florin Bidirean" +date_published: "2024-04-03" +canonical_url: "https://new.dotkernel.com/dotkernel3/php-8-3-support-in-dotkernel-api/" +category: "Dotkernel 3" +language: "en" +--- + +# PHP 8.3 support in Dotkernel API + +## TL;DR + +Dotkernel API, now at v4.2.1, is the last remaining Dotkernel application updated to support PHP 8.3, following the same approach used for the Frontend update. The update drops PHP 8.1 support, updates a large set of dependencies, removes the `PhpFileCache`-based configuration in favor of the new `dot-cache` package, and requires a small query change (`useQueryCache()` to `setCacheable()`). + +## Supported PHP versions + +Dotkernel API now supports only PHP 8.2 and PHP 8.3. Support for PHP 8.1 was removed, and the project's CI workflows were updated accordingly. + +## Updated dependencies + +### require + +- `dotkernel/dot-annotated-services`: ^4.1.7 +- `dotkernel/dot-cli`: ^3.5.0 +- `dotkernel/dot-data-fixtures`: ^1.1.3 +- `dotkernel/dot-doctrine-metadata`: ^3.2.2 +- `dotkernel/dot-errorhandler`: ^3.3.2 +- `dotkernel/dot-mail`: ^4.1.1 +- `dotkernel/dot-response-header`: ^3.2.3 +- `laminas/laminas-component-installer`: ^3.4.0 +- `laminas/laminas-config`: ^3.9.0 +- `laminas/laminas-config-aggregator`: ^1.14.0 +- `laminas/laminas-http`: ^2.19.0 +- `laminas/laminas-hydrator`: ^4.15.0 +- `laminas/laminas-inputfilter`: ^2.29.0 +- `laminas/laminas-paginator`: ^2.18.0 +- `laminas/laminas-stdlib`: ^3.19.0 +- `laminas/laminas-text`: ^2.11.0 +- `mezzio/mezzio`: ^3.19.0 +- `mezzio/mezzio-authentication-oauth2`: ^2.8.0 +- `mezzio/mezzio-authorization-acl`: ^1.10.0 +- `mezzio/mezzio-authorization-rbac`: ^1.7.0 +- `mezzio/mezzio-cors`: ^1.11.1 +- `mezzio/mezzio-fastroute`: ^3.11.0 +- `mezzio/mezzio-problem-details`: ^1.13.1 +- `mezzio/mezzio-twigrenderer`: ^2.15.0 +- `ramsey/uuid-doctrine`: ^2.0.0 +- `roave/psr-container-doctrine`: ^4.1.0 +- `symfony/filesystem`: ^7.0.3 + +### require-dev + +- `laminas/laminas-coding-standard`: ^2.5 +- `laminas/laminas-development-mode`: ^3.12.0 +- `mezzio/mezzio-tooling`: ^2.9.0 +- `phpunit/phpunit`: ^10.5.10 +- `roave/security-advisories`: dev-latest +- `vimeo/psalm`: ^5.22.0 + +## Caching changes + +The `PhpFileCache` class (previously required for cache configuration) and its related parameters were removed from `config/autoload/doctrine.global.php`, including the `use Doctrine\Common\Cache\PhpFileCache` import and the cache-related configuration parameters. + +In its place, the `dot-cache` package was added, with corresponding updates made to `composer.json`, `config/autoload/doctrine.global.php`, and `config/config.php`. + +## Upgrade notes + +- When upgrading from an older version of Dotkernel API (before PHP 8.3 support), you may need to run `composer update` and/or install Sodium, for example via `sudo dnf install php-sodium.x86_64`. +- Update your queries: replace `useQueryCache()` with `setCacheable()` wherever it is used. + +## FAQ + +**Q: What PHP versions does Dotkernel API support after this update?** +A: Dotkernel API, now at v4.2.1, supports only PHP 8.2 and PHP 8.3. Support for PHP 8.1 was removed, including from the project's workflows. + +**Q: What changed with caching in Dotkernel API?** +A: The PhpFileCache class and its related parameters were removed from config/autoload/doctrine.global.php, which previously required it for cache configuration. The dot-cache package was added instead, with corresponding updates to composer.json, config/autoload/doctrine.global.php, and config/config.php. + +**Q: Do I need to update my Doctrine queries after this update?** +A: Yes. Wherever useQueryCache() is used in your queries, it should be replaced with setCacheable(). + +**Q: What should I do when upgrading an older Dotkernel API installation to this version?** +A: When upgrading from a version of Dotkernel API older than the PHP 8.3 support, you may need to run composer update and/or install Sodium, for example by running sudo dnf install php-sodium.x86_64. + +**Q: Where can I find the full list of changes for this update?** +A: The full list of file changes is available in the linked pull request on GitHub for the dotkernel/api repository, and the steps taken mirror those used for the PHP 8.3 update of the Dotkernel Frontend. + +## Resources + +- [Dotkernel API on GitHub](https://github.com/dotkernel/api) +- [Dotkernel Frontend on GitHub](https://github.com/dotkernel/frontend) +- [Full list of file changes (PR #222)](https://github.com/dotkernel/api/pull/222) diff --git a/public/readme/dotkernel3/php-8-3-support-in-dotkernel-frontend.md b/public/readme/dotkernel3/php-8-3-support-in-dotkernel-frontend.md new file mode 100644 index 00000000..e9723c71 --- /dev/null +++ b/public/readme/dotkernel3/php-8-3-support-in-dotkernel-frontend.md @@ -0,0 +1,52 @@ +--- +title: "PHP 8.3 support in Dotkernel Frontend" +description: "The DotKernel team updated the Frontend application to version 4.2.0 to support PHP 8.3, dropping PHP 8.1 and the PhpFileCache class along the way." +author: "Florin Bidirean" +date_published: "2024-02-14" +canonical_url: "https://new.dotkernel.com/dotkernel3/php-8-3-support-in-dotkernel-frontend/" +category: "Dotkernel 3" +language: "en" +--- + +# PHP 8.3 support in Dotkernel Frontend + +## TL;DR + +To take advantage of PHP 8.3 support in the newest packages, the DotKernel team updated the [Frontend](https://github.com/dotkernel/frontend) application to version 4.2.0. As with the earlier Admin update, this required dropping support for PHP 8.1 and for the no-longer-available `PhpFileCache` class, until a replacement is implemented. + +## What changed + +- **Supports only PHP 8.2 and PHP 8.3** + - Removed support for PHP 8.1 + - Updated CI workflows to remove PHP 8.1 +- **Updated dependencies** in `composer.json` (`require` and `require-dev`), including packages such as `dotkernel/dot-annotated-services`, `dotkernel/dot-controller`, `dotkernel/dot-mail`, `mezzio/mezzio`, `phpunit/phpunit`, `rector/rector`, and `vimeo/psalm`. +- **Updated npm dependencies** in `packagist.json` - recommended to use npm v10.0.4 and Node.js v20.11.0. +- **Removed `PhpFileCache` usage** from configuration files (required due to the class no longer being available): + - Updated `doctrine.global.php` + - Removed the `use Doctrine\Common\Cache\PhpFileCache;` statement + - Removed the parameters used by the `configuration` and `cache` cache settings +- **Fixed Psalm and PHPCS issues** - updated type hints and indentation. + +Next on the roadmap: implementing these changes in the DotKernel Frontend live projects and processing the feedback. + +## FAQ + +**Q: What update adds PHP 8.3 support to Dotkernel Frontend?** +A: The DotKernel team updated the Frontend application to version 4.2.0 so it can take advantage of PHP 8.3 support in the newest packages. + +**Q: Which PHP versions does Dotkernel Frontend 4.2.0 support?** +A: Version 4.2.0 supports only PHP 8.2 and PHP 8.3. Support for PHP 8.1 was removed, similar to the earlier Admin update. + +**Q: What changed with caching in this update?** +A: The no-longer-available PhpFileCache class is no longer supported until a replacement is implemented. The related parameters were removed from configuration files, including removing the `use Doctrine\Common\Cache\PhpFileCache;` statement and the configuration and cache parameters in `doctrine.global.php`. + +**Q: What npm and Node.js versions are recommended for this update?** +A: Alongside updated npm dependencies in packagist.json, it is recommended to use npm v10.0.4 and Node.js v20.11.0. + +**Q: Where can I find the full list of file changes for this update?** +A: The full list of file changes is linked in the article, pointing to the corresponding pull request on the Dotkernel Frontend GitHub repository. + +## Resources + +- [Dotkernel Frontend on GitHub](https://github.com/dotkernel/frontend) +- [Full list of file changes (pull request)](https://github.com/dotkernel/frontend/pull/417) diff --git a/public/readme/headless-platform/complementary-admin-in-dotkernel-headless-platform.md b/public/readme/headless-platform/complementary-admin-in-dotkernel-headless-platform.md new file mode 100644 index 00000000..dae86f91 --- /dev/null +++ b/public/readme/headless-platform/complementary-admin-in-dotkernel-headless-platform.md @@ -0,0 +1,90 @@ +--- +title: "Complementary Admin in Dotkernel Headless Platform" +description: "Overview of Dotkernel Admin, the independent, first-party admin application designed to pair seamlessly with Dotkernel API in the Dotkernel Headless Platform." +author: "Florin Bidirean" +date_published: "2025-08-06" +canonical_url: "https://new.dotkernel.com/headless-platform/complementary-admin-in-dotkernel-headless-platform/" +category: "Headless Platform" +language: "en" +--- + +# Complementary Admin in Dotkernel Headless Platform + +## TL;DR + +The Dotkernel Headless Platform's core components are Dotkernel API and Dotkernel Queue, but the suite also offers a fully separate, complementary **Admin** application designed to pair seamlessly with Dotkernel API. Admin is an independent app built on the same Mezzio + Laminas foundation, sharing a unified tech stack with the API so the two form a cohesive, consistent system. + +## What is Dotkernel Admin? + +Dotkernel Admin lives in its own separate repository and is a fully-functional, independent application. It's built on top of the same Mezzio + Laminas foundation as Dotkernel API, and comes with a built-in UI including authentication, user management and a web interface. + +### Installation + +1. Clone the repository into your folder of choice: + ```shell + git clone https://github.com/dotkernel/admin.git . + ``` +2. Follow the step-by-step [installation guide](https://docs.dotkernel.org/admin-documentation/v5/installation/getting-started/) to configure the database credentials and run the migrations and fixtures that create and populate the database. This takes only a few minutes and results in a ready-to-use admin panel. + +> Make sure to change the default credentials when in production. + +## Features & Architecture + +Dotkernel Admin comes with several useful features out of the box: + +- **Role-Based Access Control** using [dot-rbac](https://docs.dotkernel.org/dot-rbac/) and [dot-rbac-guard](https://docs.dotkernel.org/dot-rbac-guard/) to manage permissions, routes and handler-level security. +- **OpenAPI** annotations included in each module via `OpenAPI.php`, making admin endpoints documented and testable. +- **Frontend tooling** using [Twig](https://twig.symfony.com/) and [NodeJS/NPM](https://www.npmjs.com/) for client-side assets, including `build` and `watch` scripts to manage JavaScript/CSS bundling. + +The file architecture features a modular design with several functional modules: + +| Module | Responsibility | +|---|---| +| Admin module | Manages administrator users | +| App module | Houses core functions like authentication and error reporting | +| Setting module | Stores and manages display or application settings for various report pages | + +## How They Work Together + +- The [REST API](https://github.com/dotkernel/api) is designed to serve JSON endpoints for clients, services and mobile apps alike. +- The [Admin UI](https://github.com/dotkernel/admin) is a web-based interface that communicates with the database via its own endpoints to manage data, users and settings. + +One of the greatest benefits to using both Dotkernel API and Admin is the **unified tech stack**. Both use the [Mezzio microframework](https://docs.mezzio.dev/), [Laminas components](https://docs.laminas.dev/), [Doctrine ORM](https://www.doctrine-project.org/) for database storage and object mapping, [PHP FIG's PSR standards](https://www.php-fig.org/), and share modules like authentication and `dot-rbac`. + +Put together, these ensure consistency across backend and admin tooling, while also making it easier to switch between developing one or the other. The similar architecture normally means fewer developers are needed to manage the codebases. + +## Summary + +Dotkernel Admin is a complementary admin panel that provides effective reporting out of the box and is a good starting point for admin requirements. It's not bundled by default, but is provided as a first-party, installable package designed to work seamlessly alongside the API. + +Together, the two applications form a cohesive system: you normally start with the API backend and add the rich administrative interface to complete your admin platform. Both are built with the same underlying file architecture and implement the same PHP standards - [PSR-7 (HTTP messages)](https://www.php-fig.org/psr/psr-7/) and [PSR-15 (middleware)](https://github.com/php-fig/http-server-handler). + +Benefits of both components: + +- Fully open-source and in active development. +- Highly configurable to suit exact requirements. +- Guarantee a future-proof system using the best practices recommended by the PHP community. + +## FAQ + +**Q: What is Dotkernel Admin?** +A: Dotkernel Admin is a fully-functional, independent application that lives in its own separate repository. It's built on the same Mezzio + Laminas foundation as Dotkernel API and includes a built-in UI with authentication, user management and a web interface. + +**Q: How do I install Dotkernel Admin?** +A: Install it via Git into the folder of your choice with `git clone https://github.com/dotkernel/admin.git .`, then follow the step-by-step installation guide to configure the database credentials and run the migrations and fixtures that create and populate the database. Be sure to change the default credentials before going to production. + +**Q: What features does Dotkernel Admin include out of the box?** +A: It ships with Role-Based Access Control powered by dot-rbac and dot-rbac-guard to manage permissions, routes and handler-level security; OpenAPI annotations in each module for documented and testable admin endpoints; and frontend tooling built on Twig and NodeJS/NPM, including build and watch scripts for JavaScript/CSS bundling. + +**Q: What modules make up Dotkernel Admin's architecture?** +A: The file architecture is modular, featuring an Admin module that manages administrator users, an App module that houses core functions like authentication and error reporting, and a Setting module that stores and manages display or application settings for various report pages. + +**Q: How do Dotkernel API and Admin work together?** +A: The REST API serves JSON endpoints for clients, services and mobile apps, while the Admin UI is a web-based interface that communicates with the database via its own endpoints to manage data, users and settings. Both share a unified tech stack - Mezzio, Laminas components, Doctrine ORM and PHP FIG's PSR standards - plus shared modules like authentication and dot-rbac, which keeps backend and admin tooling consistent and lets fewer developers manage both codebases. + +## Resources + +- [Dotkernel Admin full documentation](https://docs.dotkernel.org/admin-documentation/) +- [Dotkernel Admin Installation](https://docs.dotkernel.org/admin-documentation/v5/installation/getting-started/) +- [Dotkernel Repositories](https://github.com/dotkernel) +- [PHP Framework Interop Group Standards](https://www.php-fig.org/) diff --git a/public/readme/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.md b/public/readme/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.md new file mode 100644 index 00000000..f7edd87d --- /dev/null +++ b/public/readme/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.md @@ -0,0 +1,72 @@ +--- +title: "Dotkernel API v6: The root of Dotkernel Headless Platform" +description: "Overview of what changed in Dotkernel API v6 since the original architecture and components article, covering new features, the Core module, composer updates, and testing/configuration changes." +author: "Florin Bidirean" +date_published: "2025-07-22" +canonical_url: "https://new.dotkernel.com/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform/" +category: "Headless Platform" +language: "en" +--- + +# Dotkernel API v6: The root of Dotkernel Headless Platform + +## TL;DR + +Dotkernel API has evolved significantly since its original architecture and components article, adding Content Negotiation, standardized error responses via mezzio-problem-details, a shareable Core module, a custom templating solution replacing Twig, and a leaner handler dependency setup. Packages were updated across the board, the test suite switched from Psalm to PHPStan at a stricter rule level, and the roadmap for v6.1 targets Service Manager 4 and PHP 8.4/8.5 support. + +## New Features + +- **Content Negotiation** makes it possible for diverse systems to work seamlessly together, defining the communication parameters between client and server so both sides agree on how the data exchange takes place. +- **Standardized error codes**, implemented via [mezzio-problem-details](https://github.com/mezzio/mezzio-problem-details), returning problem details responses based on either PHP primitives or exceptions/throwables to help error reporting system-wide. +- A new **Core module** holding common logic, making it easier to share that functionality with other Dotkernel applications. To reuse it, the Core module is saved as a separate Git repository, which can then be added as a submodule to any Dotkernel application. +- **Custom templating solution** replacing Twig, avoiding the use of [mezzio-twigrenderer](https://github.com/mezzio/mezzio-twigrenderer). This offers more reliable template handling, at the small cost of some features that are normally not used in APIs. +- Refactored **handle delegators** and **injected InputFilters**, reducing the number of dependencies in handlers. + +Smaller changes: + +- Added OpenAPI documentation for all endpoints. +- Added `.gitattributes` to set some git properties based on path and filename. +- Implemented enums in the database, where relevant, mostly as a proof of concept. + +## Coming Soon + +| | Currently supported | Planned for v6.1 | +|---|---|---| +| Service Manager | Version 3 (restricted because of constraints from some dependencies) | Version 4 | +| PHP | 8.3 | 8.4 and 8.5 | + +## Composer Updates + +All packages were updated to their most recent versions that still allow for an installable set of dependencies. + +One package that stands out is [ramsey/uuid](https://github.com/ramsey/uuid). UUID version 7 is already being used in ramsey/uuid version 4, so it will be available with no additional code changes once ramsey/uuid version 5 is released. + +## Tweaked Configuration and Testing + +Alongside smaller changes related to cache configuration for Doctrine and route grouping, the test suite has been revised: [Psalm](https://psalm.dev/) has been replaced with [PHPStan](https://phpstan.org/), a decision made to remain in line with developers from popular projects like Doctrine and Composer, who have also made the switch recently. A separate upgrade increased the rule level to 8, enabling stricter static analysis and revealing more potential errors. + +## FAQ + +**Q: What are the main new features in Dotkernel API v6?** +A: Dotkernel API v6 adds Content Negotiation for smoother communication between client and server, standardized error codes via mezzio-problem-details, a new Core module that centralizes common logic for easier sharing across Dotkernel applications, a custom templating solution that replaces Twig/mezzio-twigrenderer, and refactored handle delegators and injected InputFilters that reduce the number of dependencies in handlers. + +**Q: Why was common logic moved into a Core module?** +A: Moving the shared logic into a Core module makes it easier to reuse that functionality across other Dotkernel applications. The Core module can be saved as a separate Git repository and then added as a submodule to any Dotkernel application. + +**Q: Why did Dotkernel API replace Twig with a custom templating solution?** +A: Twig was replaced to avoid depending on mezzio-twigrenderer. The custom solution offers more reliable template handling, at the small cost of some features that are normally not used in APIs anyway. + +**Q: What is planned for Dotkernel API v6.1?** +A: Dotkernel API currently supports Service Manager version 3, restricted because of constraints from some dependencies, and PHP 8.3. The next version on the roadmap, v6.1, is planned to add support for Service Manager 4 as well as PHP 8.4 and 8.5. + +**Q: Why was Psalm replaced with PHPStan in the test suite?** +A: Psalm was replaced with PHPStan to remain in line with developers from popular projects like Doctrine and Composer, who have also made the switch. A separate upgrade also increased the rule level to 8, enabling stricter static analysis and revealing more potential errors. + +## Resources + +- [Dotkernel API architecture and components (original article)](https://www.dotkernel.com/dotkernel3/dotkernel-api-architecture-components/) +- [mezzio-problem-details on GitHub](https://github.com/mezzio/mezzio-problem-details) +- [mezzio-twigrenderer on GitHub](https://github.com/mezzio/mezzio-twigrenderer) +- [ramsey/uuid on GitHub](https://github.com/ramsey/uuid) +- [Psalm](https://psalm.dev/) +- [PHPStan](https://phpstan.org/) diff --git a/public/readme/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.md b/public/readme/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.md new file mode 100644 index 00000000..31877aa3 --- /dev/null +++ b/public/readme/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.md @@ -0,0 +1,101 @@ +--- +title: "Dotkernel Headless Platform - The Whats, Hows and Whys" +description: "Overview of the Dotkernel Headless Platform architecture, its two main components (Dotkernel API and Dotkernel Admin), how to use them together or separately, and the benefits of the combined setup." +author: "Florin Bidirean" +date_published: "2025-06-13" +canonical_url: "https://new.dotkernel.com/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys/" +category: "Headless Platform" +language: "en" +--- + +# Dotkernel Headless Platform - The Whats, Hows and Whys + +## TL;DR + +A Headless Platform decouples the frontend (UI) from the backend services, with responses consumed by another system such as a website or mobile app. The Dotkernel Headless Platform is made up of Dotkernel API (a REST API based on the Mezzio skeleton) and Dotkernel Admin (a backend management interface), which can be installed separately or together. Using both together, sharing a common Core module, gives consistent entities and queries, an easy-to-maintain shared file structure, and an architecture that scales from small microservices to enterprise-grade APIs. + +## Components of the Dotkernel Headless Platform + +The Dotkernel Headless Platform contains two major components: + +- [Dotkernel API](https://www.dotkernel.org) — a REST API based on the [Mezzio skeleton](https://github.com/mezzio/mezzio-skeleton). +- [Dotkernel Admin](https://github.com/dotkernel/admin) — a complementary component aimed at quickly setting up a backend for your platform. + +The Admin is not bundled by default but is available as a first-party, installable application designed to work seamlessly alongside your API. Together, they form a cohesive system: an API-first backend paired with a rich administrative interface, both built on the same underlying architecture and standards. + +Both components use a PSR-compliant middleware stack, implementing [PSR-7](https://www.php-fig.org/psr/psr-7) and [PSR-15](https://www.php-fig.org/psr/psr-15) as defined by the [PHP Framework Interop Group](https://www.php-fig.org/), promoting a lean, modular architecture. + +Rather than relying on elements built into the framework, business logic is built from scratch — from handlers to dependencies — giving full control over business logic and data architecture. + +## How to Use the Dotkernel Headless Platform + +Dotkernel API and Admin can be installed separately or together, based on business requirements. They are normally designed to complement each other: + +- Dotkernel **Admin** manages the data (create, edit, delete). +- Dotkernel **API** exposes the content to 3rd-party frontends or backends. + +A simple project can be designed around one of the two at first, then expand to the other: + +- **Start with Dotkernel Admin** to populate your database, then later use Dotkernel API to present the data to other frontends written in any programming language, integrating with 3rd-party applications for full control over how data is presented. +- **Start with Dotkernel API** and integrate it into an existing platform. The API can manage access permissions to keep data secure — admins create and edit data for the backend, while users read data for the frontend. Admin can be added later for its simple table-based approach, reports, and graphs. + +The recommendation is to use both API and Admin from the start. + +## Benefits of the Dotkernel Headless Platform Architecture + +Using both API and Admin together brings several benefits: + +### Common code between applications + +The file structure can be configured to use a **Core** module — a common code repository shared between API and Admin, ensuring entities and queries stay consistent. Entities (products, articles, etc.) are the building blocks of the application, and queries handle CRUD interactions with them. + +### Implement only the handlers you need + +Having many entities in the Core doesn't require implementing handlers for each one in both API and Admin. Each application only handles what it needs — for example, a shop may need to edit orders in Admin but only build monthly sales reports via the API. + +### Shared file structure + +API and Admin share a file structure, so becoming familiar with one makes the other easy to pick up. This means onboarding is easy and the applications can be maintained by fewer developers — potentially a single developer, at least initially. By contrast, an API+Angular approach often needs two developers working together to implement a new feature. + +### Can satisfy any application size + +Thanks to the shared Core module, API and Admin evolve together, forming a good starting point for anything from microservices to enterprise-grade APIs. A large number of packages are available for integration, and developers decide exactly what modules to include, keeping the finished application lightweight and preventing bloat and long-term technical debt. + +### Versatile architecture + +The Dotkernel architecture aims to support several architectural designs: + +- **Clean Architecture** — software components are organized into concentric layers, with code dependencies flowing from outer to inner layers, improving testability and independence from libraries, UI, and databases. +- **Domain-Driven Design (DDD)** — the focus is on custom solutions for business logic; rather than building reusable services for a Service Oriented Architecture (SOA), only the specific components needed to satisfy requirements are implemented. +- **Hexagonal Architecture** — divides a system into several loosely-coupled, interchangeable components (application core, database, user interface, test scripts, interfaces with other systems), as an alternative to traditional layered architecture. + +### Supported by an active community + +The Dotkernel development team actively investigates and implements recommended design patterns. Dotkernel applications and packages continue to receive updates with bugfixes, improvements, and recommendations from the PHP community. Some updates may break backward compatibility, but this is typically highlighted in companion articles that may include step-by-step tutorials. The Dotkernel team is available to help with issues related to Dotkernel and Laminas apps and components. + +## FAQ + +**Q: What is a Headless Platform?** +A: A Headless Platform decouples the User Interface (frontend) from the backend services. The responses from the platform are then used by another system, such as a website or mobile app. + +**Q: What are the two main components of the Dotkernel Headless Platform?** +A: The Dotkernel Headless Platform contains two major components: Dotkernel API, a REST API based on the Mezzio skeleton, and Dotkernel Admin, a complementary component aimed at quickly setting up a backend. The Admin is not bundled by default but is available as a first-party, installable application designed to work alongside the API. + +**Q: Can Dotkernel API and Admin be installed separately?** +A: Yes. They can be installed separately or together, based on your business requirements. You can start with Dotkernel Admin to populate your database and later add Dotkernel API to present the data to other frontends, or start with Dotkernel API and integrate it into your existing platform, adding Admin later for its table-based approach, reports, and graphs. + +**Q: What benefits come from using both API and Admin together?** +A: Using both together lets you share a common Core module containing the same entities and queries between the two applications, implement only the handlers you actually need, and benefit from a shared file structure that makes onboarding easy and can be maintained by fewer developers. This setup can also satisfy any application size, from microservices to enterprise-grade APIs. + +**Q: What architectural patterns does the Dotkernel Headless Platform follow?** +A: The architecture is designed to be versatile, following Clean Architecture (concentric layers with dependencies flowing from outer to inner layers), Domain-Driven Design (implementing only the specific components needed for your business logic rather than generic reusable services), and Hexagonal Architecture (dividing the system into loosely-coupled, interchangeable components). + +## Resources + +- [Dotkernel API](https://github.com/dotkernel/api) +- [Dotkernel Admin](https://github.com/dotkernel/admin) +- [Dotkernel repositories](https://github.com/dotkernel) +- [Dotkernel documentation](https://docs.dotkernel.org/) +- [Headless CMS vs. Traditional CMS: What's Right for PHP Apps?](https://www.zend.com/blog/headless-cms-vs-traditional-cms) +- [Laminas GitHub discussions](https://github.com/orgs/laminas/discussions) +- [Dotkernel Discussions](https://github.com/orgs/dotkernel/discussions) diff --git a/public/readme/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.md b/public/readme/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.md new file mode 100644 index 00000000..a8372c05 --- /dev/null +++ b/public/readme/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.md @@ -0,0 +1,132 @@ +--- +title: "Dotkernel Queue - Asynchronous Execution in Dotkernel Headless Platform" +description: "Overview of Dotkernel Queue, a Symfony Messenger based component for running time-consuming or resource-intensive operations asynchronously via background workers in the Dotkernel Headless Platform." +author: "Florin Bidirean" +date_published: "2025-09-04" +canonical_url: "https://new.dotkernel.com/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform/" +category: "Headless Platform" +language: "en" +--- + +# Dotkernel Queue - Asynchronous Execution in Dotkernel Headless Platform + +## TL;DR + +Dotkernel Queue is a component built on Symfony Messenger (via the netglue/laminas-messenger adapter) that lets time-consuming or resource-intensive operations run asynchronously on background workers instead of inside the normal PHP request-response cycle. An active daemon listens for TCP connections, stores incoming messages in Redis, and processes them in FIFO order, with logging, IP-whitelisting security, a configurable retry mechanism, reporting metrics, and a Dead Letter Queue for messages that fail. Priorities and parallel execution are planned future features. + +## What Dotkernel Queue Is + +Dotkernel Queue is a component based on Symfony Messenger that is used to queue asynchronous tasks. netglue/laminas-messenger is an adapter that integrates Symfony Messenger with the Laminas Service Manager container for Mezzio/Laminas applications. + +Some everyday operations are time-consuming and resource-intensive, so it's best if they run on separate machines, decoupled from the regular request-response cycle. Asynchronous execution performed by background workers ensures these operations are not lost, terminated by PHP timeouts, or interrupted by new requests. The main benefit is preventing the main platform from overloading, so it can return a response and remain responsive to new requests while the heavy lifting is scheduled to run later. + +The main goal for the queue is to not have the main platform wait for a response, because the queue executes the task sometime in the future. The regular request-response cycle is completed swiftly by the main platform, possibly with a confirmation message; afterward, the main platform can check with the queue regularly to see if the requested task was completed. + +## Why Use Dotkernel Queue? + +Instead of going straight to Symfony Messenger, using Dotkernel Queue offers several benefits: + +- It uses the Symfony Messenger mechanisms at its heart, so it stays updated as Symfony Messenger releases updates. +- netglue/laminas-messenger turns the component into middleware compatible with Mezzio/Laminas. +- It is seamlessly compatible with the Dotkernel Headless Platform, sharing the same file structure and the same Core submodule used across the Dotkernel application suite (Frontend, API, Admin), ensuring consistency across the platform. +- Dotkernel Queue is a standalone application in its own right, so it can be added more easily to an existing platform. +- The implemented queue features are used in the team's own projects, so their worth is guaranteed in live projects. + +## Why Decouple Operations from the HTTP Request-Response Cycle? + +By default, PHP settings impose a maximum execution time of 30 seconds via the `max_execution_time` parameter. Extending the interval isn't practical up to hours or days for a simple request, and even 30 seconds is unreasonably long for a website that normally expects a response in a few seconds or under 1 second. The solution is to delegate the execution of those tasks to an outside system specifically designed for this purpose. + +## What Are Tasks with Long Execution? + +Tasks delegated to the queue typically: + +- Take extended periods of time to execute and may be interrupted by PHP limitations. +- Are external calls that introduce delays from authentication, awaiting responses from potentially multiple interrogations, or may fail because the 3rd party server is offline. +- Are not part of the main PHP request-response cycle. + +Categories of tasks that can take an extended time to finish: + +- **Data Processing** — big data analytics, scientific simulations, or mathematical computations. +- **File Handling & Media Processing** — video and image processing, or compression/decompression of large files. +- **Networking** — sending email, newsletters, notifications using 3rd party providers. +- **Database Operations** — imports/exports or migrations. +- **System & Infrastructure Tasks** — OS updates, software compilation, CI pipelines. + +Reasons for long execution times include: data size (gigabytes, terabytes, etc.), complex algorithms, hardware limitations (CPU speed, memory, storage, network bandwidth), and external dependencies (waiting on APIs or human input). + +## How the Dotkernel Queue Works + +The queue system has an active daemon that listens for TCP connections on a specific port and stores incoming messages into Redis. This supports a large number of requests per second without overloading. Operations are then scheduled for execution when resources are available, using the FIFO (First-In, First-Out) method, where the oldest request is processed first, followed by newer requests. + +## Main Features + +The following features are already implemented and have been tried and tested extensively in the team's live projects. + +### Logging + +Logging allows developers to monitor operations and investigate issues, helping pinpoint the cause of an error for faster bug fixes and less downtime. Detailed queue logs ensure the execution of long operations is maintainable and allows debugging, though project-specific core logic must still be coded to satisfy business requirements. + +### Security + +Access control is secured by a firewall that only allows requests from whitelisted IPs, keeping the queue as fast as possible without delays from generating and authenticating dynamic tokens. A more secure setup is to keep the Dotkernel Queue server accessible only via the internal network, which removes the need for the firewall and simplifies initial queue setup. + +### Retry Mechanism + +If message processing fails, an internal retry feature guarantees reliable and stable execution. The system can be configured to retry failing tasks a certain number of times, logging the cause each time, before giving up and reporting a failure. After configuring the number of retries, additional handling of failed messages is left to the developer: some failures (e.g. an invalid email address) won't fix themselves on retry, while others (e.g. a temporarily overloaded database with valid queries) just need to run later. + +### Reporting + +Logs enable developers to investigate metrics via console commands, including: + +- Queue length — how many jobs are in the to-do list. +- Processing time per job. +- Error rates — how many messages failed. +- Throughput — jobs/sec processed. + +### Dead Letter Queue (DLQ) + +A Dead-Letter Queue is a separate message queue that temporarily stores messages that failed execution due to errors — for example, incomplete messages, or a 3rd party receiver unable to process the request or unavailable. Certain errors may move messages into the DLQ on the first try, keeping the main queue from being overwhelmed or blocked by messages that will never be processed in their current state. It also gives troubleshooters a central location to identify causes of errors, apply fixes, and then either manually push the messages back into the main queue or delete them. + +## Future Features + +Ways to improve Dotkernel Queue are being explored, with new features added as needed in the field. + +### Priorities + +Currently the queue works on the FIFO method described above. Priorities would determine how soon a task begins execution — immediately or delayed — since certain tasks may be fast enough to handle as they come in, while others are purposefully pushed further down the line. Priorities are set to be integrated into Dotkernel Queue in the near future. + +### Parallel Execution + +Execution is currently made for each task one at a time. Parallel execution of operations via multiple workers is being investigated, where applicable — for example, preprocessing a report could run in parallel with sending emails, since they are separate systems (email processing is external, while the report uses data from the internal database). + +## FAQ + +**Q: What is Dotkernel Queue?** +A: Dotkernel Queue is a component based on Symfony Messenger used to queue asynchronous tasks. It uses the netglue/laminas-messenger adapter to integrate Symfony Messenger with the Laminas Service Manager container for Mezzio/Laminas applications. + +**Q: Why use Dotkernel Queue instead of using Symfony Messenger directly?** +A: Dotkernel Queue builds on Symfony Messenger's mechanisms so it stays updated as Symfony Messenger evolves, uses netglue/laminas-messenger to work as Mezzio/Laminas-compatible middleware, shares the same file structure and Core submodule as the Dotkernel Headless Platform for seamless compatibility, works as a standalone application that can be added to an existing platform, and includes features already tried and tested in the team's own live projects. + +**Q: Why should long-running operations be decoupled from the HTTP request-response cycle?** +A: By default, PHP imposes a maximum execution time of 30 seconds via the max_execution_time parameter, which is too long for a typical request. Instead of extending that limit, the solution is to delegate such tasks to an outside system designed for this purpose, so the main platform can return a response quickly and stay responsive to new requests. + +**Q: How does the Dotkernel Queue process messages?** +A: An active daemon listens for TCP connections on a specific port and stores incoming messages into Redis, which supports a large number of requests per second without overloading. Messages are then scheduled for execution using the FIFO (First-In, First-Out) method, so the oldest request is processed first. + +**Q: What happens when a queued message fails to process?** +A: A configurable retry mechanism retries failing tasks a set number of times, while logging the cause, before giving up and reporting a failure. Messages that ultimately fail can be moved into a Dead Letter Queue, a separate store for failed messages, where troubleshooters can identify the cause, apply a fix, and either push the message back into the main queue or delete it. + +**Q: What features is Dotkernel Queue planning to add in the future?** +A: Two features are being investigated: Priorities, which would let tasks begin execution sooner or later instead of strictly following FIFO order, and Parallel Execution, which would let independent operations run at the same time via multiple workers, such as preprocessing a report in parallel with sending emails. + +## Resources + +- [Dotkernel Queue Documentation](https://docs.dotkernel.org/queue-documentation/v1/overview/) +- [Dotkernel Repository](https://github.com/dotkernel/) +- [Symfony Messenger Documentation](https://symfony.com/doc/current/messenger.html) +- [Symfony Messenger systemd](https://jolicode.com/blog/symfony-messenger-systemd) +- [How To Prioritize Messages When Building Asynchronous Applications With Symfony Messenger](https://sensiolabs.com/blog/2025/how-to-prioritize-messages-when-building-asynchronous-applications-with-symfony-messenger) +- [Dotkernel Queue on GitHub](https://github.com/dotkernel/queue) +- [Symfony Messenger on GitHub](https://github.com/symfony/messenger) +- [netglue/laminas-messenger on GitHub](https://github.com/netglue/laminas-messenger) +- [Laminas Service Manager Documentation](https://docs.laminas.dev/laminas-servicemanager/) diff --git a/public/readme/headless-platform/dotmaker-generate-common-code-in-dotkernel.md b/public/readme/headless-platform/dotmaker-generate-common-code-in-dotkernel.md new file mode 100644 index 00000000..282084b9 --- /dev/null +++ b/public/readme/headless-platform/dotmaker-generate-common-code-in-dotkernel.md @@ -0,0 +1,126 @@ +--- +title: "DotMaker - Generate common code in Dotkernel" +description: "Overview of dotkernel/dot-maker, the CLI tool that generates project files and directories matching the Dotkernel file structure, and how to use it to scaffold modules and individual code items." +author: "Florin Bidirean" +date_published: "2025-09-12" +canonical_url: "https://new.dotkernel.com/headless-platform/dotmaker-generate-common-code-in-dotkernel/" +category: "Headless Platform" +language: "en" +--- + +# DotMaker - Generate common code in Dotkernel + +## TL;DR + +DotMaker (`dotkernel/dot-maker`) programmatically generates project files and directories matching the Dotkernel file structure inspired by Mezzio. It boosts productivity and enforces consistency and standardization compared to creating modules and files by hand, and it can tell the difference between Dotkernel applications (Api, Admin, Frontend) to create the files each one requires. + +## Why use dot-maker? + +Creating a new module manually requires a relatively large number of files and folders. Doing this by hand leaves too many opportunities for mistakes that generate errors and delay development. DotMaker was built to handle this heavy lifting. + +DotMaker is inspired by the [Symfony Maker Bundle](https://github.com/symfony/maker-bundle) in terms of functionality. Mezzio has its own [CLI Tooling](https://docs.mezzio.dev/mezzio/v3/reference/cli-tooling/) that performs similar actions, but DotMaker uses a more opinionated naming pattern and is designed specifically for Dotkernel applications. + +## The file structure for a module + +Example file structure for a `Book` module and the related files it uses from the `Core` module: + +``` +. +└── src/ + ├── Book/ + │ └── src/ + │ ├── Collection/ + │ │ └── BookCollection.php + │ ├── Handler/ + │ │ ├── GetBookCollectionHandler.php + │ │ ├── GetBookResourceHandler.php + │ │ └── PostBookResourceHandler.php + │ ├── InputFilter/ + │ │ ├── Input/ + │ │ │ ├── AuthorInput.php + │ │ │ ├── NameInput.php + │ │ │ └── ReleaseDateInput.php + │ │ └── CreateBookInputFilter.php + │ ├── Service/ + │ │ ├── BookService.php + │ │ └── BookServiceInterface.php + │ ├── ConfigProvider.php + │ └── RoutesDelegator.php + └── Core/ + └── src/ + └── Book/ + └── src/ + ├── Entity/ + │ └── Book.php + ├── Repository/ + │ └── BookRepository.php + └── ConfigProvider.php +``` + +This example is for a Dotkernel API project, so it has a Collection but is missing the Form and template files that Dotkernel Admin requires. DotMaker can tell the difference between Dotkernel applications and will create the required files for each. + +## How dot-maker works + +To create a new module: + +1. Run `./vendor/bin/dot-maker module`. +2. Follow the prompted list of steps that create each relevant item, from entities and repositories to handlers and services. +3. DotMaker automatically creates the module's `ConfigProvider.php` and tells you how to register it in `config/config.php` and `composer.json`. +4. DotMaker generates documentation in an `OpenApi.php` file and tells you what command to run to generate migrations for the new module. +5. Once the files are created, manually configure the authorization for the new module based on the platform's requirements. + +Authorization is configured in different files between Dotkernel applications: + +| Application | Authorization file | +|---|---| +| Dotkernel Api | `config/autoload/authorization.global.php` ([docs](https://docs.dotkernel.org/api-documentation/v6/core-features/authorization/)) | +| Dotkernel Admin | `config/autoload/authorization-guards.global.php` ([docs](https://docs.dotkernel.org/admin-documentation/v6/how-to/authorization/)) | +| Dotkernel Frontend | `config/autoload/authorization-guards.global.php` ([docs](https://docs.dotkernel.org/frontend-documentation/v5/how-to/authorization/)) | + +### Generating individual items + +DotMaker can also create individual items related to a module, like an entity, a form, a new command, or a middleware, instead of a whole module. These commands work only after the basic command is added to `composer.json`. + +| Command | Effect | +|---|---| +| `composer make collection` / `./vendor/bin/dot-maker collection` | Creates a Collection for an existing module | +| `composer make command` / `./vendor/bin/dot-maker command` | Creates a Command for an existing module | +| `composer make entity` / `./vendor/bin/dot-maker entity` | Creates an Entity and its associated Repository (skips the Repository if it already exists) | +| `composer make form` / `./vendor/bin/dot-maker form` | Creates a Form for an existing module | +| `composer make handler` / `./vendor/bin/dot-maker handler` | Creates a Handler for an existing module | +| `composer make input` / `./vendor/bin/dot-maker input` | Creates an Input for an existing module | +| `composer make input-filter` / `./vendor/bin/dot-maker input-filter` | Creates an InputFilter for an existing module (prompts for create, edit, or replace type) | +| `composer make middleware` / `./vendor/bin/dot-maker middleware` | Creates a Middleware for an existing module | +| `composer make module` / `./vendor/bin/dot-maker module` | Creates a Module and its predefined files (entity, repository, service, handler, etc.) | +| `composer make repository` / `./vendor/bin/dot-maker repository` | Creates a Repository and its Entity (skips the Entity if it already exists) | +| `composer make service` / `./vendor/bin/dot-maker service` | Creates a Service and its associated ServiceInterface (skips the ServiceInterface if it already exists) | +| `composer make service-interface` / `./vendor/bin/dot-maker service-interface` | Creates a ServiceInterface and its associated Service (skips the Service if it already exists) | + +DotMaker won't do all the work, but after running through the maker process you have a solid foundation ready to accept custom code. + +## FAQ + +**Q: What does the DotMaker library do?** +A: DotMaker (dotkernel/dot-maker) programmatically generates project files and directories that match the Dotkernel file structure inspired by Mezzio, giving a productivity boost and promoting consistency and standardization compared to creating everything manually. + +**Q: What inspired DotMaker and how is it different from Mezzio's own CLI tooling?** +A: DotMaker is inspired by the Symfony Maker Bundle in terms of functionality. Mezzio has its own CLI Tooling that performs similar actions, but DotMaker uses a more opinionated naming pattern and is designed specifically for Dotkernel applications. + +**Q: How do I create a new module with DotMaker?** +A: Run ./vendor/bin/dot-maker module and follow the prompts that walk you through creating each relevant item, from entities and repositories to handlers and services. DotMaker also automatically creates the module's ConfigProvider.php, tells you how to register it in config/config.php and composer.json, generates documentation in an OpenApi.php file, and tells you what command to run to generate migrations. + +**Q: Does DotMaker configure authorization for the new module?** +A: No, authorization must be configured manually after the files are created, and the location differs between Dotkernel applications: Dotkernel Api uses config/autoload/authorization.global.php, while Dotkernel Admin and Dotkernel Frontend use config/autoload/authorization-guards.global.php. + +**Q: Can DotMaker generate individual items instead of a whole module?** +A: Yes. DotMaker provides individual commands to create items such as a collection, command, entity, form, handler, input, input-filter, middleware, repository, service, or service-interface for an existing module. For example, ./vendor/bin/dot-maker entity creates an Entity and its associated Repository, skipping the Repository if it already exists. + +**Q: Do the composer make shortcuts work right away?** +A: No, commands like composer make entity only work after the basic command has been added to composer.json; the equivalent ./vendor/bin/dot-maker form is available regardless. + +## Resources + +- [DotMaker full documentation](https://docs.dotkernel.org/dot-maker/v1/overview/) +- [Implementing a book module in Dotkernel API using DotMaker](https://docs.dotkernel.org/api-documentation/v6/tutorials/create-book-module-via-dot-maker/) +- [API file structure](https://docs.dotkernel.org/api-documentation/v6/introduction/file-structure/) +- [Symfony Maker Bundle Documentation](https://symfony.com/bundles/SymfonyMakerBundle/current/index.html) diff --git a/public/readme/headless-platform/evolution-pattern-versus-api-versioning.md b/public/readme/headless-platform/evolution-pattern-versus-api-versioning.md new file mode 100644 index 00000000..9d097945 --- /dev/null +++ b/public/readme/headless-platform/evolution-pattern-versus-api-versioning.md @@ -0,0 +1,100 @@ +--- +title: "Evolution Pattern versus API Versioning" +description: "How Dotkernel weighs an evolution pattern against API versioning when changing an API, and the sunsetting mechanism used in Dotkernel API to announce breaking changes." +author: "Florin Bidirean" +date_published: "2025-12-09" +canonical_url: "https://new.dotkernel.com/headless-platform/evolution-pattern-versus-api-versioning/" +category: "Headless Platform" +language: "en" +--- + +# Evolution Pattern versus API Versioning + +## TL;DR + +An Evolution Pattern keeps the same codebase and evolves it gradually (for example via sunsetting), while API versioning maintains multiple parallel versions of an API so existing clients aren't broken. The two are not mutually exclusive. Dotkernel API favors an evolution pattern with a sunsetting mechanism, reserving full versioning for major, format-level changes. + +## Definitions + +- **Evolution Pattern**: A reusable, high-level strategy for modifying or evolving existing software systems over time, keeping software relevant for old and new users as new needs arise. +- **Versioning**: The practice of assigning unique version numbers to different releases of a software platform to track changes, improvements, bug fixes, compatibility, and evolution. API versioning specifically assigns versions to an API so it can change without breaking existing clients. +- **Sunsetting**: A mechanism for announcing that an API's old functionality will be phased out on a given date, via response headers, documentation, and public announcements. + +## When to use an Evolution Pattern versus Versioning + +Technically, the two concepts are not in conflict — an evolution pattern keeps the same codebase and announces breaking changes via mechanisms like sunsetting, while API versioning maintains multiple versions of an API so that changes don't affect clients using older versions. A platform can use both strategies together if it suits its needs. The responsibility falls on the team to decide where to invest development and maintenance costs. + +| Problem | Potential Solution | Strategy | +|---|---|---| +| Fix a typo in the parameter name | Sunsetting / do nothing (it's only a typo) | Evolution pattern. Versioning is overkill. | +| Add a new parameter | Make it optional, so it doesn't break existing implementations | Evolution pattern | +| Implement a backward-compatibility-breaking update that clients must adhere to | Announce it in time to allow clients to adapt | Evolution pattern | +| Implement a major update to an endpoint | Add a `v2` for only that endpoint, not the whole API, and encourage clients to migrate | Evolution pattern + Versioning | +| Return responses in a totally different format (e.g. GraphQL vs REST APIs) | Keep both versions active | Versioning | + +## Practical example from Dotkernel API + +Dotkernel avoids full rewrites, considering them too risky, costly, and often not the best solution. Software evolution should instead be gradual and organic, adapting to new requirements with as little impact to the codebase and clients as possible. + +For Dotkernel API, an evolution pattern with a sunsetting mechanism was chosen: the codebase stays the same, and new requirements are implemented in a targeted manner. + +To enhance an endpoint, Dotkernel distinguishes between calls based on their payload. For example, on the `/user` endpoint: + +- A request with only an `email` uses the older functionality and returns the response currently working for existing clients. +- A request with both `email` and `name` returns an enhanced response, requiring clients to update their own code to handle the new response structure. + +### Announcing deprecation + +Deprecation is announced by including an updated response header via handler attributes, with customizable values for `sunset`, `link`, and `deprecationReason`. A deprecated endpoint's response includes headers similar to: + +``` +HTTP/1.1 200 OK +Host: 0.0.0.0:8080 +Date: Mon, 1 Dec 2025 10:10:10 GMT +Connection: close +X-Powered-By: PHP/6.4.20 +Content-Type: application/json +Permissions-Policy: interest-cohort=() +Sunset: 2026-01-01 +Link: https://docs.dotkernel.org/api-documentation/v7/tutorials/api-evolution/;rel="sunset";type="text/html" +Vary: Origin +``` + +Developers must read and understand this header so they can plan an update before the sunset date. New functionality is normally kept alongside the old functionality for a reasonable period. Once the sunset date is reached, the old functionality is removed and only the new one remains in production: + +- An endpoint may return `404 Not Found`. +- A property may be removed from the response. + +Dotkernel uses multiple channels to announce impactful, backward-incompatible updates: + +- **API headers** include the deprecation header. +- **Documentation pages** detail the change and recommended code updates. +- **Public portal** announcements, like blog articles and newsletters. + +Notifications should go out early enough to reach developers of third-party software using the API, so they can plan and implement updates with little or no downtime. + +## Conclusions + +An evolution pattern requires good planning, including allocating enough time for clients to be aware of updates and implement them, ideally with no downtime for either side. Versioning remains a valid strategy but is best reserved for impactful architectural changes rather than smaller changes like fixing a typo or adding a field. Ultimately, the best approach depends on the specific use case. + +## FAQ + +**Q: What is the difference between an Evolution Pattern and API versioning?** +A: An Evolution Pattern is a reusable, high-level strategy for modifying or evolving existing software systems over time while keeping the same codebase, often using mechanisms like sunsetting. API versioning instead maintains multiple versions of an API so that changes do not affect clients relying on older versions. The two concepts are not in conflict, and a platform can use both strategies if it suits its needs. + +**Q: When should you use an evolution pattern instead of versioning?** +A: Small changes, like fixing a typo in a parameter name or adding a new optional parameter, are best handled with an evolution pattern, since versioning would be overkill. Backward-compatible-breaking updates should be announced in time via an evolution pattern. A major update to a single endpoint can combine an evolution pattern with versioning, for example by adding a v2 for just that endpoint. Only when responses need to be returned in a totally different format, such as GraphQL versus REST, is versioning the better fit. + +**Q: How does Dotkernel API announce endpoint deprecations?** +A: Dotkernel API distinguishes calls based on payload (for example, an older response for a request with only an email, versus an enhanced response when both email and name are supplied). Deprecation is announced by including an updated response header via handler attributes, with customizable values for sunset, link, and deprecationReason. These updates are also communicated through documentation pages and public portal channels like blog articles and newsletters. + +**Q: What happens once an endpoint's sunset date is reached?** +A: The old functionality is removed and only the new functionality remains in production. In practice this means an endpoint may start returning 404 Not Found, or a property may be removed from the response. + +## Resources + +- [What is the HTTP Sunset header?](https://sophiabits.com/blog/what-is-the-http-sunset-header) +- [Deprecating Resources and Properties](https://api-platform.com/docs/core/deprecations) +- [API Evolution for REST/HTTP APIs](https://philsturgeon.com/api-evolution-for-rest-http-apis) +- [Just say no - to versioning APIs](https://www.hmeid.com/blog/just-say-no-to-versioning) +- [Ways to version your API, Part 2](https://urthen.github.io/2013/05/16/ways-to-version-your-api-part-2) diff --git a/public/readme/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.md b/public/readme/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.md new file mode 100644 index 00000000..08fe4c00 --- /dev/null +++ b/public/readme/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.md @@ -0,0 +1,149 @@ +--- +title: "Implementing Time-based One-Time Password (TOTP) in Dotkernel" +description: "Tutorial on installing and using dot-totp to add two-factor authentication (2FA) with time-based one-time passwords to Dotkernel Admin, including required files, database changes, and the resulting user flow." +author: "Florin Bidirean" +date_published: "2026-04-03" +canonical_url: "https://new.dotkernel.com/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel/" +category: "Headless Platform" +content_type: "tutorial" +language: "en" +entities: + - name: "dot-totp" + type: "SoftwareSourceCode" + url: "https://github.com/dotkernel/dot-totp" + - name: "Dotkernel Admin" + type: "SoftwareApplication" + url: "https://github.com/dotkernel/admin" +prerequisites: + - "Dotkernel Admin installed (the steps apply similarly to any middleware-based application)" + - "An Authenticator app on a mobile device (for end users enabling TOTP)" +keywords: ["TOTP", "time-based one-time password", "2FA", "two-factor authentication", "dot-totp", "Dotkernel Admin", "PHP", "Mezzio", "middleware", "recovery codes", "QR code", "authenticator app"] +official_docs: "https://docs.dotkernel.org/admin-documentation/v7/tutorials/install-dot-totp/" +code_examples: "https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp" +--- + +# Implementing Time-based One-Time Password (TOTP) in Dotkernel + +## TL;DR + +`dot-totp` adds two-factor authentication (2FA) to Dotkernel Admin using time-based one-time passwords. Users authenticate with their password plus a 6-digit code from an Authenticator app that refreshes every 30 seconds. Installation is one Composer command plus a set of forms, handlers, middleware, and templates from the official code examples, applying a `TotpTrait` to the relevant entity, migrating three new database columns, and registering routes/pipeline/ConfigProvider updates. + +## Definitions + +- **TOTP (Time-based One-Time Password)**: A security algorithm used as part of two-factor authentication. Generates temporary, unique 6-digit codes that change every 30 seconds, via an Authenticator app. +- **2FA (Two-Factor Authentication)**: Requires both a password and an additional one-time code, to protect against account attacks. +- **dot-totp**: The Dotkernel package that integrates the TOTP mechanism into Dotkernel applications. +- **Recovery codes**: Single-use backup codes generated during TOTP activation, usable for login when the mobile device is unavailable. + +## Key facts + +| Fact | Value | +|---|---| +| Package | `dotkernel/dot-totp` | +| Install command | `composer require dotkernel/dot-totp` | +| Target application | Dotkernel Admin (applies similarly to any middleware-based application) | +| Code format | 6-digit numeric code | +| Code lifetime | 30 seconds | +| Recovery codes | Single-use each; must be saved in a secure location | +| Entity integration | `TotpTrait` applied to any entity requiring 2FA | +| New database columns | `totpSecret`, `totp_enabled`, `recovery_codes` | +| Official tutorial | https://docs.dotkernel.org/admin-documentation/v7/tutorials/install-dot-totp/ | +| Code examples | https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp | + +## 2FA with TOTP: authentication flow + +1. User submits username and password (unchanged from standard login). +2. Since TOTP is activated, the application also asks for the code from the user's Authenticator app. +3. User submits the current 6-digit code, or alternatively a single-use recovery code. +4. If the code is valid, the user is logged in. + +## Installation steps + +### Step 1 — Install the package + +Prerequisite: a working Dotkernel Admin installation. + +```shell +composer require dotkernel/dot-totp +``` + +### Step 2 — Add the integration files + +Following the Dotkernel file structure, add the files below (downloadable from the [official code examples](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp)): + +Forms: +- `src/Admin/src/Form/RecoveryForm.php` +- `src/Admin/src/Form/TotpForm.php` + +Handlers (in `src/Admin/src/Handler/Account/`): +- `GetDisableTotpFormHandler.php` +- `GetEnableTotpFormHandler.php` +- `GetRecoveryFormHandler.php` +- `GetTotpHandler.php` +- `PostDisableTotpHandler.php` +- `PostEnableTotpHandler.php` +- `PostValidateRecoveryHandler.php` +- `PostValidateTotpHandler.php` + +Templates: +- `src/Admin/templates/admin/recovery-form.html.twig` + +Middleware: +- `src/App/src/Middleware/CancelUrlMiddleware.php` +- `src/App/src/Middleware/TotpMiddleware.php` + +### Step 3 — Apply the entity trait and migrate the database + +Apply the trait at `src/Core/src/App/src/Entity/TotpTrait.php` to any entity that requires 2FA, then migrate the new columns onto that entity's table: `totpSecret`, `totp_enabled`, and `recovery_codes`. + +### Step 4 — Register the remaining snippets + +The `_misc` folder in the code examples contains four required additions: + +| Snippet | Destination | +|---|---| +| Enable/disable 2FA button (`totp-append-view-account.html.twig`) | `view-account.html.twig`, or a new page | +| Routes updates (`totp-append-routes.php`) | `src/Admin/src/RoutesDelegator.php` | +| Pipeline updates (`totp-append-Pipeline.php`) | `config/pipeline.php`, after `$app->pipe(AuthMiddleware::class);` | +| ConfigProvider updates (`totp-append-ConfigProvider.php`) | `src/Admin/src/ConfigProvider.php` | + +## Using TOTP in Dotkernel Admin (end-user flow) + +### Enabling TOTP + +1. Navigate to the account profile (top-right image in Dotkernel Admin). A TOTP box with an "Enable TOTP" button is shown. +2. Click "Enable TOTP". A QR code is displayed. An Authenticator app on a mobile device is required. +3. Scan the QR code with the mobile device. +4. Enter the 6-digit code generated by the Authenticator app. The code refreshes every 30 seconds. +5. Save the recovery codes shown during activation in a secure location - each is usable only once. +6. If the code is valid, the user is logged in and TOTP is activated for the account. + +### Logging in with TOTP enabled + +1. Enter username and password as before. +2. Submit the current code from the Authenticator app, or alternatively a recovery code. +3. On success, the user is logged in. + +## FAQ + +**Q: What is a Time-based One-Time Password (TOTP)?** +A: A Time-based One-Time Password (TOTP) is a security algorithm used as part of two-factor authentication (2FA) that requires both a password and an additional one-time code generated by an Authenticator app. In Dotkernel, this is implemented through the dot-totp package. + +**Q: Can dot-totp be used outside of Dotkernel Admin?** +A: Yes. Although this tutorial installs dot-totp in Dotkernel Admin, the installation steps work similarly in any middleware-based PHP application. + +**Q: What happens if I lose access to my authenticator app?** +A: You can log in using one of the recovery codes generated when you activated TOTP. Each recovery code is usable only once, so make sure to save them in a secure location. + +**Q: How often does the TOTP code change?** +A: The code generated by your Authenticator app refreshes every 30 seconds. + +**Q: What database changes are required to support dot-totp?** +A: You need to migrate three new columns onto the entity that uses the TotpTrait: totpSecret, totp_enabled, and recovery_codes. + +## Resources + +- dot-totp on GitHub: https://github.com/dotkernel/dot-totp +- Dotkernel Admin on GitHub: https://github.com/dotkernel/admin +- Official tutorial — Installing dot-totp into Dotkernel Admin: https://docs.dotkernel.org/admin-documentation/v7/tutorials/install-dot-totp/ +- Complete code examples: https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp diff --git a/public/readme/index.md b/public/readme/index.md new file mode 100644 index 00000000..c3f5a0ca --- /dev/null +++ b/public/readme/index.md @@ -0,0 +1,85 @@ +--- +title: "Dotkernel — A Headless Platform for Modern Web Applications" +description: "Dotkernel is a collection of open-source PHP application skeletons built on Mezzio and Laminas, ranging from a minimal presentation-site skeleton to a full headless platform (API, Admin, Queue) for enterprise-grade applications." +url: "http://light-blog.localhost/" +language: "en" +entities: + - name: "Dotkernel API" + type: "SoftwareApplication" + url: "https://github.com/dotkernel/api" + - name: "Dotkernel Admin" + type: "SoftwareApplication" + url: "https://github.com/dotkernel/admin" + - name: "Dotkernel Queue" + type: "SoftwareApplication" + url: "https://github.com/dotkernel/queue" + - name: "Dotkernel Light" + type: "SoftwareApplication" + url: "https://github.com/dotkernel/light" + - name: "Dotkernel Frontend" + type: "SoftwareApplication" + url: "https://github.com/dotkernel/frontend" + - name: "Mezzio" + type: "SoftwareFramework" + url: "https://github.com/mezzio/mezzio-skeleton" +standards: ["PSR-7", "PSR-11", "PSR-15"] +keywords: ["headless platform", "PHP", "Mezzio", "Laminas", "PSR-15 middleware", "REST API", "admin panel", "task queue", "open source"] +--- + +# Dotkernel + +## TL;DR + +Dotkernel is a collection of open-source PHP application skeletons built on Mezzio and Laminas, licensed BSD-3-Clause. Its headless platform combines three components — **API**, **Admin**, and **Queue** — that can be adopted separately or together. Two additional skeletons, **Light** and **Frontend**, stand outside the platform for simpler or frontend-only use cases. + +## Key facts + +| Fact | Value | +|---|---| +| Runtime | Mezzio + Laminas | +| Standards | PSR-7, PSR-11, PSR-15 | +| License | BSD-3-Clause | +| Router | FastRoute | +| Response | Diactoros | +| Maintainer | Apidemia | + +## Request lifecycle + +Router (FastRoute) → Authentication (dot-authentication) → RBAC guard (dot-rbac-guard) → your PSR-15 handler → Response (Diactoros). + +## The headless platform: three applications, one platform + +API, Admin, and Queue are designed to integrate into a single, unified headless platform - better together than the sum of their parts. + +| Component | Role | Repository | +|---|---|---| +| **API** | Framework-less, opinionated toolkit for shipping REST APIs; an alternative to legacy Laminas API Tools (Apigility) | https://github.com/dotkernel/api | +| **Admin** | Table-based backend for managing platform records, plus tools to build reports and dashboards | https://github.com/dotkernel/admin | +| **Queue** | Asynchronous task queuing built on Symfony Messenger, wired into the Laminas Service Manager container via netglue/laminas-messenger | https://github.com/dotkernel/queue | + +## Skeletons outside the platform + +Not every project needs a headless platform - these two stand on their own. + +| Skeleton | Purpose | Repository | +|---|---|---| +| **Light** | Minimal, PSR-15 compliant skeleton built for learning purposes - a didactic example, not a platform component | https://github.com/dotkernel/light | +| **Frontend** | Standalone skeleton for building frontend applications on Mezzio and Laminas, separate from the headless platform | https://github.com/dotkernel/frontend | + +## Components (dot-* packages) + +Small, independent packages - pull in what you need. Grouped by concern: authentication (dot-authentication, dot-auth-social), authorization (dot-authorization, dot-rbac, dot-rbac-guard), controllers (dot-controller), forms (laminas-form), session (dot-session, dot-flashmessenger, dot-cache), logging & error handling (dot-log, dot-errorhandler), emailing (dot-mail), backend/database abstraction (doctrine/orm, doctrine/dbal, dot-data-fixtures), navigation (dot-navigation), templating (dot-twigrenderer), and supporting tools (dot-annotated-services, dot-event, dot-helpers, dot-cli, dot-doctrine-metadata, dot-response-header, dot-user-agent-sniffer, dot-debugbar, dot-geoip). + +Full package list and lifecycle/support status: https://new.dotkernel.com/dotkernel-packages-oss-lifecycle/ + +## About + +Dotkernel is an open-source project created and led by the dev team at Apidemia - built first as an internal tool for handling complex architectures, now freely shared with the community. + +## Resources + +- Documentation: https://docs.dotkernel.org +- GitHub organization: https://github.com/dotkernel +- Blog: https://new.dotkernel.com/blog/ +- Categories: https://new.dotkernel.com/categories/ +- Contact: https://new.dotkernel.com/contact/ diff --git a/public/readme/javascript/javascript-email-validator.md b/public/readme/javascript/javascript-email-validator.md new file mode 100644 index 00000000..10d83695 --- /dev/null +++ b/public/readme/javascript/javascript-email-validator.md @@ -0,0 +1,34 @@ +--- +title: "Javascript: Email Validator" +description: "A regex-based fix for email validation that allows the plus (+) and dash (-) characters in the appropriate parts of an email address." +author: "admin" +date_published: "2008-10-03" +canonical_url: "https://new.dotkernel.com/javascript/javascript-email-validator/" +category: "Javascript" +language: "en" +--- + +# Javascript: Email Validator + +## Problem + +Email validation should allow the dash (-) character anywhere in an email address or domain, and the plus (+) character in the username (many people use it for categorization, especially on Gmail). + +## Solution + +```javascript +var regex = new RegExp("^+(\.+)*@+(\.+)*\.({2,})$","i"); +``` + +This will also validate emails like `username1+username2@gmail-domain.co.uk`. + +## FAQ + +**Q: What problem does this email validator solve?** +A: Common email regex patterns fail to allow the plus (+) character in the username and the dash (-) character anywhere in the address or domain, even though both are commonly used (plus for categorization on Gmail, dash in domain names). + +**Q: What is the suggested regex solution?** +A: The regular expression `^+(\.+)*@+(\.+)*\.({2,})$` (case-insensitive) that permits both characters in the appropriate parts of the address. + +**Q: What kind of email addresses does this regex validate?** +A: Addresses like `username1+username2@gmail-domain.co.uk`, combining a plus-separated username with a dashed domain. diff --git a/public/readme/licensing/mit-versus-lgpl-in-practice-dotkernel-case.md b/public/readme/licensing/mit-versus-lgpl-in-practice-dotkernel-case.md new file mode 100644 index 00000000..2a030b6c --- /dev/null +++ b/public/readme/licensing/mit-versus-lgpl-in-practice-dotkernel-case.md @@ -0,0 +1,60 @@ +--- +title: "MIT versus LGPL in practice: Dotkernel case" +description: "How DotKernel handled discovering an LGPL v3 dependency in one of its MIT-licensed packages." +author: "Florin Bidirean" +date_published: "2024-04-15" +canonical_url: "https://new.dotkernel.com/licensing/mit-versus-lgpl-in-practice-dotkernel-case/" +category: "Licensing" +language: "en" +--- + +# MIT versus LGPL in practice: Dotkernel case + +## TL;DR + +DotKernel discovered that an upstream dependency, matomo/device-detector, was licensed under LGPL v3 - a more restrictive license than the MIT license DotKernel uses for its own projects. Because the more restrictive license would have to apply to the whole application, DotKernel implemented a workaround: it stopped bundling that dependency by default and documented the licensing implications. + +## Detailing the problem + +The package in question, [matomo/device-detector](https://github.com/matomo-org/device-detector), requires developers to share their derivative code publicly (LGPL v3). This conflicted with DotKernel's contractual obligations to keep client code business and enterprise friendly, and risked legal issues. + +## The solution + +DotKernel decided to discard the use of third-party packages licensed under LGPL v3. The matomo/device-detector package had been used in [dotkernel/dot-user-agent-sniffer](https://github.com/dotkernel/dot-user-agent-sniffer) to identify the user agent and use the results in internal reports. + +All of [DotKernel's](https://github.com/dotkernel) packages are licensed under MIT, which has no restrictions on the source code other than keeping the license and copyright notice in a file within each package. Other non-restrictive licenses deemed acceptable (used by some dependencies instead of MIT) include Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MPL-2.0 and OSL-3.0. + +## The resolution + +Starting with version 3.4.0, the dotkernel/dot-user-agent-sniffer package must follow the LGPL v3 license. The [admin application](https://github.com/dotkernel/admin) will not use the features from matomo/device-detector, but will contain instructions on how to add the package for developers who intend to use it. + +## Important note + +Any application using dotkernel/dot-user-agent-sniffer with a version lower than 3.4.0 is still a legal liability. DotKernel will not abandon the issue, and is looking into a solution to bring back the device detector functionality in the future under a less restrictive license. For now, dotkernel/dot-user-agent-sniffer is not included in any DotKernel application by default. + +## FAQ + +**Q: Why did DotKernel need a workaround for this package?** +A: An analysis found that matomo/device-detector was licensed under LGPL v3, which requires sharing derivative code publicly. Since DotKernel projects use MIT, the more restrictive license would have to apply to the whole application, conflicting with contractual obligations to keep client code business friendly. + +**Q: Where was the LGPL v3 package being used?** +A: In DotKernel's dot-user-agent-sniffer package, to identify the user agent and use the results in internal reports. + +**Q: What solution did DotKernel adopt?** +A: Discarding the use of third-party LGPL v3 packages. All DotKernel packages are MIT licensed, and other acceptable non-restrictive licenses include Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MPL-2.0 and OSL-3.0. + +**Q: What changed in dot-user-agent-sniffer as a result?** +A: Starting with version 3.4.0 it must follow LGPL v3. The admin application will not use matomo/device-detector's features, but will document how to add it for developers who want it. + +**Q: Is it still risky to use an older version of dot-user-agent-sniffer?** +A: Yes - any application using a version lower than 3.4.0 is still a legal liability. DotKernel will not include the package in its applications by default while it looks for a less restrictive replacement. + +## Resources + +- [LGPL v3 license](https://www.gnu.org/licenses/lgpl-3.0.en.html) +- [MIT license](https://opensource.org/license/mit) +- [matomo/device-detector](https://github.com/matomo-org/device-detector) +- [dotkernel/dot-user-agent-sniffer](https://github.com/dotkernel/dot-user-agent-sniffer) +- [DotKernel GitHub organization](https://github.com/dotkernel) +- [DotKernel admin application](https://github.com/dotkernel/admin) +- [Rob Allen: Check the licenses of your Composer dependencies](https://akrabat.com/check-licenses-of-composer-dependencies/) diff --git a/public/readme/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.md b/public/readme/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.md new file mode 100644 index 00000000..c990846b --- /dev/null +++ b/public/readme/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.md @@ -0,0 +1,71 @@ +--- +title: "Dotkernel Light improvements: PSR-15 Handlers, Vite, PHPStan" +description: "An overview of recent improvements to Dotkernel Light: migrating to PSR-15 handlers, adopting Vite, switching to PHPStan, and other smaller updates." +author: "Florin Bidirean" +date_published: "2025-05-02" +canonical_url: "https://new.dotkernel.com/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan/" +category: "Middleware" +language: "en" +--- + +# Dotkernel Light improvements: PSR-15 Handlers, Vite, PHPStan + +## TL;DR + +[Dotkernel Light](https://github.com/dotkernel/light) is a PSR-15 compliant application built on Mezzio and Laminas, aimed at simple websites like presentation sites. Since its last update, it has moved from controllers to PSR-15 handlers, adopted Vite as its bundler, replaced Psalm with PHPStan, and picked up several smaller improvements. + +## Migrating from controllers to handlers + +dotkernel/dot-controller isn't going away, but Dotkernel Light has switched to PSR-15 handlers instead. PSR-7 defines the request handler as an individual component that processes a request and produces a response; since the request can be filtered or augmented in a middleware architecture by the time it reaches custom code, handlers split code into more manageable chunks and a cleaner file structure, and make it easier to understand an application's functionality. + +## Implementing PSR-15 compatible handlers + +PSR-15, defined by the PHP Framework Interop Group, specifies common interfaces for HTTP server request handlers and middleware that use HTTP messages as described by PSR-7. The MVC design pattern is considered obsolete by respected members of the PHP community, so Dotkernel Light is replacing it with the middleware and HTTP message design pattern. + +## Adopting a naming pattern for PSR-15 handlers + +A naming pattern was devised for PSR-15 handlers that highlights the method, resource, and action for each file, so navigation and onboarding are easier. + +## Implementing Vite + +[Vite](https://vite.dev/) replaces [webpack](https://webpack.js.org) as the static modules bundler. It concatenates and compresses `.css` and `.js` files for faster downloads, and preprocesses `.scss` files into `.css`. Vite was configured to work similarly to webpack for existing Light developers; the migration was justified by easier dependency management and execution speed, and it's highly recommended by the PHP community. + +## Replacing Psalm with PHPStan + +Both Psalm and PHPStan are respected, widely-used static analysis tools. Some big names in the PHP ecosystem made the same switch, so Dotkernel followed suit. Functionality is similar to the previous tool, but growing interest and improved detection quality made PHPStan the better choice. PHPStan is configured to run at rule level 8 to help prevent bugs and write better code. + +## Other updates + +- Adding support for PHP 8.4. +- Enabling PHPStan and Qodana to run for PHP 8.4 as well. +- Cleaning up and adapting the error configuration file to make use of the latest features. +- Updating laminas/laminas-coding-standard to the latest major version. +- Updating composer.json to be up-to-date with the latest releases for each dependency, and removing obsolete items. +- Implementing a more reliable and efficient post-install script. +- Removing dotkernel/dot-twigrenderer in favor of a direct implementation of mezzio/mezzio-twigrenderer. + +## FAQ + +**Q: Why did Dotkernel Light switch from controllers to handlers?** +A: Handlers split code into more manageable chunks and a cleaner file structure, and make it easier to understand an application's functionality, especially given that requests can be filtered or augmented by middleware before reaching custom code. + +**Q: What naming pattern is used for the new PSR-15 handlers?** +A: One that highlights the method, resource, and action for each handler file, so file names reflect functionality at a glance. + +**Q: Why was Vite adopted instead of webpack?** +A: For easier dependency management and execution speed, and because it's highly recommended by the PHP community; it was configured to work similarly to webpack for existing developers. + +**Q: Why was Psalm replaced with PHPStan?** +A: Growing interest and improved detection quality, following the lead of other big names in the PHP ecosystem; PHPStan runs at rule level 8. + +**Q: What other smaller updates were made to Dotkernel Light?** +A: PHP 8.4 support, PHPStan/Qodana running on PHP 8.4, error configuration cleanup, an updated coding standard, refreshed composer.json dependencies, a more reliable post-install script, and removal of dot-twigrenderer in favor of mezzio-twigrenderer directly. + +## Resources + +- [Dotkernel Light Git](https://github.com/dotkernel/light) +- [Dotkernel Light demo](https://light.dotkernel.net/) +- [Dotkernel Light documentation](https://docs.dotkernel.org/light-documentation/) +- [PHP Framework Interop Group](https://www.php-fig.org/) +- [PSR-15: HTTP Server Request Handlers](https://www.php-fig.org/psr/psr-15/) +- [Static Analysis – Replacing Psalm with PHPStan](https://www.dotkernel.com/php-development/static-analysis-replacing-psalm-with-phpstan/) diff --git a/public/readme/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.md b/public/readme/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.md new file mode 100644 index 00000000..1d5c014d --- /dev/null +++ b/public/readme/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.md @@ -0,0 +1,86 @@ +--- +title: "Handling dynamic routing using FastRoute in Dotkernel" +description: "How Dotkernel moved from static, hard-coded route declarations to a dynamic, centralized routing configuration." +author: "Florin Bidirean" +date_published: "2025-02-26" +canonical_url: "https://new.dotkernel.com/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel/" +category: "Middleware" +language: "en" +--- + +# Handling dynamic routing using FastRoute in Dotkernel + +## TL;DR + +This article, the first in a series about switching from controllers to PSR-15 compliant handlers, explains how Dotkernel replaced its static, hard-coded route declarations with a centralized, dynamic configuration in `local.php`. The change is aimed at static pages only - any method other than GET (post, put, delete) returns a 405 status code. + +## The old way of doing things + +Routes were declared in a `RoutesDelegator.php` file present in the `src` folder of each module, with entries like: + +```php +$app->get('/page', , 'page'); +``` + +This route serves urls like `/page/about` or `/page/who-we-are`. Each entry required: + +- A **method**, such as `get`, aimed at static pages. +- A **path** used to direct execution to a handler, such as `/page`, which also uses the optional `action` parameter. +- A **handler** to execute, such as `GetPageViewHandler`, designed to display static pages out of the box but expandable as needed. +- A unique **route name**, such as `page`, referenceable for redirects or authorization. + +In a live application the route list can grow to many entries across areas like product list, product details, checkout, contact us, reports, order history, and blog - grouped into modules, meaning a routing error could require digging through multiple RoutesDelegators. + +## The new approach + +The update centralizes route configuration in `config/autoload/local.php`, under a `routes` array. `RoutesDelegator.php` reads this configuration and generates the routes, so it doesn't need to be touched most of the time: + +```php +$routes = $container->get('config') ?? []; +foreach ($routes as $prefix => $moduleRoutes) { + foreach ($moduleRoutes as $routeUri => $templateName) { + $app->get( + sprintf('/%s/%s', $prefix, $routeUri), + GetPageViewHandler::class, + sprintf('%s::%s', $prefix, $templateName) + ); + } +} +``` + +Under the `routes` array: the module name (e.g. `page`) is the top-level key, the key (e.g. `about`) builds the page's path, and the value (e.g. `about`) is the template file. This supports `/page/about` and `/page/who-we-are`. + +## Advanced configuration + +The same versatility as before is retained: + +1. **Change the template**: replace `$template = $request->getAttribute(RouteResult::class)->getMatchedRouteName();` with a fixed template, like `$template = 'my-template';`. +2. **Change the URL for SEO** (e.g. `/page/about` to `/about`): remove the `$moduleName` parameter from `sprintf('/%s/%s', $moduleName, $routeUri)`, making it `sprintf('/%s', $routeUri)` - carefully, to avoid breaking other routes. +3. **Change the URL segment**: edit `'about' => 'about',` to `'about-us' => 'about',` in `local.php`, changing the url to `/page/about-us` while still using the `about` template. +4. **Add a dynamic parameter**: edit `'about' => 'about',` to `'about/{id}' => 'about',`, supporting urls like `/page/about/us`, `/page/about/company`, `/page/about/123`, and read the value in the handler with `$request->getAttribute('id')`. + +## FAQ + +**Q: What is the goal of this dynamic routing update?** +A: To replace static, hard-coded route declarations with a more dynamic, centralized implementation that is easier to set up and review. + +**Q: How were routes configured in the old, static approach?** +A: In a per-module `RoutesDelegator.php` file, with hard-coded entries specifying a method, path, handler, and route name. + +**Q: How does the new approach centralize route configuration?** +A: Route data moves into a `routes` array in `config/autoload/local.php`, which `RoutesDelegator.php` reads to generate routes automatically. + +**Q: What do the entries under the routes array represent?** +A: The module name, the URL path segment (key), and the template file to render (value). + +**Q: Can routes still be customized beyond the default setup?** +A: Yes - templates, URL structure, URL segments, and dynamic parameters can all still be adjusted. + +**Q: Does this update support methods other than GET?** +A: No - it targets static pages only; other HTTP methods return a 405 status code. + +## Resources + +- [Dotkernel Light](https://github.com/dotkernel/light) +- [Dotkernel Light Routing How-to](https://docs.dotkernel.org/light-documentation/v1/how-tos/routing/) +- [FastRoute](https://github.com/nikic/FastRoute) diff --git a/public/readme/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.md b/public/readme/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.md new file mode 100644 index 00000000..319aed2e --- /dev/null +++ b/public/readme/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.md @@ -0,0 +1,141 @@ +--- +title: "Replacing controllers with PSR-15 compliant handlers in Dotkernel Light" +description: "Why and how Dotkernel Light replaced its controllers with PSR-15 compliant handlers, including a worked GetPageViewHandler example." +author: "Florin Bidirean" +date_published: "2025-03-03" +canonical_url: "https://new.dotkernel.com/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light/" +category: "Middleware" +language: "en" +--- + +# Replacing controllers with PSR-15 compliant handlers in Dotkernel Light + +## TL;DR + +The goal of this update is to implement [PSR-15](https://www.php-fig.org/psr/psr-15/) handlers into [Dotkernel Light](https://github.com/dotkernel/light), keeping the application up-to-date with recommended design guidelines, secure, and aligned with standards widely adopted by the PHP community. + +## What makes handlers better than controllers? + +Handlers split code into manageable chunks instead of one large controller file with several actions, following the first SOLID principle (Single-responsibility). SOLID stands for: + +- S - Single-responsibility Principle +- O - Open-closed Principle +- L - Liskov Substitution Principle +- I - Interface Segregation Principle +- D - Dependency Inversion Principle + +With single-responsibility, handlers separate each action into its own class, which makes them easier to maintain, refactor, and test. Expanding an application is easier too - rather than searching for a place to fit new code into a controller, you simply create a new handler. Refactoring is simpler because there's less code to worry about breaking, and tests only need to inject or bind mocks for a single action per handler instead of covering multiple branches. + +## How to implement the Page handler + +Replacing controllers with handlers in Dotkernel Light means the `dot-controller` package is no longer needed and can be removed, along with any existing Controllers. + +For Dotkernel Light, most of the old Controller's actions were combined under a single Handler, `GetPageViewHandler`, since they all performed the same task - displaying static content. The only exception is `IndexHandler.php`, which was kept separate but is set up similarly. + +Handlers are registered in each module's `ConfigProvider`, mapping factories under `getDependencies()`. Add `GetPageViewHandler` under the `factories` key and remove any reference to `PageController`: + +```php +public function getDependencies(): array +{ + return [ + ..., + 'factories' => [ ... ], + 'aliases' => [ ... ], + ]; +} +``` + +`GetPageViewHandlerFactory.php` adds the template renderer as a dependency, making it available in the handler. `PageControllerFactory.php` is no longer needed and can be deleted: + +```php +get(TemplateRendererInterface::class); + assert($template instanceof TemplateRendererInterface); + + return new GetPageViewHandler($template); + } +} +``` + +`GetPageViewHandler.php` determines the template file name from the route name and displays it. No dynamic elements are included, since it deals only with static pages. `PageController.php` can be deleted: + +```php +getAttribute(RouteResult::class)->getMatchedRouteName(); + + return new HtmlResponse( + $this->template->render($template) + ); + } +} +``` + +These are the bare essentials to get started with handlers for a website that displays static pages. + +## FAQ + +**Q: What is the goal of replacing controllers with PSR-15 handlers?** +A: Keeping Dotkernel Light up-to-date with recommended design guidelines, secure, and aligned with PHP community standards. + +**Q: What makes handlers better than controllers?** +A: They apply the Single-responsibility Principle, splitting actions into their own classes, which makes them easier to maintain, refactor, expand, and test. + +**Q: Do you still need the dot-controller package?** +A: No - it can be removed along with any existing Controllers once handlers replace them. + +**Q: How is GetPageViewHandler set up?** +A: Registered as a factory in the module's ConfigProvider (replacing PageController), with GetPageViewHandlerFactory injecting the template renderer, and the handler resolving the template from the matched route name. + +**Q: Does every action need its own separate handler?** +A: Not necessarily - in Dotkernel Light most static-page actions were combined into one handler, with only IndexHandler.php kept separate; other applications may need to split actions across multiple handlers. + +## Resources + +- [PSR-15](https://www.php-fig.org/psr/psr-15/) +- [Dotkernel Light](https://github.com/dotkernel/light) +- [PR for replacing controllers with handlers](https://github.com/dotkernel/light/pull/33) +- [Mezzio features](https://docs.mezzio.dev/mezzio/v3/getting-started/features/) +- [Single Action Handlers in PHP Frameworks](https://dev.to/ilyasdeckers/single-action-handlers-in-php-frameworks-3jai) diff --git a/public/readme/php-development/end-of-support-for-php-5-2-x-branch.md b/public/readme/php-development/end-of-support-for-php-5-2-x-branch.md new file mode 100644 index 00000000..8b69c590 --- /dev/null +++ b/public/readme/php-development/end-of-support-for-php-5-2-x-branch.md @@ -0,0 +1,39 @@ +--- +title: "End of Support for PHP 5.2.x Branch" +description: "PHP 5.2.14 marks the end of active support for the PHP 5.2 branch, with PHP 5.3.3 released and ready for upgrade." +author: "admin" +date_published: "2010-07-22" +canonical_url: "https://new.dotkernel.com/php-development/end-of-support-for-php-5-2-x-branch/" +category: "PHP Development" +language: "en" +--- + +# End of Support for PHP 5.2.x Branch + +## TL;DR + +PHP 5.2.14 was just released, marking the end of active support for the PHP 5.2.x branch. PHP 5.3.3 was released at the same time, and projects and servers are encouraged to upgrade to the 5.3.x branch. + +## The announcement + +PHP 5.2.14 was just released, and this is the end of support for the 5.2.x branch of PHP. + +> **NOTE:** This release marks the end of the active support for PHP 5.2. Following this release the PHP 5.2 series will receive no further active bug maintenance. Security fixes for PHP 5.2 might be published on a case by case basis. All users of PHP 5.2 are encouraged to upgrade to PHP 5.3 + +PHP 5.3.3 was also just released, so it is time to upgrade every project to the PHP 5.3.x branch, and also upgrade all servers to 5.3.x. + +## FAQ + +**Q: What does the release of PHP 5.2.14 mean for the 5.2.x branch?** +A: PHP 5.2.14 marks the end of active support for the PHP 5.2 branch. After this release, PHP 5.2 will receive no further active bug maintenance, though security fixes might still be published on a case-by-case basis. + +**Q: What should users of PHP 5.2 do?** +A: All users of PHP 5.2 are encouraged to upgrade to PHP 5.3. + +**Q: Which PHP version was just released alongside the end of 5.2.x support?** +A: PHP 5.3.3 was just released, so it is time to upgrade every project to the PHP 5.3.x branch and also upgrade all servers to 5.3.x. + +## Resources + +- [PHP 5.2.14 changelog](http://www.php.net/ChangeLog-5.php#5.2.14) +- [PHP 5.3.3 changelog](http://www.php.net/ChangeLog-5.php#5.3.3) diff --git a/public/readme/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.md b/public/readme/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.md new file mode 100644 index 00000000..acd25ce5 --- /dev/null +++ b/public/readme/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.md @@ -0,0 +1,99 @@ +--- +title: "Floating-Point Arithmetic - Why is (int)((0.7+0.1)*10) = 7 ?" +description: "An explanation of why PHP's floating-point arithmetic and int casting can produce unexpected results like (int)((0.7+0.1)*10) = 7 instead of 8." +author: "Gabi DJ" +date_published: "2016-01-26" +canonical_url: "https://new.dotkernel.com/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7/" +category: "PHP Development" +language: "en" +--- + +# Floating-Point Arithmetic - Why is (int)((0.7+0.1)*10) = 7 ? + +## TL;DR + +This applies to PHP 5.x and PHP 7. Floating-point arithmetic doesn't always produce the results you'd expect, especially when casting values to int, because numbers like 0.7 and 0.1 cannot be represented exactly in binary. The result is that `(int)((0.7+0.1)*10)` evaluates to 7 instead of the mathematically expected 8. + +## The problem + +While using floating-point arithmetic you might have noticed that not all calculation results are as expected — this can usually be observed when casting values. + +The output for `(0.7 + 0.1) * 10` is: + +```php +var_dump((0.7+0.1)*10); # float(8) +var_dump(intval((0.7+0.1)*10)); # int(7) +``` + +The same thing happens with 0.6 instead of 0.7: + +```php +var_dump((0.6+0.1)*10); # float(7) +var_dump(intval((0.6+0.1)*10)); # int(7) +``` + +## How does the CPU understand these numbers? + +The CPU makes calculations in binary; floating-point numbers are represented as follows: + +| Format | Size | Sign | Exponent | Mantissa | +|---|---|---|---|---| +| IEEE Short Real (single precision) | 32 bits | 1 bit | 8 bits | 23 bits | +| IEEE Long Real (double precision) | 64 bits | 1 bit | 11 bits | 52 bits | + +Numbers that can easily be represented in binary are 1/(2^1), 1/(2^2), 1/(2^3), 1/(2^4), etc., because they have a mantissa equal to 1 (encoded as 0). + +A number's value can only be represented exactly if it can be expressed by the formula **exponent * mantissa**. The mantissa is the number the exponent is multiplied by, and its value is 1 + 1/rb, where rb is the reverse binary interpretation. + +For example, take the number 3.5: 3.5 = sign(1) * exponent(2) * mantissa(1.75). + +Mantissa: 1 + 11000000000000000000000 → 1/(2^0) + 1/(2^1) + 1/(2^2) + 0*(2^3) + ... + 0*(2^23) → 1 + 0.5 + 0.25 → 1.75 + +Some numbers cannot be represented exactly (such as 0.99999999). + +## Why does this happen? + +Using an IEEE 754 converter, it turns out that: + +- 0.7 is actually represented as 0.699999988079071 +- 0.1 is actually represented as 0.10000000149011612 + +Adding these two values gives 0.7999999895691871. Multiplying by 10 gives 7.999999895691871, which when cast to int is 7 — the same way 3.5 is 3 when cast to int. + +The 0.6 example still shows 7 because 0.6 is actually represented as 0.6000000238418579, and (0.6000000238418579 + 0.10000000149011612) * 10 is 7.00000025331974. + +## But still ... + +If you use echo and var_dump, or apply mathematical operations, PHP automatically adjusts the values — but intval and casting to int work on the underlying bits before those values were adjusted: + +```php +var_dump((0.7+0.1)*10); # float(8) +var_dump(intval( ((0.7+0.1)*10) ) ); # int(7) +var_dump(intval( ((0.7+0.1)*10)+1 ) ); # int(9) +``` + +If these values are important for your project, you can get correct results by using the BCMath PHP Extension. + +## FAQ + +**Q: Why does (int)((0.7+0.1)*10) return 7 instead of 8?** +A: Because 0.7 is actually represented internally as 0.699999988079071 and 0.1 as 0.10000000149011612. Adding them gives 0.7999999895691871, and multiplying by 10 gives 7.999999895691871, which truncates to 7 when cast to int. + +**Q: Does the same rounding issue happen with 0.6 instead of 0.7?** +A: Yes. 0.6 is represented as 0.6000000238418579, and (0.6000000238418579 + 0.10000000149011612) * 10 equals about 7.00000025331974, which also truncates to int(7). + +**Q: Why do var_dump and echo show the expected value while intval/int casting does not?** +A: When using echo, var_dump, or mathematical operations, PHP automatically adjusts the displayed values. However, intval and casting to int operate on the underlying bits before those values were adjusted, which is why they can produce a different (truncated) result. + +**Q: How are floating-point numbers represented at the CPU level?** +A: The CPU performs calculations in binary. IEEE Short Real (single precision) uses 32 bits: 1 sign bit, 8 exponent bits, and 23 mantissa bits. IEEE Long Real (double precision) uses 64 bits: 1 sign bit, 11 exponent bits, and 52 mantissa bits. A number can only be represented exactly if it can be expressed as exponent * mantissa; numbers like 0.99999999 cannot be represented exactly. + +**Q: Is there a way to get accurate results for calculations like this in PHP?** +A: If precise values matter for your project, the article recommends using the BCMath PHP Extension to get correct results. + +## Resources + +- [Floating point tutorial (kipirvine.com)](http://kipirvine.com/asm/workbook/floating_tut.htm) +- [IEEE 754 converter (h-schmidt.net)](http://www.h-schmidt.net/FloatConverter/IEEE754.html) +- [BCMath PHP Extension documentation](http://php.net/manual/en/book.bc.php) +- [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) diff --git a/public/readme/php-development/how-to-upgrade-wamp-to-php-5-3-4.md b/public/readme/php-development/how-to-upgrade-wamp-to-php-5-3-4.md new file mode 100644 index 00000000..6f3b44c7 --- /dev/null +++ b/public/readme/php-development/how-to-upgrade-wamp-to-php-5-3-4.md @@ -0,0 +1,53 @@ +--- +title: "How To Upgrade Wamp to PHP 5.3.4" +description: "Step-by-step instructions for upgrading a WAMP server's PHP version to 5.3.4." +author: "admin" +date_published: "2010-12-13" +canonical_url: "https://new.dotkernel.com/php-development/how-to-upgrade-wamp-to-php-5-3-4/" +category: "PHP Development" +language: "en" +--- + +# How To Upgrade Wamp to PHP 5.3.4 + +## TL;DR + +A step-by-step guide to manually upgrading the PHP version used by a WAMP server to PHP 5.3.4, by downloading the VC6 Thread Safe build, copying over configuration files, and switching the active PHP version in WAMP. + +## Steps + +1. Stop the WAMP server. +2. Go to [windows.php.net](http://windows.php.net/download/) and download the latest ZIPPED package for PHP 5.3.4. Make sure it is the **VC6 Thread Safe build** — do not download the installer. +3. Create a folder `php5.3.4` inside `wamp/bin/php`. +4. Extract the downloaded zip into the newly created `php5.3.4` folder. +5. Copy the files `php.ini`, `phpForApache.ini`, and `wampserver.conf` from your existing PHP 5.3 folder (e.g. `wamp/bin/php/php5.3.3`) to the new `php5.3.4` folder. +6. Open the files `php.ini` and `phpForApache.ini` and search/replace the string `5.3.3` with `5.3.4`. +7. Go to `wamp/bin/apache/apache/apache2.2.11/bin` and delete the file called `php.ini`. +8. Restart the WAMP server. +9. Choose PHP version 5.3.0. +10. Restart the WAMP server. +11. Now choose PHP version 5.3.4. +12. Check that the PEAR path is correct in `php.ini`, and modify accordingly. +13. Restart the WAMP server. +14. Enjoy. + +## FAQ + +**Q: Where do I download the PHP 5.3.4 package for WAMP?** +A: Download the latest ZIPPED package for PHP 5.3.4 from windows.php.net. Make sure it is the VC6 Thread Safe build, and do not download the installer. + +**Q: Where should the downloaded PHP 5.3.4 files be extracted?** +A: Create a folder named php5.3.4 inside wamp/bin/php, then extract the downloaded zip into that newly created folder. + +**Q: Which configuration files need to be copied and edited when upgrading?** +A: Copy php.ini, phpForApache.ini, and wampserver.conf from the existing php5.3 folder (e.g. wamp/bin/php/php5.3.3) into the new php5.3.4 folder. Then, in php.ini and phpForApache.ini, search and replace the string 5.3.3 with 5.3.4. + +**Q: Is there anything to remove from the Apache folder before restarting WAMP?** +A: Yes. Go to wamp/bin/apache/apache/apache2.2.11/bin and delete the file called php.ini, then restart the WAMP server. + +**Q: How do I actually switch WAMP to the new PHP version after restarting?** +A: After restarting WAMP, choose PHP version 5.3.0, restart WAMP server again, and then choose PHP version 5.3.4. Also check that the PEAR path is correct in php.ini and modify it accordingly, then restart WAMP server once more. + +## Resources + +- [windows.php.net downloads](http://windows.php.net/download/) diff --git a/public/readme/php-development/mezzio-app-development-in-wsl2.md b/public/readme/php-development/mezzio-app-development-in-wsl2.md new file mode 100644 index 00000000..84fa234a --- /dev/null +++ b/public/readme/php-development/mezzio-app-development-in-wsl2.md @@ -0,0 +1,62 @@ +--- +title: "Mezzio app development in WSL2" +description: "A step-by-step guide to installing a Mezzio application (DotKernel API) inside WSL2, running it on Ubuntu 20.04 LTS." +author: "Alex Karajos" +date_published: "2022-09-08" +canonical_url: "https://new.dotkernel.com/php-development/mezzio-app-development-in-wsl2/" +category: "PHP Development" +language: "en" +--- + +# Mezzio app development in WSL2 + +## TL;DR + +This article runs through the steps of installing a Mezzio application (DotKernel API) in WSL2 and running it on Ubuntu 20.04 LTS, from installing WSL2 itself to configuring PHPStorm to work with the WSL2 file system. + +## Steps + +1. Make sure WSL2 is installed on your machine, following the [WSL2 installation guide](https://github.com/dotkernel/development/blob/main/wsl/README.md). +2. Install Ubuntu 20.04 LTS inside WSL2, as described in the [Ubuntu 20 setup guide](https://github.com/dotkernel/development/blob/main/wsl/os/ubuntu20/README.md). +3. Create a virtual host for your project using the [virtual hosts guide](https://github.com/dotkernel/development/blob/main/wsl/os/ubuntu20/README.md#create-virtual-hosts). +4. Using your terminal, move into the virtual host directory you just created: + + ```shell + cd /home/your-username/projects/your-virtualhost + ``` + + Install DotKernel API by following the [DotKernel API guide](https://github.com/dotkernel/api). Make sure the `data` and `log` directories are writable by changing their permissions, as described in the [common permission issues guide](https://github.com/dotkernel/development/blob/main/wsl/HELP.md#fix-common-permission-issues). +5. Set up PHPStorm to work with WSL2 files, as described in the [JetBrains WSL development environment article](https://www.jetbrains.com/help/phpstorm/how-to-use-wsl-development-environment-in-product.html). + +## Note + +There is a guide for AlmaLinux 8 as well, but it is not fully functional because of a well-known issue regarding running systemd inside WSL2. + +## FAQ + +**Q: What does this guide help you accomplish?** +A: It walks you through installing a Mezzio application (DotKernel API) using WSL2 and running it on Ubuntu 20.04 LTS. + +**Q: What do you need before installing Ubuntu inside WSL2?** +A: You first need WSL2 installed on your machine, which you can set up by following the linked WSL2 installation guide, before installing Ubuntu inside it. + +**Q: How do you set up a virtual host for the project?** +A: After Ubuntu is installed inside WSL2, you create a virtual host for your project by following the linked virtual-host creation guide. + +**Q: How do you install DotKernel API once the virtual host is ready?** +A: Using your terminal, move into the virtual host directory you created (for example, `cd /home/your-username/projects/your-virtualhost`), then install DotKernel API by following its guide. Afterward, make sure the `data` and `log` directories are writable by changing their permissions, as described in the linked permission-fix guide. + +**Q: How do you get PHPStorm to work with the WSL2 project files?** +A: You set up PHPStorm to work with WSL2 files by following the linked JetBrains article on using a WSL development environment in the product. + +**Q: Does this guide also work for AlmaLinux instead of Ubuntu?** +A: There is a guide for AlmaLinux 8 as well, but it is not fully functional because of a well-known issue regarding running systemd inside WSL2. + +## Resources + +- [WSL2 installation guide](https://github.com/dotkernel/development/blob/main/wsl/README.md) +- [Ubuntu 20 setup inside WSL2](https://github.com/dotkernel/development/blob/main/wsl/os/ubuntu20/README.md) +- [Create virtual hosts guide](https://github.com/dotkernel/development/blob/main/wsl/os/ubuntu20/README.md#create-virtual-hosts) +- [DotKernel API installation guide](https://github.com/dotkernel/api) +- [Fix common permission issues](https://github.com/dotkernel/development/blob/main/wsl/HELP.md#fix-common-permission-issues) +- [Using WSL development environment in PHPStorm](https://www.jetbrains.com/help/phpstorm/how-to-use-wsl-development-environment-in-product.html) diff --git a/public/readme/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.md b/public/readme/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.md new file mode 100644 index 00000000..ecc989cf --- /dev/null +++ b/public/readme/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.md @@ -0,0 +1,29 @@ +--- +title: "PHP 5.3.6 released. No upgrade possible for WampServer." +description: "PHP 5.3.6 dropped Visual Studio C++ 6 Windows builds, making an in-place WampServer upgrade impossible since WampServer itself is built with VC++ 6." +author: "admin" +date_published: "2011-03-18" +canonical_url: "https://new.dotkernel.com/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver/" +category: "PHP Development" +language: "en" +--- + +# PHP 5.3.6 released. No upgrade possible for WampServer. + +PHP 5.3.6 was released, but it comes with bad news for WampServer users. The release notes state: + +> Windows users: please mind that we do no longer provide builds created with Visual Studio C++ 6 + +Since WampServer is built using VC++ 6, an upgrade isn't possible without a rewrite of the entire WampServer package from scratch. + +## FAQ + +**Q: Why can't WampServer users upgrade to PHP 5.3.6?** +A: PHP 5.3.6 no longer provides builds created with Visual Studio C++ 6 for Windows users. Since WampServer is built using VC++ 6, upgrading isn't possible without rewriting the entire WampServer package from scratch. + +**Q: Where can WampServer users find more details about this issue?** +A: More details are available on the WampServer forum, linked in this article. + +## Resources + +- [WampServer forum discussion](http://www.wampserver.com/phorum/read.php?2,72243) diff --git a/public/readme/php-development/php-environment-development-staging-production.md b/public/readme/php-development/php-environment-development-staging-production.md new file mode 100644 index 00000000..6dfa1866 --- /dev/null +++ b/public/readme/php-development/php-environment-development-staging-production.md @@ -0,0 +1,52 @@ +--- +title: "PHP Environment : Development Staging Production" +description: "An overview of the development, staging and production server environments used to improve the development, testing and release process of client-server applications." +author: "Teo" +date_published: "2010-07-30" +canonical_url: "https://new.dotkernel.com/php-development/php-environment-development-staging-production/" +category: "PHP Development" +language: "en" +--- + +# PHP Environment : Development Staging Production + +## TL;DR + +In hosted software development, an environment is a server tier designated to a specific stage of a release process. The three most common environments are Development, Staging and Production, and applications are typically moved between them using Subversion source control. + +## Development + +This is where the software is developed - it's the working environment for individual developers or small teams. Its purpose is for the developer to work on local host, separate from the rest of the team, allowing them to make changes without worrying that it may alter the work of other team members. + +## Staging + +Staging is used to assemble, test and review the application before it goes into production. It usually tries to simulate the production environment as much as possible, both hardware- and software-wise. Normally, before releasing an update to the production environment, the update must be tested on staging first. This environment can also be used as a demonstration or training environment. + +## Production + +Production is the "live" environment, where the final application goes out to the world and becomes active. + +## Switching between environments + +To switch from one environment to another, the article recommends using Subversion (SVN) source code. The "Using SVN on Aptana" article explains how to set up a development environment on a local computer and then move it to a staging environment. + +## FAQ + +**Q: What is a "server environment" in hosted software development?** +A: It refers to a server tier designated to a specific stage in a release process. The purpose of using different environments is to improve the development, testing and release processes in client-server applications. + +**Q: What is the development environment used for?** +A: It's the working environment for individual developers or small teams, where the developer works on local host, separate from the rest of the team, allowing changes to be made without worrying about altering the work of other team members. + +**Q: What is the staging environment used for?** +A: It's used to assemble, test and review the application before it goes into production, and it usually tries to simulate the production environment as closely as possible, both hardware- and software-wise. Before releasing an update to production, it must normally be tested on staging first, which can also serve as a demonstration or training environment. + +**Q: What is the production environment?** +A: It's the "live" environment, where the final application goes out to the world and becomes active. + +**Q: How do you switch an application from one environment to another?** +A: The article recommends using Subversion source code to switch from one environment to another, and points to the "Using SVN on Aptana" article for details on setting up a development environment locally and then moving it to a staging environment. + +## Resources + +- [Software development practice](http://dltj.org/article/software-development-practice/) diff --git a/public/readme/php-development/php-support-back-in-aptana-3-0.md b/public/readme/php-development/php-support-back-in-aptana-3-0.md new file mode 100644 index 00000000..1ee81701 --- /dev/null +++ b/public/readme/php-development/php-support-back-in-aptana-3-0.md @@ -0,0 +1,47 @@ +--- +title: "PHP support back in Aptana 3.0" +description: "Aptana PHP support is coming back and will be integrated directly into the Studio 3 core rather than as a separate plugin." +author: "admin" +date_published: "2010-06-11" +canonical_url: "https://new.dotkernel.com/php-development/php-support-back-in-aptana-3-0/" +category: "PHP Development" +language: "en" +--- + +# PHP support back in Aptana 3.0 + +## TL;DR + +Aptana 3.0, then in beta, was set to bring PHP support back - and this time it would be built directly into the Studio 3 core rather than shipped as a separate plugin. A PHP debugger was also announced, to arrive as a separate set of plugins a few weeks later. + +## The announcement + +Aptana 3.0 was in beta stage at the time of writing, available for download from the official Aptana site. + +The good news, based on an announcement from Aptana's own forum, was that Aptana PHP support was coming back - and would be integrated into Studio 3 core, not delivered as a separate plugin. + +Aptana's Shalom G. shared the following in the forum announcement: + +> Aptana PHP is coming back. We are in the process of developing and integrating it into the Studio 3. +> The debugger will also be introduced, but it will take a few weeks till you'll be able to get it. +> +> At the moment, the idea is to provide the PHP debugger as a separate set of plugins that you will be able to grab from the Studio. The majority of the PHP editing capabilities will be integrated into, and delivered with, the Studio core. +> +> Cheers, +> -Shalom G + +## FAQ + +**Q: Is Aptana 3.0 available yet, and where can it be downloaded?** +A: Aptana 3.0 is in beta stage and can be downloaded from the official Aptana site. + +**Q: Will PHP support in Aptana 3.0 be a separate plugin?** +A: No. According to an announcement on Aptana's forum, PHP support is coming back and is being developed and integrated directly into the Studio 3 core, rather than delivered as a separate plugin. + +**Q: What about the PHP debugger?** +A: The debugger will also be introduced, but it will take a few weeks to become available. The plan is to provide the PHP debugger as a separate set of plugins you can grab from the Studio, while the majority of the PHP editing capabilities will be integrated into and delivered with the Studio core. + +## Resources + +- [Aptana Studio 3 official product page](http://www.aptana.com/products/studio3) +- [Aptana forum announcement thread](https://aptanastudio.tenderapp.com/discussions/questions/419-php-in-studio-3) diff --git a/public/readme/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.md b/public/readme/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.md new file mode 100644 index 00000000..bfb99dad --- /dev/null +++ b/public/readme/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.md @@ -0,0 +1,65 @@ +--- +title: "Protection against SQL Injection using PDO and Zend Framework - part 2" +description: "A closer look at Zend_Db's query, fetchAll, insert, update, and delete methods, and where SQL injection can still slip through even with prepared statements." +author: "Teo" +date_published: "2010-06-18" +canonical_url: "https://new.dotkernel.com/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2/" +category: "PHP Development" +language: "en" +--- + +# Protection against SQL Injection using PDO and Zend Framework - part 2 + +## TL;DR + +Following up on the earlier SQL Injection article, this part digs into the specific methods of Zend_Db (and related classes Zend_Db_Statement, Zend_Db_Select, Zend_Db_Tables) to show exactly when their use of prepared statements does, and does not, protect against SQL injection - and offers a quick type-casting tip for WHERE clauses. + +## Zend_Db and related classes + +Zend_Db is the primary class used for accessing the database, but there is more: Zend_Db_Statement, Zend_Db_Select and Zend_Db_Tables. + +## What you should know about their methods + +| Method | Behavior | +| --- | --- | +| `query(mixed $sql, ...)` | Uses prepared statements internally, but SQL Injection is still possible if `$sql` is dynamically created. | +| `fetchAll(string\|Zend_Db_Select $sql, ...)` | All the fetch methods use prepared statements internally, but SQL Injection is still possible if `$sql` is dynamically created. | +| `insert(mixed $table, $bind)` | Uses prepared statements internally, so SQL Injection is not possible. | +| `update(mixed $table, $bind, ...)` | Uses prepared statements internally, but SQL Injection may be possible if `$where` is created dynamically. | +| `delete(mixed $table, ...)` | SQL Injection may be possible if `$where` is created dynamically. | + +**Note:** even if you use prepared statements via Zend_Db methods, SQL Injection is still possible if the `WHERE` and `ORDER BY` clauses are wrongly written, so pay attention to them. + +## A quick tip for WHERE clauses + +A short tip: you can use type casting to avoid SQL Injection in a WHERE clause where possible. + +```php +$sql = 'SELECT * FROM table WHERE id = ' . (int)$_POST; +``` + +## FAQ + +**Q: What is the focus of this article?** +A: Following the earlier article about SQL Injection, this article makes a stronger argument for using Zend Framework to handle database access. Zend_Db is the primary class used for accessing the database, but there is more to it: Zend_Db_Statement, Zend_Db_Select, and Zend_Db_Tables. + +**Q: Does Zend_Db's query() method protect against SQL injection?** +A: query() uses prepared statements internally, but SQL Injection is still possible if the $sql parameter passed to it is dynamically created. + +**Q: What about fetchAll() and the other fetch methods?** +A: All of the fetch methods use prepared statements internally, but SQL Injection is still possible if the $sql is dynamically created. + +**Q: Is insert() safe from SQL injection?** +A: Yes. insert() uses prepared statements internally, so SQL Injection is not possible with it. + +**Q: What about update() and delete()?** +A: update() uses prepared statements internally, but SQL Injection may be possible if the $where clause is created dynamically. Likewise, with delete(), SQL Injection may be possible if $where is created dynamically. + +**Q: Even with prepared statements, when can SQL injection still happen, and what tip helps avoid it in WHERE clauses?** +A: Even when using prepared statements via Zend_Db methods, SQL Injection is still possible if the WHERE and ORDER BY clauses are wrongly written, so these deserve special attention. A short tip mentioned is to use type casting to avoid SQL Injection in a WHERE clause where possible, for example: `$sql = 'SELECT * FROM table WHERE id = ' . (int)$_POST;` + +## Resources + +- [Protection against SQL Injection using PDO and Zend Framework (part 1)](http://www.dotkernel.com/php-development/protection-against-sql-injection-using-pdo-and-zend-framework/) +- [Secure Programming with the Zend Framework (Stefan Esser slides)](http://www.suspekt.org/downloads/DPC_Secure_Programming_With_The_Zend_Framework.pdf) +- [Type casting in PHP - what's the point?](http://www.dustinweber.com/main-page/type-casting-in-php-whats-the-point/) diff --git a/public/readme/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.md b/public/readme/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.md new file mode 100644 index 00000000..93681bf3 --- /dev/null +++ b/public/readme/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.md @@ -0,0 +1,70 @@ +--- +title: "Protection against SQL Injection using PDO and Zend Framework" +description: "An overview of what SQL injection is, what PDO provides, and how prepared statements in Zend Framework help - but don't fully guarantee - protection against SQL injection." +author: "admin" +date_published: "2010-06-16" +canonical_url: "https://new.dotkernel.com/php-development/protection-against-sql-injection-using-pdo-and-zend-framework/" +category: "PHP Development" +language: "en" +--- + +# Protection against SQL Injection using PDO and Zend Framework + +## TL;DR + +SQL injection exploits unfiltered user input passed into SQL statements. PDO (PHP Data Objects) is a standardized database access layer that provides a data-access abstraction (not a database abstraction) and offers several benefits, including help protecting against SQL injection. In Zend Framework, prepared statements are encouraged since they handle parameter escaping, but they are not a complete guarantee against SQL injection - especially with PDO_MySQL, and with WHERE IN / ORDER BY clauses. + +## What is SQL injection? + +SQL injection is a technique that exploits a security vulnerability occurring in the database layer of an application. Usually, user input is not filtered by the script and is passed into a SQL statement. + +## What is PDO? + +PDO - PHP Data Objects - is a database access layer providing a standardized method of access to multiple databases. PDO provides a data-access abstraction layer, meaning that regardless of which database you're using, you apply the same functions to issue queries and fetch data. PDO does not provide a database abstraction; it doesn't rewrite SQL or emulate missing features. + +Among PDO's benefits are: + +- Access methods that allow complete control over how attributes are read and written +- Validation on a per-record and per-attribute level +- Easier fetching of objects from related tables +- Reusable logic - the same codebase is much easier to maintain +- Cleaner code by using object-oriented code +- Fewer errors from SQL query generation +- Protection against SQL injection + +## Prepared statements in Zend Framework + +In Zend Framework, database access methods usually support prepared statements. Dynamic SQL queries are allowed, but all parameters must be escaped, otherwise SQL injection is possible. Because of this, prepared statements are encouraged, since they handle escaping parameters for you. + +Most people believe that using prepared statements makes them 100% protected from SQL injection, but this is not true. Input data should always be validated and sanitized, and PDO should be seen as another line of defense. PDO does not protect against other security vulnerabilities like XSS (cross-site scripting), but it does help protect an application against SQL injection. + +## The PDO_MySQL risk + +A problem can occur in Zend Framework where SQL injection is possible even while using PDO_MySQL. PDO_MySQL is more dangerous than traditional MySQL applications: traditional MySQL allows only a single SQL query, while PDO_MySQL has no such limitation, so there is a risk of being injected with multiple queries. To avoid this, use the correct prepared statements provided by Zend Framework. Also pay attention when your SQL query has `WHERE IN` and `ORDER BY` clauses, since these cannot normally be handled correctly by prepared statements - in this case, escape the data instead. + +Zend_Db provides two escaping methods for this purpose: `quote()` and `quoteIdentifier()`. Both handle strings by putting them between single quotes. + +## FAQ + +**Q: What is SQL injection?** +A: SQL injection is a technique that exploits a security vulnerability occurring in the database layer of an application. Usually, user input is not filtered by the script and is passed directly into a SQL statement. + +**Q: What is PDO and what kind of abstraction does it provide?** +A: PDO (PHP Data Objects) is a database access layer providing a standardized method of access to multiple databases. It provides a data-access abstraction layer, meaning you apply the same functions to issue queries and fetch data regardless of which database you're using. However, PDO does not provide a database abstraction - it doesn't rewrite SQL or emulate missing database features. + +**Q: What are the benefits of using PDO?** +A: Among the benefits are: access methods that allow complete control over how attributes are read and written, validation on a per-record and per-attribute level, easier fetching of objects from related tables, reusable logic that makes the codebase easier to maintain, cleaner code through object-oriented programming, fewer errors from SQL query generation, and protection against SQL injection. + +**Q: Does using prepared statements in Zend Framework fully protect against SQL injection?** +A: Not entirely. Zend Framework's database access methods usually support prepared statements, which are encouraged because they handle escaping parameters for you, but dynamic SQL queries are still allowed and any parameters must be escaped manually or SQL injection becomes possible. Many people believe prepared statements offer 100% protection, but this isn't true - input data should always be validated and sanitized, with PDO treated as another line of defense. PDO also does not protect against other vulnerabilities such as XSS (cross-site scripting). + +**Q: Why can PDO_MySQL be riskier than traditional MySQL usage, and how can this be avoided?** +A: Traditional MySQL allows only a single SQL query at a time, but PDO_MySQL has no such limitation, meaning there is more risk of being injected with multiple queries. To avoid this, you should use the correct prepared statements from Zend Framework, and pay particular attention to WHERE IN and ORDER BY clauses, since they aren't normally handled correctly by prepared statements - in these cases you should escape the data yourself. + +**Q: What escaping methods does Zend_Db provide?** +A: Zend_Db has two escaping methods that can be used: quote() and quoteIdentifier(). Both of these methods handle strings by putting them between single quotes. + +## Resources + +- [SQL Injection Protection in PHP With PDO (ezinearticles.com)](http://ezinearticles.com/?SQL-Injection-Protection-in-PHP-With-PDO&id=1815110) +- [Zend Webinar: Secure Application Development with the ZF](http://www.zend.com/webinar/Framework/70170000000bEs9-webinar-secure-application-development-with-the-ZF-20100505.flv) diff --git a/src/App/assets/scss/components/_custom.scss b/src/App/assets/scss/components/_custom.scss index bd4c831f..24ca67d5 100644 --- a/src/App/assets/scss/components/_custom.scss +++ b/src/App/assets/scss/components/_custom.scss @@ -844,6 +844,7 @@ details.acc { .read-more { margin-top: 14px; + margin-left: 2px; font-family: var(--mono); font-size: 12px; color: var(--amber); diff --git a/src/App/templates/app/index.html.twig b/src/App/templates/app/index.html.twig index c4167642..e43370ba 100644 --- a/src/App/templates/app/index.html.twig +++ b/src/App/templates/app/index.html.twig @@ -97,10 +97,10 @@
                          Skeleton

                          API

                        - PHP - Packagist - License - Stars + PHP + Packagist + License + Stars

                        An alternative to legacy Laminas API Tools (Apigility). A framework-less, opinionated toolkit for intermediate-to-advanced developers who want to ship REST APIs quickly and efficiently.

                        @@ -115,10 +115,10 @@
                        Skeleton

                        Admin

                        - PHP - Packagist - License - Stars + PHP + Packagist + License + Stars

                        A ready-to-go admin skeleton for managing your platform's records with a simple, table-based approach - plus the tools to build reports and dashboards.

                        @@ -133,10 +133,10 @@
                        Skeleton

                        Queue

                        - PHP - Packagist - License - Stars + PHP + Packagist + License + Stars

                        Asynchronous task queuing built on Symfony Messenger, wired into the Laminas Service Manager container via netglue/laminas-messenger.

                        @@ -164,10 +164,10 @@
                        Skeleton . Didactic

                        Light

                        - PHP - Packagist - License - Stars + PHP + Packagist + License + Stars

                        A minimal, PSR-15 compliant skeleton built for learning purposes - a simple, didactic example of how to put together a basic website, not a Headless Platform component.

                        @@ -182,10 +182,10 @@
                        Skeleton . Standalone

                        Frontend

                        - PHP - Packagist - License - Stars + PHP + Packagist + License + Stars

                        A web starter skeleton for building frontend applications on Mezzio and Laminas - a standalone alternative, separate from the Headless Platform.

                        diff --git a/src/App/templates/partial/meta.html.twig b/src/App/templates/partial/meta.html.twig index ad96c49f..c8b39712 100644 --- a/src/App/templates/partial/meta.html.twig +++ b/src/App/templates/partial/meta.html.twig @@ -9,9 +9,8 @@ - + - diff --git a/src/Blog/templates/page/JSON-LD/android/listen-for-android-install-referrer.jsonld.twig b/src/Blog/templates/page/JSON-LD/android/listen-for-android-install-referrer.jsonld.twig index 920a2e2a..6e277570 100644 --- a/src/Blog/templates/page/JSON-LD/android/listen-for-android-install-referrer.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/android/listen-for-android-install-referrer.jsonld.twig @@ -1,27 +1,78 @@ diff --git a/src/Blog/templates/page/JSON-LD/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.jsonld.twig b/src/Blog/templates/page/JSON-LD/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.jsonld.twig index 920a2e2a..4c312a76 100644 --- a/src/Blog/templates/page/JSON-LD/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.jsonld.twig @@ -1,27 +1,64 @@ diff --git a/src/Blog/templates/page/JSON-LD/architecture/configprovider-bootstrap-modern-php-applications.jsonld.twig b/src/Blog/templates/page/JSON-LD/architecture/configprovider-bootstrap-modern-php-applications.jsonld.twig index 920a2e2a..6976e7ae 100644 --- a/src/Blog/templates/page/JSON-LD/architecture/configprovider-bootstrap-modern-php-applications.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/architecture/configprovider-bootstrap-modern-php-applications.jsonld.twig @@ -1,27 +1,96 @@ diff --git a/src/Blog/templates/page/JSON-LD/architecture/request-lifecycle-for-a-mezzio-based-application.jsonld.twig b/src/Blog/templates/page/JSON-LD/architecture/request-lifecycle-for-a-mezzio-based-application.jsonld.twig index 920a2e2a..6e6b94c2 100644 --- a/src/Blog/templates/page/JSON-LD/architecture/request-lifecycle-for-a-mezzio-based-application.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/architecture/request-lifecycle-for-a-mezzio-based-application.jsonld.twig @@ -1,27 +1,96 @@ diff --git a/src/Blog/templates/page/JSON-LD/architecture/understanding-middleware.jsonld.twig b/src/Blog/templates/page/JSON-LD/architecture/understanding-middleware.jsonld.twig index 920a2e2a..d9d86f6c 100644 --- a/src/Blog/templates/page/JSON-LD/architecture/understanding-middleware.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/architecture/understanding-middleware.jsonld.twig @@ -1,27 +1,96 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/aptana-set-svn-keywords.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/aptana-set-svn-keywords.jsonld.twig index 920a2e2a..84f03941 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/aptana-set-svn-keywords.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/aptana-set-svn-keywords.jsonld.twig @@ -1,27 +1,72 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/basic-security-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/basic-security-in-dotkernel-headless-platform.jsonld.twig index 920a2e2a..4e67a7fb 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/basic-security-in-dotkernel-headless-platform.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/basic-security-in-dotkernel-headless-platform.jsonld.twig @@ -1,27 +1,96 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/golden-rules-of-professional-php-coding.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/golden-rules-of-professional-php-coding.jsonld.twig index 920a2e2a..4187b1a0 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/golden-rules-of-professional-php-coding.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/golden-rules-of-professional-php-coding.jsonld.twig @@ -1,27 +1,88 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/htaccess-301-redirect-non-www-to-www.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/htaccess-301-redirect-non-www-to-www.jsonld.twig index 920a2e2a..f15950c1 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/htaccess-301-redirect-non-www-to-www.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/htaccess-301-redirect-non-www-to-www.jsonld.twig @@ -1,27 +1,64 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/insert-update-delete-statements-with-zend-db.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/insert-update-delete-statements-with-zend-db.jsonld.twig index 920a2e2a..633a15d8 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/insert-update-delete-statements-with-zend-db.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/insert-update-delete-statements-with-zend-db.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/sql-queries-using-zend-db-select.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/sql-queries-using-zend-db-select.jsonld.twig index 920a2e2a..94eb7b5a 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/sql-queries-using-zend-db-select.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/sql-queries-using-zend-db-select.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/subqueries-with-zend-db.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/subqueries-with-zend-db.jsonld.twig index 920a2e2a..3c7fde7a 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/subqueries-with-zend-db.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/subqueries-with-zend-db.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/svn-export-in-a-virtual-host.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/svn-export-in-a-virtual-host.jsonld.twig index 920a2e2a..d057115a 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/svn-export-in-a-virtual-host.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/svn-export-in-a-virtual-host.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/svn-keywords-setup-in-php-ide-zend-studio.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/svn-keywords-setup-in-php-ide-zend-studio.jsonld.twig index 920a2e2a..1b840d94 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/svn-keywords-setup-in-php-ide-zend-studio.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/svn-keywords-setup-in-php-ide-zend-studio.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/using-like-wildcards-with-zend-db.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/using-like-wildcards-with-zend-db.jsonld.twig index 920a2e2a..a66fa7f2 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/using-like-wildcards-with-zend-db.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/using-like-wildcards-with-zend-db.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/what-are-returning-the-fetch-functions-from-zend-db.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/what-are-returning-the-fetch-functions-from-zend-db.jsonld.twig index 920a2e2a..f26875f3 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/what-are-returning-the-fetch-functions-from-zend-db.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/what-are-returning-the-fetch-functions-from-zend-db.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.jsonld.twig index 920a2e2a..d5ff58a8 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.jsonld.twig index 920a2e2a..c319bad6 100644 --- a/src/Blog/templates/page/JSON-LD/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig index 920a2e2a..18d787c0 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig @@ -1,27 +1,96 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig index 920a2e2a..c5be1799 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig @@ -1,27 +1,72 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig index 920a2e2a..aec46de3 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig @@ -1,27 +1,96 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig index 920a2e2a..5df32311 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig @@ -1,27 +1,72 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig index 920a2e2a..d5591851 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig @@ -1,27 +1,72 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig index 920a2e2a..c7cbe4af 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig @@ -1,27 +1,88 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig index 920a2e2a..9e2a40a6 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig index 920a2e2a..2ac6776e 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig index 920a2e2a..16c9c7c6 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig index 920a2e2a..de3166f3 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig index 920a2e2a..c4c8dd80 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig index 920a2e2a..e7863c27 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig index 920a2e2a..de17a9fa 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig index 920a2e2a..f51a451b 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig @@ -1,27 +1,50 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig index 920a2e2a..49657cd5 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig index 920a2e2a..ec620818 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig @@ -1,27 +1,50 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig index 920a2e2a..b967b7a0 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig @@ -1,27 +1,49 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig index 920a2e2a..3f59dd8d 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig index 920a2e2a..33cccb64 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig index 920a2e2a..07b053e5 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig index 920a2e2a..ddba7ec5 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig index 920a2e2a..d09746f0 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig index 920a2e2a..71f2e4fb 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig index 920a2e2a..5e99feb4 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig index 920a2e2a..428d4a79 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig @@ -1,27 +1,50 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig index 920a2e2a..a744744e 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig index 920a2e2a..313b3171 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig index 920a2e2a..07a38bef 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig index 920a2e2a..4d4ad695 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig index 920a2e2a..62b7551b 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig index 920a2e2a..68fb62c8 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig index 920a2e2a..03511d51 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig @@ -1,27 +1,50 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig index 920a2e2a..4e504144 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig index 920a2e2a..031e0dd4 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig index 920a2e2a..b4b828c3 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig index 920a2e2a..8b4b68a6 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig index 920a2e2a..76ad4ecd 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig index 920a2e2a..cc66d280 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig index 920a2e2a..ff6099ab 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig index 920a2e2a..ba4bae23 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig index 920a2e2a..efeefb82 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig index 920a2e2a..54f18b64 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig index 920a2e2a..4d8de651 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig index 920a2e2a..fcfad95f 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig index 920a2e2a..4b0ac791 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig index 920a2e2a..23913285 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig index 920a2e2a..e365df19 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig index 920a2e2a..cafb3028 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig @@ -1,27 +1,50 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig index 920a2e2a..d434e413 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig @@ -1,27 +1,50 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig index 920a2e2a..23caca4d 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig index 920a2e2a..6e698b53 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig index 920a2e2a..26535f6e 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig index 920a2e2a..0a6c952c 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig index 920a2e2a..c8d34275 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig index 920a2e2a..3dda6089 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig index 920a2e2a..7f30e5d4 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig index 920a2e2a..4fa77c43 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig index 920a2e2a..48cccfa5 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig index 920a2e2a..9a26d2f1 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig index 920a2e2a..55cb2043 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig index 920a2e2a..55784d3c 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig @@ -1,27 +1,50 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig index 920a2e2a..2685508b 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig index 920a2e2a..85a84972 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig index 920a2e2a..6685d697 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig @@ -1,27 +1,64 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig index 920a2e2a..594785ff 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig @@ -1,27 +1,50 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig index 920a2e2a..b99e1452 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig index 920a2e2a..3d979c33 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig index 920a2e2a..bc929cde 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig @@ -1,27 +1,102 @@ diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig index 920a2e2a..1497679d 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig @@ -1,27 +1,102 @@ diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig index 920a2e2a..ce0d3e96 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig index 920a2e2a..2d7c052b 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig @@ -1,27 +1,102 @@ diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig index 920a2e2a..5e4bf96a 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig @@ -1,27 +1,102 @@ diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig index 920a2e2a..a97a0d5a 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig @@ -1,27 +1,110 @@ diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig index 920a2e2a..d3138498 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig @@ -1,27 +1,110 @@ diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig index 920a2e2a..be75e765 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig @@ -1,27 +1,94 @@ diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig index 920a2e2a..76bb1d9f 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig @@ -1,27 +1,102 @@ diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig index 920a2e2a..94c060dd 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig index 920a2e2a..36221994 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig index 920a2e2a..c397c585 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig index 920a2e2a..d7176b3d 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig index 920a2e2a..2313c421 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig index 920a2e2a..6c5e880d 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig index 920a2e2a..83a33c5c 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig index 920a2e2a..68406e65 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig @@ -1,27 +1,52 @@ diff --git a/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig index 920a2e2a..e4168ad8 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig b/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig index 920a2e2a..e8ee6a87 100644 --- a/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig b/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig index 920a2e2a..31cc5356 100644 --- a/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig b/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig index 920a2e2a..b5014e69 100644 --- a/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig index 920a2e2a..35a75ade 100644 --- a/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig b/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig index 920a2e2a..2cb1dbb2 100644 --- a/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig index 920a2e2a..671c7f4f 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig index 920a2e2a..a0cbf0d9 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig index 920a2e2a..752d04f9 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig index 920a2e2a..da004bd7 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig index 920a2e2a..bd0789d6 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig index 920a2e2a..08190a83 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig @@ -1,27 +1,50 @@ diff --git a/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig index 920a2e2a..53076386 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig @@ -1,27 +1,53 @@ diff --git a/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig index 920a2e2a..c5ab9215 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig @@ -1,27 +1,51 @@ diff --git a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig index 920a2e2a..0f59e5f8 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig index 920a2e2a..052f3fc8 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig @@ -1,27 +1,54 @@ diff --git a/src/Blog/templates/page/author-resource.html.twig b/src/Blog/templates/page/author-resource.html.twig index 5fdf2994..c061d03d 100644 --- a/src/Blog/templates/page/author-resource.html.twig +++ b/src/Blog/templates/page/author-resource.html.twig @@ -42,9 +42,9 @@ {{ post.category.name }} -

                        {{ post.title }}

                        +

                        {{ post.title }}

                        {{ post.excerpt|length > 200 ? post.excerpt|slice(0, 200)|trim ~ '…' : post.excerpt }}

                        - {{ post.postDate|date('M d, Y') }} + Read more → {% else %} diff --git a/src/Blog/templates/page/blog-resource/android/listen-for-android-install-referrer.html.twig b/src/Blog/templates/page/blog-resource/android/listen-for-android-install-referrer.html.twig index 3ad93373..c0637732 100644 --- a/src/Blog/templates/page/blog-resource/android/listen-for-android-install-referrer.html.twig +++ b/src/Blog/templates/page/blog-resource/android/listen-for-android-install-referrer.html.twig @@ -30,11 +30,28 @@
                        -Have you ever wondered if Android market sends you information at the moment of app install? Wouldn"t be nice to create custom links to your android application, including bits of information about the referrer, and send it directly to the app for online casino processing at install? This could be a simple and accurate solution for mobile app install tracking but I"m sure you can find this useful in many ways. +

                        Getting Referrer Data at Install Time

                        -With Android, you actually get this information as a broadcasted intent by android market at install time - even before opening your app... +

                        Have you ever wondered if Android market sends you information at the moment of app install? Wouldn't it be nice to create custom links to your Android application, including bits of information about the referrer, and send it directly to the app for processing at install? This could be a simple and accurate solution for mobile app install tracking, but I'm sure you can find this useful in many ways.

                        -  +

                        With Android, you actually get this information as a broadcasted intent by Android market at install time - even before opening your app...

                        + +

                        Frequently Asked Questions

                        + +
                        +
                        + Does Android send information when the app is installed? + +
                        +

                        Yes. Android market broadcasts an intent containing referrer information at the moment the app is installed.

                        +
                        +
                        +
                        + When is this referrer information available to the app? + +
                        +

                        It's delivered as a broadcasted intent at install time, before the app is ever opened.

                        +
                        +
                        +
                        {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.html.twig b/src/Blog/templates/page/blog-resource/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.html.twig index 645c1fbd..0c2ef2b8 100644 --- a/src/Blog/templates/page/blog-resource/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.html.twig +++ b/src/Blog/templates/page/blog-resource/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.html.twig @@ -35,6 +35,23 @@ If that's the case, one broadcast receiver might consume the broadcasted intent, This can be the case where you use 3rd party libraries with broadcast receivers defined. The following is a solution for this kind of problem, a code snippet inspired by the way Admob for android seems to solve this, as shown in their documentation, using meta-data in manifest file.... + +

                        Frequently Asked Questions

                        + +
                        +
                        + What problem does this article address? + +
                        +

                        When multiple broadcast receivers are registered separately to listen for the same intent in the same Android app, this can lead to unexpected results: one broadcast receiver might consume the broadcasted intent, leaving the others with nothing to receive.

                        +
                        +
                        +
                        + When is this issue most likely to occur? + +
                        +

                        This can happen when you use 3rd party libraries that already define their own broadcast receivers alongside your app's own receivers.

                        +
                        +
                        +
                        {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/architecture/configprovider-bootstrap-modern-php-applications.html.twig b/src/Blog/templates/page/blog-resource/architecture/configprovider-bootstrap-modern-php-applications.html.twig index 5c51c4c0..98822bff 100644 --- a/src/Blog/templates/page/blog-resource/architecture/configprovider-bootstrap-modern-php-applications.html.twig +++ b/src/Blog/templates/page/blog-resource/architecture/configprovider-bootstrap-modern-php-applications.html.twig @@ -139,6 +139,47 @@
                      • Testability - The consistent, central configuration promotes isolated (e.g. per-module) testing, easier swapping of dependencies and the assertion of pipeline setup (e.g. check if a config key is present).
                      +

                      Frequently Asked Questions

                      + +
                      +
                      + What is a ConfigProvider in PHP? + +
                      +

                      It is a class that is part of an application's bootstrap process: a class or callable that returns configuration data telling the platform which middleware should run, in what order, and sometimes under what conditions.

                      +
                      +
                      +
                      + What does the ConfigProvider return in the Laminas/Mezzio ecosystem? + +
                      +

                      In the Laminas/Mezzio ecosystem, it's literally an array of configuration, settings, or anything else the application needs, and each module or package may contain its own ConfigProvider returning middleware pipeline configuration, dependency injection mappings, and request handlers.

                      +
                      +
                      +
                      + What is the difference between 'factories' and 'invokables' in the dependencies array? + +
                      +

                      factories will have the factory build the service, while invokables will use new directly. You can also use aliases to redirect to another service name and delegators to wrap the original service.

                      +
                      +
                      +
                      + How does the ConfigProvider get used during application bootstrap? + +
                      +

                      It is automatically picked up by the framework during bootstrap: all ConfigProviders are merged into one array, the configuration array is read (similar to $config = $container->get('config') ?? [];), each item is resolved via $app->pipe(), the error-handling middleware is placed last in the pipeline, and at runtime Laminas Stratigility iterates over the pipeline in the order it was registered.

                      +
                      +
                      +
                      + Are new ConfigProviders auto-discovered in Dotkernel? + +
                      +

                      Dotkernel is an exception to the usual auto-discovery rule: new ConfigProviders have to be added manually in config/config.php, because all the initial ConfigProviders required to install the applications are already injected.

                      +
                      +
                      +
                      + What are the benefits of using a ConfigProvider? + +
                      +

                      Benefits include centralized setup instead of hardcoded bootstrap code, modularity so each package can ship its own config, container-friendliness with DI containers like Laminas ServiceManager, PHP-DI or Pimple, standardized service definitions, environment-agnostic configuration for dev/test/prod, and better testability of the pipeline setup.

                      +
                      +
                      +
                      +

                      Additional Resources

                      • Mezzio Container
                      • diff --git a/src/Blog/templates/page/blog-resource/architecture/request-lifecycle-for-a-mezzio-based-application.html.twig b/src/Blog/templates/page/blog-resource/architecture/request-lifecycle-for-a-mezzio-based-application.html.twig index 5f10f21e..e33d69f7 100644 --- a/src/Blog/templates/page/blog-resource/architecture/request-lifecycle-for-a-mezzio-based-application.html.twig +++ b/src/Blog/templates/page/blog-resource/architecture/request-lifecycle-for-a-mezzio-based-application.html.twig @@ -565,6 +565,47 @@ $('.hasTooltip').hover(

                        + +

                        Frequently Asked Questions

                        + +
                        +
                        + What is the request lifecycle? + +
                        +

                        The request lifecycle is the sequence of steps that happen from the moment a user makes an HTTP request until the server sends back a response.

                        +
                        +
                        +
                        + What happens at the entry point of a request? + +
                        +

                        The application bootstraps and loads configuration to create the Mezzio application instance, registers factories, aliases and delegators in the service container, reads all available routes with their allowed request methods and registers them (managed by FastRoute), and loads the predefined order of middleware in the pipeline.

                        +
                        +
                        +
                        + How does routing work in a Mezzio-based application? + +
                        +

                        FastRoute matches the incoming URL and method against the registered routes, for example matching a GET request to /page/about against the GetPageViewHandler handler under the route name page::about.

                        +
                        +
                        +
                        + What happens during handler invocation? + +
                        +

                        The matched route name is extracted from the request attribute and passed to the renderer, using code similar to $template = $request->getAttribute(RouteResult::class)->getMatchedRouteName();, after which the handler executes the custom business logic.

                        +
                        +
                        +
                        + What happens during template rendering? + +
                        +

                        Twig loads the matched template file, applies the layout it extends, renders its blocks, and includes any partials, producing the final HTML output.

                        +
                        +
                        +
                        + How is the response created and returned to the browser? + +
                        +

                        An HtmlResponse is created with a status code, headers, and the rendered HTML body. It then flows back through the middleware stack in reverse (the response pipeline), where middleware can modify headers, cookies, or compress content, before the response emitter sends the final response back to the browser as HTTP 20x/30x, 40x, or 50x.

                        +
                        +
                        +
                {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/architecture/understanding-middleware.html.twig b/src/Blog/templates/page/blog-resource/architecture/understanding-middleware.html.twig index 8f45fea1..7399dcb4 100644 --- a/src/Blog/templates/page/blog-resource/architecture/understanding-middleware.html.twig +++ b/src/Blog/templates/page/blog-resource/architecture/understanding-middleware.html.twig @@ -126,6 +126,47 @@

                The flow begins with a request. The execution passes the control to the IP middleware which enhances the request with the user's IP and other relevant data. The control passes to your custom handler that processes the request and returns a response. The flow continues in reverse order, back to the IP middleware which can, if needed, change the output before it gets returned to the user that initiated the request.

                +

                Frequently Asked Questions

                + +
                +
                + What is middleware? + +
                +

                Middleware is code that exists between the request and response, and which can take the incoming request, perform actions based on it, and either complete the response or pass delegation on to the next middleware in the queue.

                +
                +
                +
                + What is the purpose of middleware? + +
                +

                Middleware makes it easier for software developers to implement communication and input/output, so they can focus on the specific purpose of their application. In web services, the Input represents the Request received, and Output represents the Response to be sent.

                +
                +
                +
                + What can middleware be used for? + +
                +

                Middleware can be used for purposes such as A/B testing, debugging, caching, CORS, authentication (HTTP Basic Auth, OAuth 2.0, OpenID), CSRF protection, rate limiting, referrals, and IP restriction.

                +
                +
                +
                + What interface must PHP middleware implement to be PSR-15 compliant? + +
                +

                According to PSR-15, a compliant middleware must implement Psr\Http\Server\MiddlewareInterface, which requires a process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface method.

                +
                +
                +
                + How does middleware get called within the application pipeline? + +
                +

                The application pipeline defines the execution flow: the request passes through the middleware one by one, in the order they are placed. If control passes through all middleware successfully, execution is passed to your custom code, which generates a response, and execution then passes back through the middleware in reverse order. If execution is terminated before reaching your custom code (e.g. via an exception), the response is generated by the last middleware reached.

                +
                +
                +
                + What is a practical, real-world example of middleware? + +
                +

                A simple example is enhancing a request with the user's IP for logging purposes or geographical reporting. The request first passes through the IP middleware, which enhances the request with the user's IP and other relevant data, then control passes to the custom handler that processes the request and returns a response. The flow continues in reverse, back through the IP middleware, which can change the output before it's returned to the user.

                +
                +
                +
                +

                Additional resources:

                • Why Care About PHP Middleware?
                • diff --git a/src/Blog/templates/page/blog-resource/best-practice/aptana-set-svn-keywords.html.twig b/src/Blog/templates/page/blog-resource/best-practice/aptana-set-svn-keywords.html.twig index 311d3646..cfb26068 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/aptana-set-svn-keywords.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/aptana-set-svn-keywords.html.twig @@ -50,6 +50,29 @@ For example if you want to set the svn keyword property Id< When you make the SVN commit of the file, the $Id$ keyword will be replaced with text in the format shown below: + +

                  Frequently Asked Questions

                  + +
                  +
                  + How do you set the svn:keywords property for a file in Aptana? + +
                  +

                  Write the keyword marker (for example $Id$) in the file, then right click the file and follow Team -> Set Property..., select svn:keywords, and write Id in the text field.

                  +
                  +
                  +
                  + Why is "Set Property..." not active when I right click the file? + +
                  +

                  Set Property... will not be active if the file hasn't first been added to SVN. Use Team -> Add to Version Controller before trying to set the property.

                  +
                  +
                  +
                  + What happens to the $Id$ keyword after an SVN commit? + +
                  +

                  After the SVN commit of the file, the $Id$ keyword is replaced with text containing the file's SVN metadata, in a specific format.

                  +
                  +
                  +
              {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/basic-security-in-dotkernel-headless-platform.html.twig b/src/Blog/templates/page/blog-resource/best-practice/basic-security-in-dotkernel-headless-platform.html.twig index 0ba39833..b8cd4914 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/basic-security-in-dotkernel-headless-platform.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/basic-security-in-dotkernel-headless-platform.html.twig @@ -192,6 +192,47 @@

              The GitHub Action Laminas Continuous Integration is an integral component of Dotkernel API. It ensures code quality by streamlining the execution of PHP quality assurance (QA) tasks within continuous integration (CI) workflows. Most often triggered by commits to the repository, it builds a matrix of tests: static analysis, coding standards checks, and unit tests.

              +

              Frequently Asked Questions

              + +
              +
              + What are the main facets of software security to consider? + +
              +

              Software security spans many areas: authentication and access control, data protection, input validation and injection, web and API security, dependency and supply chain risks, configuration and deployment, network and infrastructure security, logging/monitoring and incident response, secure software development lifecycle, and human and organizational factors.

              +
              +
              +
              + How does Dotkernel handle form input validation? + +
              +

              Dotkernel API uses laminas/laminas-inputfilter to ensure a field is present and of the correct type. Dotkernel Admin additionally uses laminas/laminas-form, which provides form element objects, an InputFilter for each input (or custom validators), and methods for binding data to and from the form, integrating with laminas-escaper, laminas-validator, laminas-session, and laminas-filter.

              +
              +
              +
              + How does Dotkernel API handle content negotiation? + +
              +

              Content negotiation is handled via a middleware configured in the config/autoload/content-negotiation.global.php file. It uses the Content-Type and Accept HTTP request headers to negotiate with the client, returning application/json or application/hal+json data formats.

              +
              +
              +
              + How is CORS handled and configured in Dotkernel API? + +
              +

              CORS is handled by mezzio/mezzio-cors and configured in the config/autoload/cors.local.php file, validating calls using configuration items like origins, headers, max age, and credentials. When configuring the pipeline, the CorsMiddleware must be added before the RouteMiddleware.

              +
              +
              +
              + What should be done with the demo credentials before going to production? + +
              +

              Demo credentials are provided for convenience during installation testing, but it is important to update or remove these accounts in your production environment.

              +
              +
              +
              + What are the security recommendations around OpenAPI documentation? + +
              +

              You should not include sensitive information for your endpoints in the OpenAPI documentation, and you should not enable the documentation in a production environment.

              +
              +
              +
              +

              Additional Resources

              • Basic Security in Dotkernel Admin
              • diff --git a/src/Blog/templates/page/blog-resource/best-practice/golden-rules-of-professional-php-coding.html.twig b/src/Blog/templates/page/blog-resource/best-practice/golden-rules-of-professional-php-coding.html.twig index 88bfb08a..6d54ba5c 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/golden-rules-of-professional-php-coding.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/golden-rules-of-professional-php-coding.html.twig @@ -48,6 +48,41 @@ Maybe even a distinct class for stats ? 7. Use an IDE Aptana 2, Aptana 3, Eclipse, even Zend Studio . 8. Know your IDE: code snippets, code assist, integration with Zend Framework, SVN integration, bug tracker integration, and so on + +

                Frequently Asked Questions

                + +
                +
                + What error reporting settings should be used in development and staging? + +
                +

                Always use the highest error reporting level and turn display_errors ON, for example with error_reporting(-1); and ini_set('display_errors', 1);.

                +
                +
                +
                + What should you do about warnings and notices? + +
                +

                Fix every warning or notice that occurs, and regularly check the server's error_log for notices and warnings.

                +
                +
                +
                + How should temporary hacks or quick fixes be marked in code? + +
                +

                Identify any temporary hack with a special mark, such as a #@TODO comment noting who added it and why.

                +
                +
                +
                + What is the rule about what a function should do? + +
                +

                Each function must do a single task. For example, if you're logging in a user and also recording that login in a stats table, create a separate function (or even a distinct class) for the stats recording, rather than combining both tasks in one function.

                +
                +
                +
                + What tools does the article recommend for professional PHP development? + +
                +

                It recommends using a version control system (noting that SVN is not dead) and using an IDE such as Aptana 2, Aptana 3, Eclipse, or Zend Studio, and knowing your IDE's code snippets, code assist, Zend Framework integration, SVN integration, and bug tracker integration.

                +
                +
                +
              {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/htaccess-301-redirect-non-www-to-www.html.twig b/src/Blog/templates/page/blog-resource/best-practice/htaccess-301-redirect-non-www-to-www.html.twig index 50845946..fd6193f4 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/htaccess-301-redirect-non-www-to-www.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/htaccess-301-redirect-non-www-to-www.html.twig @@ -41,6 +41,23 @@ If, on the other hand, you want to redirect http://www.dotboost.com to http://do RewriteRule ^(.*)$ http://dotboost.com/$1 Replace dotboost.com with your site's domain. + +

              Frequently Asked Questions

              + +
              +
              + How do I redirect a non-www domain to www using .htaccess? + +
              +

              Add RewriteCond %{HTTP_HOST} ^dotboost.com and RewriteRule ^(.*)$ http://www.dotboost.com/$1 to your .htaccess file, right after RewriteEngine On, replacing dotboost.com with your own domain.

              +
              +
              +
              + How do I redirect a www domain to non-www instead? + +
              +

              Add RewriteCond %{HTTP_HOST} ^www.dotboost.com and RewriteRule ^(.*)$ http://dotboost.com/$1 instead, again replacing dotboost.com with your own domain.

              +
              +
              +
          {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/insert-update-delete-statements-with-zend-db.html.twig b/src/Blog/templates/page/blog-resource/best-practice/insert-update-delete-statements-with-zend-db.html.twig index 7d41771a..a8daf6e3 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/insert-update-delete-statements-with-zend-db.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/insert-update-delete-statements-with-zend-db.html.twig @@ -84,6 +84,35 @@ The above SQL DELETE statemnet is translated in Zend_Db as follow:
          >
           $db->delete('user', 'id = '.$id);
           
          + +

          Frequently Asked Questions

          + +
          +
          + What are DML statements? + +
          +

          DML (Data Manipulation Language) statements are statements that change data values in database tables. There are 3 primary DML statements: INSERT, UPDATE, and DELETE.

          +
          +
          +
          + How do you insert a new row with Zend_Db? + +
          +

          Build an associative array of column names to values (e.g. email, password, firstName, lastName, active) and pass it to $db->insert('user', $data), which corresponds to an SQL INSERT INTO ... VALUES statement.

          +
          +
          +
          + How do you update rows with Zend_Db, including incrementing a column? + +
          +

          Build a $data array of the columns to update, using a Zend_Db_Expr for expressions such as incrementing accountUpdate (new Zend_Db_Expr('accountUpdate+1')), then call $db->update('user', $data, 'id = '.$id).

          +
          +
          +
          + How do you delete a row with Zend_Db? + +
          +

          Call $db->delete('user', 'id = '.$id), which is equivalent to the SQL statement DELETE FROM user WHERE id = '$id'.

          +
          +
          +
        {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/sql-queries-using-zend-db-select.html.twig b/src/Blog/templates/page/blog-resource/best-practice/sql-queries-using-zend-db-select.html.twig index f85340d6..847dbd28 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/sql-queries-using-zend-db-select.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/sql-queries-using-zend-db-select.html.twig @@ -119,6 +119,47 @@ Also see: - What are returning the FETCH functions from Zend_Db
        - Subqueries with Zend_Db
        - INSERT, UPDATE, DELETE statements with Zend_Db + +

        Frequently Asked Questions

        + +
        +
        + What does Zend_Db provide? + +
        +

        Zend_Db and its related classes provide a simple SQL database interface for Zend Framework. To connect to a MySQL database, the Pdo_Mysql adapter is used via Zend_Db::factory('Pdo_Mysql', $dbConnect).

        +
        +
        +
        + How do you write a SELECT with a JOIN and a WHERE clause in Zend_Db style? + +
        +

        Use $db->select()->from(array('a'=>'users'), array('a.id','a.name'))->join(array('b'=>'orders'), 'a.id = b.user_id', array('b.order_id'))->where('a.id = ?', $userId), which is equivalent to a classical SQL query using INNER JOIN.

        +
        +
        +
        + How do you join a table without selecting any of its columns? + +
        +

        Pass an empty string as the 3rd parameter of the join() method, e.g. ->join(array('b'=>'orders'), 'a.id = b.user_id', '').

        +
        +
        +
        + What happens if the 3rd parameter of join() is omitted entirely? + +
        +

        If the 3rd parameter is not written, it will select all the fields from that joined table (equivalent to SELECT ..., b.* in SQL).

        +
        +
        +
        + How do you write a WHERE IN clause with Zend_Db? + +
        +

        Use ->where('aff_id IN (?)', array(1,2,3)) on the select object, equivalent to SQL's WHERE aff_id IN ('1','2','3').

        +
        +
        +
        + How can you check that a Zend_Db select is generating the correct query? + +
        +

        Before fetching it, echo the query to visualize it: echo $select->__toString();exit;

        +
        +
        +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/subqueries-with-zend-db.html.twig b/src/Blog/templates/page/blog-resource/best-practice/subqueries-with-zend-db.html.twig index 6bab3fbb..3ac2e8e6 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/subqueries-with-zend-db.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/subqueries-with-zend-db.html.twig @@ -71,6 +71,29 @@ $db->select() ) ->group('a.id'); + +

    Frequently Asked Questions

    + +
    +
    + What SQL techniques does this subquery example combine? + +
    +

    The example combines COUNT(), LEFT JOIN, and GROUP BY, selecting from 3 tables and counting rows from 2 different tables.

    +
    +
    +
    + How do you embed a subquery as a selected column in a Zend_Db select? + +
    +

    Wrap a nested $db->select() call inside a Zend_Db_Expr, building the subquery string with the outer table's correlated WHERE condition (e.g. c.track_id = a.id), as shown for the count_files column.

    +
    +
    +
    + How is the LEFT JOIN with a COUNT expressed in Zend_Db? + +
    +

    Use ->joinLeft(array('b'=>'track_courses'), 'a.id = b.track_id', array('count_courses' => 'COUNT(b.track_id)')) followed by ->group('a.id').

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/svn-export-in-a-virtual-host.html.twig b/src/Blog/templates/page/blog-resource/best-practice/svn-export-in-a-virtual-host.html.twig index 1cf358bc..30b49992 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/svn-export-in-a-virtual-host.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/svn-export-in-a-virtual-host.html.twig @@ -60,6 +60,41 @@ For more information, you can run svn help export. If you've exported the repository using a different user (root for example), you can change the permissions back by running the following command as root:
    >chown -R siteuser.psacln /var/www/vhosts/example.com/httpdocs
    + +

    Frequently Asked Questions

    + +
    +
    + How do you check if Subversion is installed on the host? + +
    +

    Run svn --version. If you don't get a "command not found" message, Subversion is installed; otherwise, you need to install it.

    +
    +
    +
    + What is the basic command to export a repository? + +
    +

    The command is svn export repositoryUrl targetDirectory, run from the host where you want to export the repository, ideally using the domain's user rather than root.

    +
    +
    +
    + What does the -r option do? + +
    +

    -r revisionNumber is optional and exports a specific revision; by default, the latest revision is used.

    +
    +
    +
    + What does the --force option do, and what is the risk? + +
    +

    By default SVN will not export into an existing directory; --force overrides this. Be careful, since this option can overwrite files.

    +
    +
    +
    + How do you fix file permissions if you exported the repository as a different user? + +
    +

    As root, run chown -R siteuser.psacln /var/www/vhosts/example.com/httpdocs to change the permissions back.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig b/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig index 0ad09cf2..66099d08 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig @@ -70,6 +70,41 @@ Select All resources Check the Use filtration by the resource name and add Mask: *.php svn-add + +

    Frequently Asked Questions

    + +
    +
    + Why set these SVN properties on each project? + +
    +

    They provide better integration of SVN, your PHP IDE (Zend Studio), and a bug tracker of choice, and must be set for each project you have.

    +
    +
    +
    + What does the svn:ignore property do here? + +
    +

    It tells SVN to ignore local settings files such as *.project, *.prefs, .project, cache, .settings, .buildpath, and *.ini, since you don't want to commit your local settings to the main repository.

    +
    +
    +
    + How do you set up basic bug tracker integration? + +
    +

    Set the bugtracq:label property to "Tracker ID:" and bugtraq:message; if you have a public bug tracker such as Mantis, also set bugtraq:url to a URL pattern like http://www.dotkernel.net/view.php?id=%BUGID%.

    +
    +
    +
    + Should these properties be applied recursively? + +
    +

    No. For the properties above, apply them only to the project folder, not recursively.

    +
    +
    +
    + How is the svn:keywords property applied differently? + +
    +

    Check "Apply property recursively to:", select "All resources", then check "Use filtration by the resource name" and add the mask *.php.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/using-like-wildcards-with-zend-db.html.twig b/src/Blog/templates/page/blog-resource/best-practice/using-like-wildcards-with-zend-db.html.twig index 78c25c5d..56b94d24 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/using-like-wildcards-with-zend-db.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/using-like-wildcards-with-zend-db.html.twig @@ -152,6 +152,41 @@ $result = $this->db->fetchAll($select);
  • The third character in the number column is 6 (6)
  • The rest of the number column can be any string, of any length (%)
  • + +

    Frequently Asked Questions

    + +
    +
    + What do the LIKE wildcards _ and % mean? + +
    +

    The _ wildcard matches a single character, while % matches any string of any length, including zero length.

    +
    +
    +
    + Which SQL statements can use the LIKE condition? + +
    +

    LIKE allows pattern matching in the WHERE clause and can be used in any valid SQL statement: SELECT, INSERT, UPDATE, or DELETE.

    +
    +
    +
    + How do you build a LIKE query with Zend_Db? + +
    +

    Quote the column with $this->db->quoteIdentifier(), build the condition with $this->db->quoteInto("$col LIKE ? ", $pattern), and pass the resulting $where string into ->where() on a select, then run it with $this->db->fetchAll($select).

    +
    +
    +
    + How do you combine multiple LIKE conditions with OR? + +
    +

    Build the first condition with quoteInto, then append further ones with quoteInto("OR $col LIKE (?) ", $pattern), as in the example matching 'gallery' or 'folder' in the source field.

    +
    +
    +
    + How does NOT LIKE differ from LIKE? + +
    +

    NOT LIKE negates the pattern match — for example, `id` NOT LIKE '1_' returns ids that don't start with 1 or don't have exactly 2 digits, and NOT LIKE conditions can be chained with AND to exclude several patterns at once.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/what-are-returning-the-fetch-functions-from-zend-db.html.twig b/src/Blog/templates/page/blog-resource/best-practice/what-are-returning-the-fetch-functions-from-zend-db.html.twig index 593c5d4b..2bb3c1d3 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/what-are-returning-the-fetch-functions-from-zend-db.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/what-are-returning-the-fetch-functions-from-zend-db.html.twig @@ -145,6 +145,41 @@ $a = array(
     style="background-color: #9FCFFF">
     $a = $db->fetchRow($select);
     
    + +

    Frequently Asked Questions

    + +
    +
    + What FETCH methods are available in Zend_Db_Adapter_Abstract? + +
    +

    The article covers fetchAll, fetchAssoc, fetchCol, fetchOne, fetchPairs, and fetchRow.

    +
    +
    +
    + What does fetchAll do compared to the old query style? + +
    +

    $a = $db->fetchAll($select) replaces the old-style loop that calls next_record() repeatedly and builds an array of associative rows using f() for each column.

    +
    +
    +
    + What does fetchRow return? + +
    +

    $a = $db->fetchRow($select) returns a single row as an associative array, replacing a single next_record() call followed by f() calls for each column.

    +
    +
    +
    + What does fetchOne return? + +
    +

    $a = $db->fetchOne($select) returns a single value, replacing a single next_record() call followed by one f() call.

    +
    +
    +
    + How do the old-style query(), next_record(), and f() methods relate to Mysqli? + +
    +

    query() is similar to mysqli_query(), next_record() is similar to mysqli_next_result(), and f() retrieves the value of the column specified as a parameter.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.html.twig b/src/Blog/templates/page/blog-resource/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.html.twig index c4c7cb5e..adbbc61d 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.html.twig @@ -48,6 +48,41 @@ CURRENT_TIMESTAMP is also a solution for  updating date and time field For more details check out MySQL Manual Note*: Only one timestamp field can be DEFAULT CURRENT_TIMESTAMP in a table. + +

    Frequently Asked Questions

    + +
    +
    + Why use CURRENT_TIMESTAMP as a DEFAULT value for a date/time field? + +
    +

    Because when inserting a new row, there is no need to specifically set the date/time value yourself, either from PHP Date/Time functions or with MySQL's NOW() function.

    +
    +
    +
    + How do you make a field update its timestamp automatically on every UPDATE? + +
    +

    Add the ON UPDATE CURRENT_TIMESTAMP clause, for example: ALTER TABLE `user` CHANGE `dateLogin` `dateLogin` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP.

    +
    +
    +
    + What happens if a TIMESTAMP column has neither a DEFAULT nor an ON UPDATE clause? + +
    +

    For the first TIMESTAMP field in the table, having neither clause is the same as DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP.

    +
    +
    +
    + What happens with a DEFAULT CURRENT_TIMESTAMP clause but no ON UPDATE clause? + +
    +

    The column gets the current timestamp as its default value but is not automatically updated afterward.

    +
    +
    +
    + Can more than one TIMESTAMP column default to CURRENT_TIMESTAMP in the same table? + +
    +

    No. Only one timestamp field in a table can be DEFAULT CURRENT_TIMESTAMP.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.html.twig b/src/Blog/templates/page/blog-resource/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.html.twig index 2b0756b5..da2498c3 100644 --- a/src/Blog/templates/page/blog-resource/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.html.twig +++ b/src/Blog/templates/page/blog-resource/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.html.twig @@ -69,6 +69,41 @@
  • Post-migration support and team training - this step depends on the level of collaboration between the original developers and the Apidemia team, so the more closely they work together, the easier it is to onboard the devs for the long run.
  • +

    Frequently Asked Questions

    + +
    +
    + What is Laminas MVC being replaced by? + +
    +

    Mezzio microframework, built using Laminas components. It has gone through rigorous development and testing since being released in 2015, when it was known as Zend Expressive, before being renamed Mezzio.

    +
    +
    +
    + Why is maintaining legacy platforms a problem? + +
    +

    Maintaining legacy platforms over the long term is often costly and time-consuming, so every few years platform owners must consider migrating to a newer platform that offers modern architecture, an active community, easier development/expansion/maintenance, security improvements, and more reliable dependencies.

    +
    +
    +
    + What is the core architectural pain point with legacy MVC platforms? + +
    +

    The MVC architecture is obsolete, fit for monolithic websites. Today's and tomorrow's architecture is based on middleware, building headless platforms, websites, and microservices following the same coding approach.

    +
    +
    +
    + What steps does Apidemia's migration process involve? + +
    +

    A simplified task list: code audit & migration strategy; partial or full migration to Laminas or PSR-compliant frameworks like Mezzio or Symfony; refactoring and decoupling legacy modules; unit testing and CI/CD pipeline setup; and post-migration support and team training.

    +
    +
    +
    + Who offers this migration guidance? + +
    +

    Apidemia, who has used Zend Framework, Laminas MVC, and Mezzio for years and can analyze and perform the transfer of a legacy platform to Mezzio, offering faster execution times, increased security, faster development, and long-term reliability.

    +
    +
    +
    +

    Additional resources

    • Dotkernel Headless Platform
    • diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/api-client-migration-from-postman-to-bruno.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/api-client-migration-from-postman-to-bruno.html.twig index b9f921d9..7e5d1120 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/api-client-migration-from-postman-to-bruno.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/api-client-migration-from-postman-to-bruno.html.twig @@ -120,6 +120,47 @@

      We fully expect Bruno to eventually restrict developers with paid plans, just like Postman did, but we'll cross that bridge when we get to it. Hopefully, we won't have to develop our own API client (fingers crossed). For now, Bruno becomes our de facto API client and will encourage our whole team to adopt it as soon as possible.

      +

      Frequently Asked Questions

      + +
      +
      + Why is the team considering a switch from Postman to Bruno? + +
      +

      They want a reliable API testing client that is free, stores and shares endpoint collections easily with the team, and is fast and secure. This reflects a broader trend in the PHP community toward local-first, Git-native developer tools.

      +
      +
      +
      + What is the main architectural difference between Postman and Bruno? + +
      +

      Postman's free plan now only allows one user account, while Bruno offers a fully-offline experience based on shared .bru files, so there is no restriction on the number of developers using them.

      +
      +
      +
      + How does version control differ between the two tools? + +
      +

      Postman stores collections and handles version control in the cloud, forcing developers to stay online (though collections can be exported/imported via its UI). Bruno's .bru files can be saved directly in a Git repository and are version-controlled through Git like any other project file.

      +
      +
      +
      + How does performance compare between Postman and Bruno? + +
      +

      Bruno is the clear winner on performance: it uses much less RAM and is generally faster. Postman needs to regularly synchronize with the cloud and store its advanced features in RAM, which can introduce delays.

      +
      +
      +
      + Is Bruno more secure than Postman? + +
      +

      Postman offers Single Sign-On (SSO) and Role-Based Access Control (RBAC), which the team doesn't find useful for its workflow. Bruno's local files never leave the dev environment, which the article argues makes it more secure, especially for avoiding sharing client files with online tools.

      +
      +
      +
      + What's the overall conclusion on Postman versus Bruno? + +
      +

      Postman is currently a better fit for larger teams willing to allocate a budget for a more feature-rich platform. Bruno stores collections in Git so everything works offline, which the team considers more secure while also being generally faster, and it has become their de facto API client.

      +
      +
      +
      +

      Additional Resources

      Bruno homepage

      diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/api-endpoint-to-collect-client-errors.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/api-endpoint-to-collect-client-errors.html.twig index e1f038ac..a22ca1b2 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/api-endpoint-to-collect-client-errors.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/api-endpoint-to-collect-client-errors.html.twig @@ -47,6 +47,29 @@ }

      Note: The error message will be stored by default in /log/error-report-endpoint-log.log, a separate log for Client and the message will be saved with a timestamp.

      + +

      Frequently Asked Questions

      + +
      +
      + Why was this endpoint created? + +
      +

      When a Frontend client (e.g. Angular) running on the user's machine hits an error against the Dotkernel API - whether from an overnight API response change or a simple undefined variable - there is little that can be done from the client side, so this endpoint lets clients "write down" the error instead.

      +
      +
      +
      + How do I submit an error from the client? + +
      +

      Send a simple POST request to your Dotkernel API's https://api.dotkernel.net/error-report route, with a body such as { "message": "My awesome error!!!" }.

      +
      +
      +
      + Where is the submitted error message stored? + +
      +

      By default, it is stored in a separate log file for Client, /log/error-report-endpoint-log.log, with the message saved alongside a timestamp.

      +
      +
      +
      {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig index 06c42391..9b50b80f 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig @@ -129,6 +129,47 @@ https://www.example-api.com/record/47?format=json

      Check out the relevant links below for exact details on the Dotkernel implementation of content negotiation.

      +

      Frequently Asked Questions

      + +
      +
      + What is content negotiation? + +
      +

      It's the act of a client and server agreeing on the format and language of the data they exchange, which is important for RESTful APIs since resources can support multiple representations.

      +
      +
      +
      + What does content negotiation ensure? + +
      +

      It ensures support for diverse clients (e.g. Accept: application/json or Accept: application/xml), data format flexibility for smaller responses (e.g. Accept: application/msgpack, a binary serialization), and language localization via headers like Accept-Language: en-US.

      +
      +
      +
      + Who decides the data format, the client or the server? + +
      +

      Either side technically can. In server-side negotiation, the server decides based on various factors, which can introduce erroneous assumptions and more complex implementation, forcing the client to adhere to server rules. In client-side negotiation, the client tells the server what format it prefers, which is more versatile and makes more sense.

      +
      +
      +
      + How can the preferred data format be communicated? + +
      +

      Via HTTP request headers (Content-Type and Accept) or via resource URI patterns, such as a file extension in the URL (e.g. /record/47.json) or an extra query parameter (e.g. /record/47?format=json). If the Accept header is not present, the server decides the response format.

      +
      +
      +
      + How does the quality factor (q) work in the Accept header? + +
      +

      The Accept header can list multiple accepted formats with a q value between 0 and 1 to express preference, e.g. Accept: application/json,application/xml;q=0.9,*/*;q=0.8. The server responds with the most preferred format it can satisfy, falling back further down the list if needed.

      +
      +
      +
      + How does Dotkernel API handle content negotiation? + +
      +

      Out of the box, Dotkernel API uses the Content-Type and Accept HTTP request headers to handle client-side content negotiation, supporting both application/json and application/hal+json. These can be changed as needed, and per-route content negotiation is also supported.

      +
      +
      +
      +

      Relevant Links

      Content Negotiation in Dotkernel API

      diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig index 00e82b87..9f62d155 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig @@ -63,6 +63,29 @@
    • Members are allowed to manage only their own accounts
    • OpenAPI 3 documentation - also an interactive interface that developers can use to integrate your API
    + +

    Frequently Asked Questions

    + +
    +
    + What is DotKernel API? + +
    +

    It is a Zend Expressive 3 application aiming to help developers quickly and efficiently develop an API.

    +
    +
    +
    + What key libraries does DotKernel API 1.0.0 use? + +
    +

    Among others, it's built on the ezimuel/zend-expressive-api skeleton, and uses dotkernel/dot-annotated-services for dependency injection, dotkernel/dot-console for console applications, dotkernel/dot-errorhandler for error logging, dotkernel/dot-mail for SMTP email, zend-expressive-authentication-oauth2 for OAuth2 authentication, zend-expressive-authorization-rbac for role-based permissions, and swagger-api/swagger-ui for OpenAPI 3 documentation.

    +
    +
    +
    + What features does DotKernel API 1.0.0 offer out of the box? + +
    +

    Secure authentication via OAuth2, two user roles (admin and member), where admins can manage any user account and members can manage only their own, plus OpenAPI 3 documentation with an interactive interface developers can use to integrate the API.

    +
    +
    +

     

    diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig index 799b1b29..7d2a0181 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig @@ -57,6 +57,29 @@ If the credentials are correct, the API will return a J When sending API requests to an endpoint which requires authorization, an Authorization header must be present containing the following data: "Bearer {access_token}", where {access_token} represents the content of the key with the same name found in the above response. + +

    Frequently Asked Questions

    + +
    +
    + What does a client send to request authorization? + +
    +

    The client application sends a POST request to the backend with a JSON object containing grant_type (set to "password"), client_id, client_secret, scope, username/email, and password.

    +
    +
    +
    + What does the API return when authorization succeeds? + +
    +

    If the credentials are correct, the API returns a JSON object containing token_type ("Bearer"), expires_in (86400 seconds), an access_token, and a refresh_token.

    +
    +
    +
    + How do I use the access token in subsequent requests? + +
    +

    When sending API requests to an endpoint that requires authorization, include an Authorization header containing "Bearer {access_token}", where {access_token} is the value returned in the authorization response.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig index 8044adaf..313717e7 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig @@ -86,6 +86,41 @@ These endpoints can be accessed only if a valid Bearer token is pr
     $app->get('/users', , 'users');
     
    + +

    Frequently Asked Questions

    + +
    +
    + What are the three access levels for protecting an endpoint? + +
    +

    no-auth, where the resource can be accessed without authentication/authorization; authentication, where only authenticated users can access the resource; and authorization, where only authenticated AND authorized users can access it.

    +
    +
    +
    + Where do I configure access to the endpoints? + +
    +

    In config/autoload/authorization.local.php. If that file is missing from your application, locate its dist file config/autoload/authorization.local.php.dist and copy it as config/autoload/authorization.local.php, then look for the array under the zend-expressive-authorization-rbac config key.

    +
    +
    +
    + How does role inheritance work under the roles key? + +
    +

    In the article's example, admin inherits from no other role, member inherits from admin, and guest inherits from member. The article warns this exact setup is just a model and should not be used in live projects, because guests would end up having the same rights as admins.

    +
    +
    +
    + How do I control which routes a role can access? + +
    +

    Under the permissions key you define which routes are accessible to a role. In the article's example, a member has access to the routes named avatar, users, and user.

    +
    +
    +
    + What response codes are returned for authentication and authorization endpoints? + +
    +

    Endpoints requiring authentication return a 401 Unauthorized response if a valid Bearer token isn't present in the request headers. Endpoints requiring authorization return a 403 Forbidden response instead under the same condition.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig index 513d51ee..58733fe3 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig @@ -93,6 +93,41 @@ use DrewM\MailChimp\MailChimp;   After this, you can start using the library by @Injecting MailChimp::class where it's needed. + +

    Frequently Asked Questions

    + +
    +
    + Which library does this tutorial use to add MailChimp to Dotkernel API? + +
    +

    The tutorial uses drewm/mailchimp-api, installed with the command composer require drewm/mailchimp-api.

    +
    +
    +
    + Where do you place the MailChimp configuration file? + +
    +

    In config/autoload/mailchimp.global.php, a new configuration file created as part of Step 2.

    +
    +
    +
    + What does the MailChimpFactory class do? + +
    +

    It's a factory, created at src/App/src/MailChimp/Factory/MailChimpFactory.php, that reads the config from the container and returns an instance of DrewM\MailChimp\MailChimp.

    +
    +
    +
    + Where do you register the MailChimp factory so the application can use it? + +
    +

    In src/App/src/ConfigProvider.php, inside the getDependencies() method's factories array, by mapping MailChimp::class to MailChimpFactory::class, plus adding the corresponding use statements for MailChimp and MailChimpFactory.

    +
    +
    +
    + How do you use MailChimp once it's wired up? + +
    +

    By injecting MailChimp::class wherever it's needed, using @Inject.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig index 26925671..97a6a47a 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig @@ -131,6 +131,47 @@

    Most endpoints for your API should be protected, so to access them you are required to generate an authentication token (AuthToken). The token is related to the user type, so make sure to check the privileges required for the endpoint you are testing. After you submit the token, you can test the endpoints as an authenticated user. Clicking on the 'Try it out' button will activate the required parameter input fields and the textarea for the request body. The 'Execute' button will send the request and return the response. along with its HTTP status code. You can read more details here.

    + +

    Frequently Asked Questions

    + +
    +
    + What is the OpenAPI Specification? + +
    +

    A consistent way to develop and interact with an API. It defines API structure and syntax in a universal way, regardless of the programming language used, typically described in YAML or JSON so users can quickly discover endpoints, request/response formats, security mechanisms and more.

    +
    +
    +
    + How does OpenAPI compare to Postman? + +
    +

    OpenAPI standardizes how endpoints and request/response models are described, automatically generates machine-readable documentation, lets you define the API specification before development starts, and improves collaboration across teams. Postman, by contrast, is used mainly to test an already-completed endpoint and has no real focus on standardized documentation or upfront design.

    +
    +
    +
    + Where do you document endpoints in Dotkernel API? + +
    +

    Each module (Admin, App, User) contains an OpenAPI.php file, where all endpoints from that module's RoutesDelegator.php must be documented, primarily describing the method, request and response.

    +
    +
    +
    + What core components does every OpenAPI description need? + +
    +

    OA\Info (basic project info like version and name), OA\Server (one or more target host URLs), OA\SecurityScheme (endpoint protection), OA\ExternalDocumentation (link and description for extended docs), and OA\Schema (describing an object or collection of objects).

    +
    +
    +
    + What generates the documentation file from the OpenAPI.php descriptions? + +
    +

    zircote/swagger-php, which uses the descriptions added in the OpenAPI.php files to build the documentation. The result can be listed in the command line or saved to a file in YAML or JSON format.

    +
    +
    +
    + How is the generated documentation rendered, and how do you test protected endpoints? + +
    +

    It can be rendered with Swagger UI, which lets you visualize and interact with the API's resources, or with Redoc, which lists the documentation in read-only mode. Because most endpoints are protected, testing them in Swagger UI requires generating an authentication token (AuthToken) matching the required privileges, then using the 'Try it out' button to fill in parameters/body and 'Execute' to send the request and see the response with its HTTP status code.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig index 7c63b86e..99ffb041 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig @@ -121,6 +121,41 @@ return function (Application $app, MiddlewareFactory $factory, ContainerInterfac };

    Add the cors middleware after the Error handler and before the middleware providing the data you want to access to make sure everything runs smoothly.

    This should get your project working with CORS.

    + +

    Frequently Asked Questions

    + +
    +
    + What causes the "No 'Access-Control-Allow-Origin' header" error? + +
    +

    It means the server didn't send the header that lets a local client, such as a browser, access its data. This is most common when trying to fetch data (usually JSON) that you want to process using JavaScript.

    +
    +
    +
    + What package does the article use to add CORS support? + +
    +

    Tuupola's Cors Middleware package, installed by running composer require tuupola/cors-middleware in the project.

    +
    +
    +
    + Where does the CORS configuration live? + +
    +

    In a cors.global.php file created in the config/autoload directory, containing a "cors" key with settings like origin, methods, headers.allow, headers.expose, credentials, and cache.

    +
    +
    +
    + How is the CorsMiddleware wired into the container? + +
    +

    A CorsMiddlewareFactory extracts the "cors" config array (or an empty array if it's not provided) and instantiates Tuupola's CorsMiddleware with it. That factory is then registered under the "dependencies" > "factories" section of cors.global.php.

    +
    +
    +
    + Where should the CORS middleware be added in the pipeline? + +
    +

    In config/pipelines.php via $app->pipe(CorsMiddleware::class), placed after the Error handler and before the middleware that provides the data you want to access.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.html.twig index 0582a873..9199b9ee 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.html.twig @@ -49,6 +49,41 @@ We realized that we had the wrong approach, especially for use. The WURFL entry Our solution involved adding a second cache layer, on top of WURFL's, that only cached the fields we were interested in.  This second cache layer make use of APC, storing arrays of data in  User Cache Entries. This small change (under 10 lines of code), decreased our response time by an order of magnitude, to 20-30ms. + +

    Frequently Asked Questions

    + +
    +
    + Why was WURFL's default caching too slow for this project? + +
    +

    Profiling revealed that WURFL's default filesystem cache was taking up to a few hundred milliseconds of response time, which was too slow for a relatively high-traffic application.

    +
    +
    +
    + How does WURFL's caching work by default? + +
    +

    Device data is stored in a large zipped XML file. On first use, WURFL unzips the file, serializes each device's data, and writes it to cache using an MD5 signature of the user agent as the key. Because devices are stored as a tree inheriting properties from parent nodes, each lookup requires reading and merging several files.

    +
    +
    +
    + Did WURFL's built-in APC or memcache cache providers solve the problem? + +
    +

    No. The team tried WURFL's existing cache providers for APC and memcache, but the results weren't impressive.

    +
    +
    +
    + What was the actual solution? + +
    +

    Adding a second cache layer on top of WURFL's own cache, using APC and storing arrays of only the specific fields they actually needed in User Cache Entries. The change was under 10 lines of code.

    +
    +
    +
    + What performance improvement did this bring? + +
    +

    Response time decreased by an order of magnitude, down to about 20-30ms.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig index 70df6234..9e3aa9b5 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig @@ -103,6 +103,41 @@ $myDependency->doSomething(); Learn more about how to use dependencies in DotKernel 1.x by reading this article. This article works for any DotKernel 1.x version if your server is running PHP >5.4.0. + +

    Frequently Asked Questions

    + +
    +
    + What does Composer do? + +
    +

    Composer is an application-level package manager. It auto-loads dependencies on demand and can also auto-load custom classes.

    +
    +
    +
    + What must a DotKernel project have before Composer can be used? + +
    +

    A composer.json file, for example requiring zendframework/zendframework1 at 1.12.* and mobiledetect/mobiledetectlib at 2.8.*, plus PHP >=5.4.0 listed under require-dev.

    +
    +
    +
    + What happens when you run composer update? + +
    +

    If the vendor folder already exists, composer checks for and applies updates to the packages. If it doesn't exist, composer creates the vendor folder containing all requested packages, along with an autoload file.

    +
    +
    +
    + How do you safely load the Composer autoloader in case Composer isn't present? + +
    +

    Check whether vendor/autoload.php exists using file_exists() before calling require_once() on it, and handle the case gracefully (for example by loading fallbacks) if the path is missing.

    +
    +
    +
    + Which DotKernel versions does this apply to? + +
    +

    The article states it works for any DotKernel 1.x version, as long as the server is running PHP greater than 5.4.0.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.html.twig index 9d1b2242..68814adc 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.html.twig @@ -58,6 +58,35 @@ A /images/os/windows_metro.png M stands for modify A stands for add + +

    Frequently Asked Questions

    + +
    +
    + What DotKernel version is required before applying this patch? + +
    +

    Your project must be running version 1.5.0 or newer.

    +
    +
    +
    + Which files does the patch modify or add? + +
    +

    It modifies configs/useragent/browser.xml and configs/useragent/os.xml, and adds images/browsers/edge.png and images/os/windows_metro.png.

    +
    +
    +
    + Why do you need to clear the cache after applying the patch? + +
    +

    Because the OS and browser XML files are cached, so the new icons won't show up until the cache is cleared.

    +
    +
    +
    + Will applying the patch overwrite existing files? + +
    +

    You'll be prompted to replace 2 files (browser.xml and os.xml) and should agree, and also agree to merge the folders' contents since the other files listed are added rather than replaced.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.html.twig index 23fb8ed5..b1690521 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.html.twig @@ -127,6 +127,47 @@

    Now, let's move to src/User/src/Repository/UserRepository.php and add the following methods: saveRememberUser(), getRememberUser(), findRememberMeUser(), deleteExpiredCookies(), removeRememberUser() as in UserRepository.

    The last step is to go to src/User/src/Controller/UserController.php, add a new property called $config and edit your loginAction() and logoutAction() as in UserController.

    + +

    Frequently Asked Questions

    + +
    +
    + Since which release is Remember Me implemented in Dotkernel? + +
    +

    It's implemented in Dotkernel Frontend starting from Release 3.3.0.

    +
    +
    +
    + How do you add the Remember Me checkbox to the login UI? + +
    +

    In src/User/templates/user/login.html.twig, under the password element, render the rememberMe element retrieved via form.get('rememberMe') with formElement().

    +
    +
    +
    + What form and validation changes are needed? + +
    +

    A 'rememberMe' checkbox element must be added to src/User/src/Form/LoginForm.php, and a StringTrim filter plus a NotEmpty validator must be added for it in src/User/src/InputFilter/LoginInputFilter.php.

    +
    +
    +
    + What backend pieces implement the actual remember-me functionality? + +
    +

    A new UserRememberMe entity, a database migration (created and run with phinx) for its table, a new RememberMeMiddleware registered in config/pipeline.php, a rememberMe cookie configuration block added to config/autoload/local.php, and new methods added to UserService, UserRepository, and UserController (including loginAction() and logoutAction()).

    +
    +
    +
    + What does the UserRememberMe table store? + +
    +

    It's used to store data from the cookie, which helps log the user in automatically.

    +
    +
    +
    + How do you apply the new remember-me button styles? + +
    +

    Add the CSS to src/App/assets/scss/components/_profile.scss, then run npm run prod to compile the CSS so the button styling takes effect.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.html.twig index 7b78edad..f7e254ba 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.html.twig @@ -43,6 +43,23 @@ add the line : Save and don't forget to test. Even though it's a well known fact that real men do not test... + +

    Frequently Asked Questions

    + +
    +
    + What problem does this fix address? + +
    +

    When missing static files (images, CSS, JS) are routed through the bootstrap (index.php) and the session is set to regenerate on each request, the currently logged-in user gets logged off because the session ID changes.

    +
    +
    +
    + What is the fix? + +
    +

    Below the "RewriteEngine On" line, add a RewriteCond matching file extensions like .gif, .jpg, .png, .css, and .js, so requests for those files are not routed through the bootstrap.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig index 722153a8..bf21b4de 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig @@ -72,6 +72,41 @@ else { echo 'no value cached for '. $id ; } + +

    Frequently Asked Questions

    + +
    +
    + What data does DotKernel's cache layer store? + +
    +

    Router, acl_role, menu, options (including seo_xml), browser_xml, os_xml, and test data between requests.

    +
    +
    +
    + What storage providers are available for the cache? + +
    +

    Two cache factories to choose from: APC (or APCU for newer PHP installations) and File.

    +
    +
    +
    + Where is the cache configured? + +
    +

    In /configs/application.ini, where you can enable or disable caching, set how long the cache stays valid, choose the cache namespace, and pick the storage provider (File or APC). The article recommends disabling the cache in development mode.

    +
    +
    +
    + Do you need to manually load the cache engine? + +
    +

    No, it's automatically loaded during kernel initialization (Dot_Kernel::initialize()). Manually calling Dot_Cache::loadCache() is only needed if you want to use caching outside of that normal initialization.

    +
    +
    +
    + Can you cache PHP objects, not just simple values? + +
    +

    Yes, the article shows an example of saving and loading a stdClass object using Dot_Cache::save() and Dot_Cache::load().

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/camelcase-table-names-in-mysql-on-windows.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/camelcase-table-names-in-mysql-on-windows.html.twig index 52af9467..3087abae 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/camelcase-table-names-in-mysql-on-windows.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/camelcase-table-names-in-mysql-on-windows.html.twig @@ -36,6 +36,23 @@ In order to fix this, you need to add to your my.cnf file the line: and restart mysql. More on that here: http://dev.mysql.com/doc/refman/4.1/en/server-system-variables.html#sysvar_lower_case_table_names + +

    Frequently Asked Questions

    + +
    +
    + What happens when you create a camelCase table name on WAMP or XAMPP? + +
    +

    A table created with a camelCase name, for example adminLogin, ends up stored as all lowercase, e.g. adminlogin, instead.

    +
    +
    +
    + How do you fix it? + +
    +

    Add the line lower_case_table_names=2 to your my.cnf file and restart MySQL.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.html.twig index db2bab22..a6022002 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.html.twig @@ -37,6 +37,17 @@ Now we are 5 :-) That mean we are really taking PHP  into serious , and at least we have good technical skills. Zend Yellow Pages + +

    Frequently Asked Questions

    + +
    +
    + How many Zend Certified Engineers does the team have? + +
    +

    According to the article, 2 more team members passed the ZCE exam, bringing the team's total to 5 Zend Certified Engineers.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/configuring-the-cache-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/configuring-the-cache-in-dotkernel.html.twig index d0b4208f..56e6013a 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/configuring-the-cache-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/configuring-the-cache-in-dotkernel.html.twig @@ -64,6 +64,35 @@ The setting pattern and sample are below:
    >cache.BACKEND_NAME.SETTING = "VALUE"
     ; example:
     cache.file.file_name_prefix = "DotKernel"
    + +

    Frequently Asked Questions

    + +
    +
    + What is DotKernel's caching layer based on? + +
    +

    It's based on Zend Framework Cache, configured through settings in application.ini, with more configuration options available at the Zend Framework Cache Frontends and Backends documentation links given in the article.

    +
    +
    +
    + What does the cache.enable setting do? + +
    +

    It can be used to disable caching, which is mostly useful during the development stage.

    +
    +
    +
    + What values can cache.factory take? + +
    +

    The cache.factory value is the cache service to use, and the article lists two options: file or apc.

    +
    +
    +
    + Why bother setting the individual/backend cache settings like cache.file.cache_dir? + +
    +

    These settings are optional, but the article highly recommends setting them, otherwise other projects might end up using the same cache.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig index c092064b..a32afa00 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig @@ -152,6 +152,41 @@ class UserHandler implements RequestHandlerInterface

    And, that's not all.

    If you use doctrine and repository pattern and you don't want to get your repository from EntityManager and want to inject it into your service, this package covers that too. The principle is the same, and for more insight about this, you can check the package documentation at dot-dependency-injection.

    + +

    Frequently Asked Questions

    + +
    +
    + What problem does dot-dependency-injection solve? + +
    +

    In Laminas/Mezzio applications, developers normally have to build, update, and maintain a factory class for every class that needs dependencies. dot-dependency-injection autowires those dependencies instead, so you don't need a factory for every class.

    +
    +
    +
    + Does dot-dependency-injection require Doctrine ORM? + +
    +

    The package requires Doctrine ORM, but the article notes it can still be used in applications that don't integrate Doctrine.

    +
    +
    +
    + How do you mark a class's constructor dependencies for injection? + +
    +

    Import Dot\DependencyInjection\Attribute\Inject in the class, then add the attribute to the constructor definition to specify which dependencies should be injected.

    +
    +
    +
    + What do you need to add to the ConfigProvider to use this package? + +
    +

    Only the Dot\DependencyInjection\Factory\AttributedServiceFactory class needs to be added to your config's dependencies. It builds the factory for you and handles the logic for dependencies missing from the container, with appropriate exceptions and messages.

    +
    +
    +
    + Can this package be used with the Doctrine repository pattern? + +
    +

    Yes. If you don't want to fetch a repository from the EntityManager and instead want to inject it directly into your service, the article says this package covers that too, following the same principle, with more details in the package documentation.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig index ff49d5a2..7939d6cf 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig @@ -102,6 +102,41 @@ If detected device is an mobile device we will save device info in database and } } } + +

    Frequently Asked Questions

    + +
    +
    + Why did mobile detection change in DotKernel 1.6.0? + +
    +

    Because of the new Wurfl Cloud integration. This version no longer ships with a working built-in method for mobile detection, so it must be configured first.

    +
    +
    +
    + What are the steps to configure Wurfl Cloud detection? + +
    +

    Go to the scientiamobile website and register for a Wurfl Cloud account, choose the device_os and mobile_browser capabilities for the account and save, then go to API Keys and copy the key into application.ini.

    +
    +
    +
    + Why choose the device_os and mobile_browser capabilities specifically? + +
    +

    With these two capabilities, DotKernel's built-in methods can also derive extra capabilities such as isMobile, isSmartPhone, isIphone, isAndroid, isBlackberry, isSymbian, and isWindowsMobile. Choosing other capabilities from scientiamobile results in wrong detection of these extra capabilities.

    +
    +
    +
    + What happened to the old Dot_UserAgent_Wurfl class? + +
    +

    It was removed and replaced with the new Dot_UserAgent_WurflCloud class, which uses the Wurfl Cloud API adapter.

    +
    +
    +
    + What does the redirect setting in application.ini control? + +
    +

    When resources.useragent.wurflcloud.redirect is TRUE (the default), visitors from the frontend are redirected to the mobile module the first time a mobile device is detected.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/disable-wurfl-redirect-for-mobile-browsers.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/disable-wurfl-redirect-for-mobile-browsers.html.twig index ab48f50e..02741ba5 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/disable-wurfl-redirect-for-mobile-browsers.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/disable-wurfl-redirect-for-mobile-browsers.html.twig @@ -40,6 +40,23 @@ Starting with revision 408, there's an option in application.iniControllers/frontend/IndexController.php
    (at line 19) to check the configuration:
    >//if automatic redirect is enabled in application.ini and the browser is mobile and session->mobileHit is not set, register it and redirect
     if($config->resources->useragent->wurflapi->redirect && 'mobile' == Dot_Kernel::getDevice()->getType() && !isset($session->mobileHit))
    + +

    Frequently Asked Questions

    + +
    +
    + How do you disable the automatic mobile redirect in DotKernel? + +
    +

    Starting with revision 408, set resources.useragent.wurflapi.redirect = false in application.ini. Per the article, this is also the default state of the redirect option.

    +
    +
    +
    + Where in the code is this configuration option checked? + +
    +

    In Controllers/frontend/IndexController.php (around line 19), a condition checks whether the redirect is enabled in application.ini, whether the visiting browser is mobile, and whether session->mobileHit isn't already set, before registering and redirecting the visit.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.html.twig index f31c51b8..d8c100a2 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.html.twig @@ -75,6 +75,41 @@ DotKernel 3 = DotKernel 3  

    + +

    Frequently Asked Questions

    + +
    +
    + What does the name "DotKernel" mean? + +
    +

    It combines "Dot", as a representation of the Internet, with "Kernel", the quintessence of any IT application, reflecting the aim of being a central part of Internet development.

    +
    +
    +
    + What is DotKernel 1? + +
    +

    A PHP Application Framework built on top of Zend Framework 1, first publicly released in July 2010, with an architecture based on MVC. Its latest version is 1.8 Long Term Support, which per the article will not be followed by a new version, only bugfixes.

    +
    +
    +
    + What is DotKernel 3? + +
    +

    A collection of PSR-7 Middleware applications built on top of the Zend Expressive microframework, composed of a set of custom and extended Zend Framework 3 components, with an architecture based on Middleware. It implements PSR-1, PSR-2, PSR-4, PSR-7, and PSR-11.

    +
    +
    +
    + How many applications make up DotKernel 3? + +
    +

    At the time of the article, there were two available applications, Frontend and Admin, with a third one, API, under development.

    +
    +
    +
    + When someone writes just "DotKernel", which version is meant? + +
    +

    In posts older than 2017, "DotKernel" referred to DotKernel 1, since it was the only version. Since the release of DotKernel 3, "DotKernel" refers to DotKernel 3, and all future references to DotKernel 1 are made explicitly.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig index 5087b9e1..2013febb 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig @@ -94,6 +94,47 @@ Doctrine hydration cache is a feature that stores the results of data hydration,
    $this->getQueryBuilder()
    ->select('admin')
    ->from(Admin::class, 'admin')
    ->setCacheable(true)
    ->getQuery()
    ->getResult();

    Caching is not limited to entities alone. Objects can be cached too. Check the basic cache usage for this purpose.

    In conclusion, cache plays a vital role in optimizing system performance and improving user experience by storing frequently accessed data. As technology continues to evolve, caching mechanisms will remain an integral part of modern computing architectures, driving faster access to data and smoother user interactions across various digital platforms.

    + +

    Frequently Asked Questions

    + +
    +
    + What component does this article use for caching Doctrine data? + +
    +

    The dotkernel/dot-cache component, a wrapper that sits on top of symfony/cache. It's installed with composer require dotkernel/dot-cache and registered by adding DotCacheConfigProvider::class to the configuration aggregate.

    +
    +
    +
    + What storage adapters does dot-cache currently support? + +
    +

    Two: array, which stores data in-memory and is the fastest option but uses more RAM, and filesystem, which stores data in local disk files and is slightly slower but more persistent.

    +
    +
    +
    + What are the four types of Doctrine cache covered? + +
    +

    result_cache, metadata_cache, query_cache, and hydration_cache, each configured under the doctrine.configuration.orm_default key.

    +
    +
    +
    + What does the result cache do? + +
    +

    It stores the results of queries, letting Doctrine avoid querying the database or hydrating the data again after the initial retrieval.

    +
    +
    +
    + Why cache class metadata? + +
    +

    Because parsing class metadata on every request is inefficient, so it's advisable to cache it using one of the available cache adapters.

    +
    +
    +
    + How do you mark an entity or a query as cacheable? + +
    +

    To enable caching for entities you add a caching attribute to the entity class; to cache an individual query, call setCacheable(true) on the query builder before getResult().

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig index a65ed93d..2949e386 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig @@ -234,6 +234,47 @@ new setup: status ENUM(\'active\', \'pending\') DEFAULT \'pending\' NOT NULLThe new setup solves both issues, ensuring more consistent flag management for your classes.

    +

    Frequently Asked Questions

    + +
    +
    + What update triggered this change to Dotkernel's enum handling? + +
    +

    The update of doctrine/orm to version 3.2.0 introduced EnumType columns, building on the enum type introduced in PHP 8.1. Dotkernel implemented this new data type on both the PHP side and the database side.

    +
    +
    +
    + What was the limitation of Dotkernel's old approach to columns like User->Status? + +
    +

    The old setup used a simple string type, so the value set couldn't be definitively enforced. A typo in a PHP value would still be accepted, and the database was independent of any values the PHP code allowed, so manually editing a value in the database would accept any string.

    +
    +
    +
    + What was the one advantage of the old string-based setup? + +
    +

    It made it easy to add more values to the value set, though the article notes this ease also invites bugs in the execution.

    +
    +
    +
    + What do you need to create to add a new custom enum type? + +
    +

    A PHP enum class (like UserStatusEnum) plus a DBAL type class extending AbstractEnumType, which must define a NAME constant and a getEnumClass() method; the new type is then registered under the types key in config/autoload/doctrine.global.php.

    +
    +
    +
    + Does Types::ENUM still fall back to a string or integer database column? + +
    +

    The article notes that Doctrine still defaults to Types::STRING or Types::INTEGER for columns backed by a PHP enum, as this is considered the more portable and safer default; Types::ENUM is required if you want an actual enum column in MySQL/MariaDB.

    +
    +
    +
    + What must happen when the value set of an enum changes under the new setup? + +
    +

    Any update to the value set must be made on both the PHP code and the database, since the new setup creates an explicit, enforced link between them.

    +
    +
    +
    +

    Relevant links

    + +

    Frequently Asked Questions

    + +
    +
    + What kind of release is DotKernel 1.3.2? + +
    +

    It's mainly a maintenance release, containing many bug fixes, some refactoring, and a few minor features.

    +
    +
    +
    + What bugs were fixed in 1.3.2? + +
    +

    Fixes include a CSS issue on the admin phpinfo page, a warning in the admin dashboard, a WURFL cache issue and WURFL version issue in admin, a Dot_Paginator bug, a Zend Paginator double-query issue, and a database naming convention issue.

    +
    +
    +
    + What minor features and refactoring were included? + +
    +

    Minor features include a refactor of validIP in Dot_Kernel and showing the WURFL date and API version in admin. Refactoring covered Zend_Paginator and added a dojo dijit theme to DotKernel.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig index 76c5ead0..0684f618 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig @@ -53,6 +53,41 @@ We've changed the structure of the registry, for more about this, please check < There have been about 250 commits in our SVN repository since the latest release, so we can't cover all changes in this blog post. Please download DotKernel 1.5.0 try it out yourself and tell us what you think.   + +

    Frequently Asked Questions

    + +
    +
    + Why did DotKernel jump from 1.3 straight to 1.5.0? + +
    +

    Because of the large amount of changes and the long time spent in development, the team chose to skip version 1.4 and go straight to 1.5.0.

    +
    +
    +
    + Did DotKernel switch from Dojo to jQuery in 1.5.0? + +
    +

    Yes. Starting with 1.5.0, DotKernel switched from Dojo to jQuery for its own distribution, though Dojo can still be used in your own projects.

    +
    +
    +
    + What is Dot_Model and why was it introduced? + +
    +

    Dot_Model is a base class introduced to reduce code duplication between admin and frontend models. Both admin- and frontend-specific model classes (such as User) inherit from a shared Dot_Model_User class that holds the common code.

    +
    +
    +
    + How does the "dashed controllers" feature work? + +
    +

    The controller name parsing was changed so a URL like www.example.com/search-article correctly calls SearchArticleController.php, allowing multi-word controller names split with dashes without breaking the coding standard.

    +
    +
    +
    + How much changed in the 1.5.0 release? + +
    +

    About 250 commits went into the SVN repository since the previous release, so the blog post only covers the highlights - the full DotKernel 1.5.0 download is available to try out.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig index de8252b9..8fe9d895 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig @@ -75,6 +75,47 @@ We've added a new class, Dot_Request, which lets you have control over the reque There have been a lot of commits in our SVN repository since the latest release, so we can't cover all changes in this blog post. Please download DotKernel 1.8.0 (LTS) try it out yourself and tell us what you think.   + +

    Frequently Asked Questions

    + +
    +
    + What does LTS mean for DotKernel 1.8.0? + +
    +

    LTS stands for Long-term support, a type of special version designed to be supported for longer than normal, which is particularly common for open-source software projects.

    +
    +
    +
    + What is the Plugin Architecture introduced in 1.8.0? + +
    +

    Starting with 1.8.0, DotKernel uses Plugins to make extending the framework easier.

    +
    +
    +
    + How does 1.8.0 load faster than previous versions? + +
    +

    It supports APC and File Caching within the framework, so XML files and config files are cached to maximize response speed.

    +
    +
    +
    + What is Dot_Request? + +
    +

    Dot_Request is a new class that gives you control over the request data before you use it, so that the variables $_SERVER, $_GET and $_POST are only accessed within controllers.

    +
    +
    +
    + What was removed from DotKernel in 1.8.0? + +
    +

    WURFL integration was removed (mobile device detection is now handled separately, and WURFL can be added as a plugin), and support for multiple SMTP transporters was removed (it can also be added as a plugin).

    +
    +
    +
    + What security-related additions does 1.8.0 include? + +
    +

    A security scan in the Admin Dashboard shows recommended settings, admin failed-login notifications are sent to all developers listed in devEmails (not just the first admin), and a new Alert System can notify developers if something goes wrong.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.html.twig index 3c707539..2bf997fe 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.html.twig @@ -42,6 +42,23 @@ Here are some useful download links: Upgrade from DotKernel 1.8.0 DotKernel 1.8.0 (LTS) + +

    Frequently Asked Questions

    + +
    +
    + What's new in DotKernel 1.8.1? + +
    +

    The main change is Enhanced Cache Support, which means you can use tags in your cache system if the host supports it.

    +
    +
    +
    + How do I upgrade from 1.8.0 to 1.8.1? + +
    +

    The post provides a dedicated "Upgrade from DotKernel 1.8.0" download link, separate from the full DotKernel 1.8.1 package and the original DotKernel 1.8.0 (LTS) download.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-coding-standard.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-coding-standard.html.twig index e93c7c62..658c0065 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-coding-standard.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-coding-standard.html.twig @@ -67,6 +67,35 @@ e.g: $c = 3; } } + +

    Frequently Asked Questions

    + +
    +
    + What coding standard does DotKernel follow? + +
    +

    DotKernel borrows its coding standard from the Zend Framework Coding Standard, with some exceptions described in this article.

    +
    +
    +
    + Tabs or spaces for indentation? + +
    +

    DotKernel indents with tabs, not spaces.

    +
    +
    +
    + How should classes, interfaces, and filenames be named? + +
    +

    Classes start with the prefix Dot_ (e.g. Dot_Templates), interfaces end with the string "Interface" (e.g. Dot_Db_Interface), and all PHP files use the ".php" extension, with no fancy extensions like ".inc".

    +
    +
    +
    + How should curly braces be placed for control statements? + +
    +

    Every opening curly brace starts on its own new line after the statement, and its matching closing brace also goes on a new line, aligned in the same column as the opening brace, for better indentation of the code.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-database-naming-conventions-for-mysql.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-database-naming-conventions-for-mysql.html.twig index 0a058c59..a1004bcc 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-database-naming-conventions-for-mysql.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-database-naming-conventions-for-mysql.html.twig @@ -70,6 +70,41 @@ DotKernel borrows the database naming conventions from Frequently Asked Questions + +
    +
    + Where do DotKernel's database naming conventions come from? + +
    +

    They are borrowed from FaZend's "Rules of naming of database tables and columns," an open-source PHP framework based on Zend Framework.

    +
    +
    +
    + Should table names be singular or plural? + +
    +

    Singular table names only, for example user, category, product, order, orderProduct.

    +
    +
    +
    + How should foreign key columns be named? + +
    +

    A foreign key column takes the name of the referenced table plus the name of the referenced column. For example, referencing table admin's Id column produces a column named adminId.

    +
    +
    +
    + What naming pattern is used for CONSTRAINT names? + +
    +

    The pattern is FK_referencedTableName_tableName, for example CONSTRAINT `FK_admin_adminLogin`.

    +
    +
    +
    + What casing convention applies to table/column names and to SQL keywords? + +
    +

    Table and column names must follow camelLetter naming conventions, while SQL keywords such as SELECT and INT are capitalized.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.html.twig index 328d2ab5..a14223ff 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.html.twig @@ -126,6 +126,41 @@
  • rector/rector
  • +

    Frequently Asked Questions

    + +
    +
    + What is Dotkernel Light? + +
    +

    Dotkernel Light is a version of Dotkernel Frontend that includes only the bare-bones essentials. It's suitable as a presentation site, an introduction to the Mezzio microframework architecture, or a starting point for a more complex project where you want full control over functionality.

    +
    +
    +
    + What is the goal of Dotkernel Light? + +
    +

    It's designed to be a fast-start example of using the Mezzio microframework as well as an entry-level version of Dotkernel Frontend, presenting the beginner developer with as few moving parts as possible while still letting the more advanced developer have full control of the platform's functionality.

    +
    +
    +
    + What functionality does Dotkernel Light retain? + +
    +

    It keeps routing, templating, error handling, and tests and code quality checks.

    +
    +
    +
    + What was removed compared to Dotkernel Frontend? + +
    +

    Items removed include Doctrine and all database related stuff, sessions/cookies/flash messages, authentication/authorization, dependency injection, mail related stuff, navigation, CORS, forms/validators/input filters, the User module, the Contact module, the Plugin module, unused CSS/JS code, and outdated README instructions.

    +
    +
    +
    + Which packages are no longer required in Dotkernel Light? + +
    +

    Packages such as dotkernel/dot-authorization, dotkernel/dot-mail, dotkernel/dot-session, dotkernel/dot-navigation, dotkernel/dot-flashmessenger, laminas/laminas-form, mezzio/mezzio-cors, and several others used by Frontend are not required.

    +
    +
    +
    +

    Useful links

      + +

    Frequently Asked Questions

    + +
    +
    + What two kinds of error handlers does dot-errorhandler provide? + +
    +

    A plain ErrorHandler, which is a copy of Zend Expressive's Zend\Stratigility\Middleware\ErrorHandler (copied because that class is final), and a logging LogErrorHandler, which is the same but with added logging support via the container. Both have factories registered for easier use with the Container.

    +
    +
    +
    + How do you install dot-errorhandler? + +
    +

    Run composer require dotkernel/dot-errorhandler.

    +
    +
    +
    + Which error handler is used by default? + +
    +

    The plain error handler is selected by default as a fallback, and this can be overwritten through the config file, which also registers factories for both handlers plus an alias to switch between them.

    +
    +
    +
    + What must already be in place before configuring the logging error handler? + +
    +

    A configured logger as described in the "Logging with dot-log in Zend Expressive and DotKernel" article, with the logger named default_logger as used in the package's config example.

    +
    +
    +
    + Why might whoops interfere when testing dot-errorhandler? + +
    +

    dot-errorhandler is meant to be a silent logger for staging and production environments. To actually test it, development mode should be disabled, otherwise whoops will catch the errors and show them to the developer instead.

    +
    +
    +
    + How were errors triggered to test the error handlers? + +
    +

    By throwing Exceptions, the most common case, and by raising warnings/errors such as dividing a number by zero (e.g. 16/0) or casting an array to a string.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/highcharts-integration-in-dotkernel-1-6-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/highcharts-integration-in-dotkernel-1-6-0.html.twig index 5c608308..71ff6f27 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/highcharts-integration-in-dotkernel-1-6-0.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/highcharts-integration-in-dotkernel-1-6-0.html.twig @@ -78,6 +78,29 @@ Take a quick view on the code to see how highcharts are working. series: timeActivity });   + +

    Frequently Asked Questions

    + +
    +
    + What charting library was integrated in DotKernel 1.6.0? + +
    +

    Highcharts was integrated in DotKernel 1.6.0, offering a new, intuitive and interactive charting experience.

    +
    +
    +
    + What sample charts were added to the admin? + +
    +

    The admin includes samples made with Highcharts: a pie chart (with a small custom feature), a column chart, and a line chart.

    +
    +
    +
    + Where can the Highcharts library be found in a DotKernel project? + +
    +

    The Highcharts library is located in the externals directory of the project.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/how-to-group-log-files-by-date-using-dot-log.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/how-to-group-log-files-by-date-using-dot-log.html.twig index 5b025889..dc5a5309 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/how-to-group-log-files-by-date-using-dot-log.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/how-to-group-log-files-by-date-using-dot-log.html.twig @@ -178,6 +178,47 @@ return [

    If the path to your log files contains other date format specifiers, make sure you adapt it accordingly. A complete list of the specifiers can be found here.

    Make sure you clear your application's config before usage.

    + +

    Frequently Asked Questions

    + +
    +
    + What does dot-log Version 3.1.1 add? + +
    +

    Version 3.1.1 adds the ability to use datetime formatter strings directly in the stream option of your log writer, and it fixes an issue where caching dot-log configs caused logs to be written to the same file instead of being grouped by date.

    +
    +
    +
    + How do I make sure I'm using the fix in Version 3.1.1? + +
    +

    Make sure you're using the latest version of dot-log by running composer update dotkernel/dot-log.

    +
    +
    +
    + Do I need to change anything if my logs are already written to a single static file? + +
    +

    No. If your application logs messages in a single file with a static name (e.g. log/dk.log), you can skip the rest of the article - logging will work as before.

    +
    +
    +
    + How do I group log files by date when using DotKernel? + +
    +

    In config/autoload/error-handling.global.php, locate every log writer by navigating to dot_log->loggers->default_logger->writers and replace a stream value like sprintf('%s/../../log/error-log-%s.log', __DIR__, date('Y-m-d')) with __DIR__ . '/../../log/error-log-{Y}-{m}-{d}.log'.

    +
    +
    +
    + How do I group log files by date without DotKernel? + +
    +

    In your dot-log config (e.g. /config/autoload/log.global.php), locate every writer under dot_log->loggers->my_logger->writers and replace a dynamic stream value such as sprintf('%s/../../log/dk-%s.log', __DIR__, date('Y-m-d')) with a formatter string like __DIR__ . '/../../log/error-log-{Y}-{m}-{d}.log'.

    +
    +
    +
    + What must I do after changing the stream configuration? + +
    +

    Clear the config cache. In a DotKernel application, run php bin/clear-config-cache.php; in a non-DotKernel setup, make sure you clear your application's config before usage.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.html.twig index f69e36ad..a1cc13f4 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.html.twig @@ -40,6 +40,29 @@ Do not try something like below, will not work: Instead , use the below line
    >database.params.persistent = TRUE
    + +

    Frequently Asked Questions

    + +
    +
    + Where do you configure a persistent database connection in a DotKernel project? + +
    +

    The option should be added in application.ini, as shown in the DotKernel approach used as an example in the article.

    +
    +
    +
    + What Zend_Db configuration line should NOT be used for a persistent connection? + +
    +

    database.params.options.PDO::ATTR_PERSISTENT = TRUE should not be used - the article states that it will not work.

    +
    +
    +
    + What's the correct line to enable a persistent connection? + +
    +

    Use database.params.persistent = TRUE instead.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/how-to-use-alerts-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/how-to-use-alerts-in-dotkernel.html.twig index 0586be84..022723ec 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/how-to-use-alerts-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/how-to-use-alerts-in-dotkernel.html.twig @@ -96,6 +96,41 @@ Our message doesn't look that good, the setDetails method will replace our >$alert->setDetails($details); Everything is great, we can now send our alert:
    >$alert->send();
    + +

    Frequently Asked Questions

    + +
    +
    + What is a Dot_Alert used for? + +
    +

    Alerts (Dot_Alert's) are e-mails usually sent to the site developers using PHP's mail() function. They shouldn't be used to send regular mail - they only notify the developer that something is wrong.

    +
    +
    +
    + What does the Dot_Alert class resemble, and what does an alert contain? + +
    +

    Dot_Alert resembles Dot_Email. Like a mail message, an alert has at least a sender, a subject, a destination and a message, and it can be sent.

    +
    +
    +
    + What's an example use of Alerts in DotKernel? + +
    +

    DotKernel uses Alerts to notify the developer when an email was not sent successfully, with the message kept in the dots.xml file under the alertMessages section.

    +
    +
    +
    + How are the {VARIABLES} in an alert message replaced with real data? + +
    +

    A $details array is prepared (e.g. e_class, site_name, site_url, e_message, to_email, from_email, date_now), and the alert's setDetails() method replaces the {VARIABLES} placeholders in the subject and message with that data.

    +
    +
    +
    + What are the steps to build and send an alert? + +
    +

    Create a new Dot_Alert(), add headers such as From, Reply-To and X-Mailer, then call setTo(), setSubject() and setContent(), then setDetails() to fill in the placeholders, and finally call send().

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.html.twig index 156f0f4b..b3c8712c 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.html.twig @@ -63,6 +63,41 @@ The minimum PHP version in order to run DotKernel was raised to PHP 5.3.8 .     + +

    Frequently Asked Questions

    + +
    +
    + Why was DotKernel refactored to use the new PHP 5.5 Password Hashing API? + +
    +

    To use the new Password Hashing functions introduced in PHP 5.5 and unify all password related functions used for both admin and users, DotKernel's codebase was refactored in version 1.8.0, starting from revision 799.

    +
    +
    +
    + How can PHP versions older than 5.5 use these new hashing functions? + +
    +

    Since the 4 new functions are only available since PHP 5.5, DotKernel used the Password Compatibility library (ircmaxell/password_compat).

    +
    +
    +
    + What is the minimum PHP version required after this change? + +
    +

    The minimum PHP version required to run DotKernel was raised to PHP 5.3.8.

    +
    +
    +
    + What steps are needed to apply this refactor to an older DotKernel system? + +
    +

    Change the password column definition in the admin and user tables via ALTER TABLE, remove the settings.admin.salt entry from application.ini, apply the provided patch, and run the conversion script described in Console/Controller.php at line 47.

    +
    +
    +
    + What happens to existing admin passwords during the upgrade? + +
    +

    Admin passwords cannot be converted automatically, so they need to be recreated manually.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.html.twig index 9982d30f..81079331 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.html.twig @@ -76,7 +76,48 @@ Copy those .dat files in C:\YOUR_LOCATION\Zend\ZendServer\bin\ And now you can use geoip functions on your Zend Server 5.6 - + +

    Frequently Asked Questions

    + +
    +
    + How do you test if the php_geoip extension is available on Zend Server? + +
    +

    Create a PHP file with var_dump(function_exists('geoip_database_info'));. It outputs true if the extension is available, or false if it isn't.

    +
    +
    +
    + Where can you download the php_geoip extension for Zend Server 5.6? + +
    +

    From http://windows.php.net/downloads/pecl/releases/geoip/1.0.8/, where several php_geoip.dll files are available for different architectures and compilers.

    +
    +
    +
    + How do you know which php_geoip.dll file to download? + +
    +

    You can find your PHP's architecture and the compiler it was built with by running phpinfo().

    +
    +
    +
    + Where does the php_geoip.dll file need to be copied? + +
    +

    Into C:\YOUR_LOCATION\Zend\ZendServer\lib\phpext\.

    +
    +
    +
    + How do you enable the geoip extension after copying the dll? + +
    +

    Go into the Zend Server interface, restart PHP, turn on the geoip extension from Server Setup -> Extension, and restart PHP again. If you get an error loading the extension, you picked the wrong dll and should try another one.

    +
    +
    +
    + What else is needed besides the extension itself? + +
    +

    You also need to download geoip databases from MaxMind, such as geoIP.dat, geoIPCity.dat, and geoIPOrganization.dat, and copy those .dat files into C:\YOUR_LOCATION\Zend\ZendServer\bin\.

    +
    +
    +
    + {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.html.twig index 78292d3c..b19c9af0 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.html.twig @@ -39,6 +39,35 @@ As an update to the post Save the file and "Restart PHP" :-))  from Zend Server GUI
  • follow the step from the previous article in order to test GeoIP integration , and download the Geoip*.dat files
  • + +

    Frequently Asked Questions

    + +
    +
    + Which php_geoip file should be downloaded for Zend Server 6.1? + +
    +

    Download the php_geoip-1.0.8-5.4-nts-vc9-x86.zip file from http://windows.php.net/downloads/pecl/releases/geoip/1.0.8/.

    +
    +
    +
    + Where does the php_geoip.dll go on Zend Server 6.1? + +
    +

    Copy the php_geoip.dll file from the archive to ZEND_PATH\Zend\ZendServer\lib\phpext\.

    +
    +
    +
    + How is the extension enabled on Zend Server 6.1? + +
    +

    Open the php.ini file at ZEND_PATH\Zend\ZendServer\etc\php.ini, add the line extension=php_geoip.dll at the end of the file, save it, and click "Restart PHP" from the Zend Server GUI.

    +
    +
    +
    + How do you verify GeoIP is working after this setup? + +
    +

    Follow the same steps described in the previous article (Installing GeoIP extension in Zend Server 5.6 on Windows) to test the GeoIP integration and download the Geoip*.dat files.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig index ffa49eb3..18c7e1f3 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig @@ -249,6 +249,48 @@ $logger->notice('5 NOTICE'); $logger->info('6 INF'); $logger->debug('7 debug'); $logger->log(Logger::NOTICE, 'NOTICE from log()'); + +

    Frequently Asked Questions

    + +
    +
    + How do I register dot-log's config provider? + +
    +

    In config/config.php, add \Dot\Log\ConfigProvider::class if it's not already there, making sure it is added before application-specific components such as Frontend\App\ConfigProvider or Admin\App\ConfigProvider.

    +
    +
    +
    + How is a logger retrieved from the container instead of using the plain class name? + +
    +

    Dot\Log\ConfigProvider registers an abstract factory, LoggerAbstractServiceFactory, that responds to "selectors" instead of class names. Instead of requesting Zend\Log\Logger::class from the container, you request dot-log.my_logger (or just my_logger if using zend-log).

    +
    +
    +
    + What is a writer, and how many does a logger need? + +
    +

    A writer is an object that inherits from Zend\Log\Writer\AbstractWriter and is responsible for recording log data to a storage backend. Loggers must have at least one writer, and the writer's "name" key is mandatory while its "priority" key is optional and only used to organize writers, not to affect which errors get written.

    +
    +
    +
    + What does a filter do, and how are log levels ordered? + +
    +

    A filter prevents a message from being written to the log. Per PSR-3, the log levels in order of priority/importance are emergency (0), alert (1), critical (2), error (3), warn (4), notice (5), info (6), and debug (7) - the operator for "more important" messages is <= because a smaller number represents a more important message.

    +
    +
    +
    + What does the formatter configuration control? + +
    +

    The formatter accepts a "name" (a class implementing Zend\Log\Formatter\FormatterInterface) and "options" to pass to that formatter's constructor. Zend Log provides String, XML, JSON and FirePHP formatting, and arrays can be serialized this way for better readability.

    +
    +
    +
    + How do you actually write log messages once the logger is configured? + +
    +

    Fetch the logger from the container, e.g. $logger = $container->get('dot-log.my_logger');, then call methods such as emerg(), alert(), crit(), err(), warn(), notice(), info(), debug(), or the generic log(Logger::NOTICE, 'message').

    +
    +
    +
    +

    Sources:

    At this moment, mime and imap related functionality is removed.

    + +

    Frequently Asked Questions

    + +
    +
    + Why was laminas/laminas-mail replaced? + +
    +

    The Laminas Technical Steering Committee decided on 2023-12-04 that laminas/laminas-mail would be abandoned because there was nobody to maintain it, and several alternatives were already available in the ecosystem.

    +
    +
    +
    + What alternatives were identified for the abandoned laminas-mail package? + +
    +

    The alternatives mentioned are ddeboer/imap for interacting with IMAP, zbateson/mail-mime-parser for parsing MIME messages, and symfony/mailer for sending mail.

    +
    +
    +
    + How did Dotkernel handle the removal of laminas-mail in dot-mail? + +
    +

    The Dotkernel team replaced laminas/laminas-mail with symfony/mailer inside the dotkernel/dot-mail package, revising the code and configuration files while aiming for minimal impact on existing projects, so the calls used to send mail stay the same, even though some lesser-used functionality like mime and imap is lost.

    +
    +
    +
    + Which mail transport does dot-mail use by default? + +
    +

    The revised mail.global.php configuration defaults the transport to sendmail. You should use ONE of the transporters, either 'transport' => 'sendmail' or 'transport' => 'esmtp', based on your server configuration; Sendmail was set as the default.

    +
    +
    +
    + How do I update dotkernel/dot-mail from version 3 or 4 to version 5? + +
    +

    Download the new mail.global.php.dist configuration file, add the values you configured for your project (focusing on transport, message_options and smtp_options) to replace your old configuration file, then update "dotkernel/dot-mail" to "^5.0.0" in composer.json and run composer update.

    +
    +
    +
    + What functionality is lost after switching to symfony/mailer? + +
    +

    At the time of the article, mime and imap related functionality is removed from dot-mail as a result of the switch.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.html.twig index d5fce107..4a0313cc 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.html.twig @@ -49,6 +49,35 @@ with the Dotkernel software framework, and does not allow makin even when the source code is provided or the language is an interpreted one and regardless of whether the code is not obfuscated.   + +

    Frequently Asked Questions

    + +
    +
    + Why was the WURFL PHP library removed from DotKernel 1.6.0? + +
    +

    In DotKernel 1.6.0, released on May 16th, 2012, the GPL'ed WURFL PHP library was removed because the code was obsolete anyway and the structure of the XML file had changed.

    +
    +
    +
    + What replaced the old WURFL PHP library? + +
    +

    DotKernel implemented Scientia Mobile's WURFL Cloud PHP library instead.

    +
    +
    +
    + Where can I find instructions to set up the WURFL Cloud API key? + +
    +

    Instructions on how to sign up for a free account and set up your API key are linked in the article, pointing to the "Detecting Mobile Devices in DotKernel 1.6" article.

    +
    +
    +
    + What are the terms of DotKernel's special WURFL Cloud license? + +
    +

    The license is non-transferable, conditioned, limited to the version received, and restricted to use and distribution only in connection with the Dotkernel software framework. It does not allow making derivative works, even when source code is provided or the code is not obfuscated.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/sending-emails-using-dot-email-component-and-zend-email.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/sending-emails-using-dot-email-component-and-zend-email.html.twig index 1813d8dd..13fe1fe8 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/sending-emails-using-dot-email-component-and-zend-email.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/sending-emails-using-dot-email-component-and-zend-email.html.twig @@ -72,6 +72,41 @@ Methods inherited  from Zend_Mail and that can be used in your code are:
  • setSubject($subject)
  • addHeader($name, $value, $append = false)
  • + +

    Frequently Asked Questions

    + +
    +
    + What is Dot_Email? + +
    +

    Dot_Email is a class that extends Zend_Mail, so all methods from Zend_Mail are available in it. Aside from its constructor, Dot_Email itself is composed of only two methods, with everything else inherited from Zend_Mail.

    +
    +
    +
    + What are the two methods that Dot_Email adds on top of Zend_Mail? + +
    +

    setContent($content, $format = 'text/plain') sets the body of the email by calling setBodyText() when the format is "text/plain" or setBodyHtml() when the format is "text/html". send() sets the transporter and calls the parent::send() method to send the email.

    +
    +
    +
    + What is the minimal sequence of calls needed to send an email with Dot_Email? + +
    +

    You must always use addTo(), setSubject(), and setBodyText() or setBodyHtml() or setContent(), and finally call send(), as shown in the article's example that creates a new Dot_Email, adds a recipient, sets the subject and body text, then sends it and checks the returned success value.

    +
    +
    +
    + How do I override the default From and Reply-To addresses? + +
    +

    Only if you want to overwrite the default Email and Name, add calls to setFrom($fromEmail, $fromName) and setReplyTo($fromEmail, $fromName).

    +
    +
    +
    + Which Zend_Mail methods are inherited and usable through Dot_Email? + +
    +

    Inherited methods listed in the article include setBodyText(), setBodyHtml(), addTo(), addCc(), addBcc(), setFrom(), setReplyTo(), setReturnPath(), setSubject(), and addHeader().

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/templating-in-dotkernel3.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/templating-in-dotkernel3.html.twig index e36741b7..beea122f 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/templating-in-dotkernel3.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/templating-in-dotkernel3.html.twig @@ -45,6 +45,35 @@ In DotKernel 3, we made the move to the popular Templates + +

    Frequently Asked Questions

    + +
    +
    + Which templating engine does DotKernel3 use? + +
    +

    DotKernel3 moved to the popular Twig Templating Engine.

    +
    +
    +
    + Why did DotKernel move away from the previous templating engine? + +
    +

    The previous templating engine, although solid and robust, was also 10 years old and used techniques that had become slightly outdated, and it was one of the pain points in the previous version of DotKernel.

    +
    +
    +
    + What features does Twig bring to DotKernel3 templates? + +
    +

    Templates can now be written as HTML code with extra Twig tags, and Twig includes features such as layouts, loops, variables, and escaping, giving the familiarity of HTML with the overview and convenience of PHP.

    +
    +
    +
    + Where can I read the official Twig templating documentation for DotKernel? + +
    +

    The official documentation is linked in the article at docs.dotkernel.com/Prerequisites/Templates.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig index 524450b9..007b0eda 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig @@ -99,6 +99,47 @@ The full examples can be found here: Zend Framework 2 - Rendering a barcode This article works for any DotKernel 1.x version if your server is running PHP >5.4.0. + +

    Frequently Asked Questions

    + +
    +
    + Do I need to manually include or require Composer dependencies in DotKernel? + +
    +

    No. Composer automatically loads dependencies, so there is no need to include or require them yourself.

    +
    +
    +
    + What example does the article use to demonstrate Composer dependencies? + +
    +

    The article renders a Barcode using Zend Framework 1 as the Non-Namespaced dependency (installed with composer require 'zendframework/zendframework1') and Zend Framework 2 as the Namespaced dependency (installed with composer require 'zendframework/zend-barcode').

    +
    +
    +
    + When can these Composer dependencies be used in the application? + +
    +

    These dependencies can be used anywhere after the Dot_Kernel::initialize() function has been called.

    +
    +
    +
    + How are non-namespaced (Zend Framework 1) classes loaded compared to namespaced (Zend Framework 2) classes? + +
    +

    Non-namespaced ZF1 classes are loaded PSR-0 style, meaning the class name looks like VendorName_PackageName_ClassName (e.g. Zend_Barcode). Namespaced ZF2 classes are loaded PSR-4 style, meaning the class name looks like \VendorName\PackageName\ClassName (e.g. Zend\Barcode\Barcode).

    +
    +
    +
    + Can the same code work for both namespaced and non-namespaced barcode dependencies? + +
    +

    Yes. If you add "use Zend\Barcode\Barcode as Zend_Barcode;" as the first line, the ZF1-style example will work for both, and you can then reference the module as Zend\Barcode\Barcode, Barcode, or Zend_Barcode.

    +
    +
    +
    + What DotKernel and PHP versions does this article apply to? + +
    +

    This article works for any DotKernel 1.x version if your server is running PHP greater than 5.4.0.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig index 93baccbf..5da9b2fa 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig @@ -50,6 +50,35 @@ For example, the following will evaluate at true with  utf8_general_ci collatio Ü = U Those differences happens only on MySQL level ( for instance in queries using LIKE operator) and not at PHP level ( for instance, in str_replace() calls ) + +

    Frequently Asked Questions

    + +
    +
    + What needs to change to use UTF8 encoding in a DotKernel-based system? + +
    +

    Both the database structure and the application.ini file need changes: all tables and columns must have a proper UTF8 collation (utf8_general_ci or utf8_bin), and application.ini must load the utf8 charset.

    +
    +
    +
    + What line should I add to application.ini to enable UTF8? + +
    +

    Add the line database.params.charset = utf8 to your application.ini file.

    +
    +
    +
    + What is the difference between utf8_general_ci and utf8_bin collation? + +
    +

    utf8_bin compares strings by the binary value of each character, while utf8_general_ci compares strings using general language rules with case-insensitive comparisons. For example, Ä = A, Ö = O, and Ü = U evaluate as true under utf8_general_ci but not under utf8_bin.

    +
    +
    +
    + Does the collation choice affect PHP string functions too? + +
    +

    No. These collation differences only happen at the MySQL level, for instance in queries using the LIKE operator, and not at the PHP level, for instance in str_replace() calls.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.html.twig index 8c5889d7..2419f309 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.html.twig @@ -50,6 +50,35 @@ Quick steps to have a functional device detection in DotKernel. Now you can test it by changing the user agent in your browser to a mobile device and access your project. Now you can see that you are redirected to the mobile module. To see how mobile detection is working in DotKernel read this post. + +

    Frequently Asked Questions

    + +
    +
    + What is WURFL Cloud? + +
    +

    WURFL Cloud is a new way that WURFL (Wireless Universal Resource FiLe) delivers its device detection services. DotKernel integrated it as the default detection method for mobile devices starting with version 1.6.0.

    +
    +
    +
    + How do I get a functional device detection setup with WURFL Cloud? + +
    +

    Register for a Wurfl Cloud account with Scientia Mobile, choose device_os and mobile_browser as your capabilities, then copy your API key into application.ini in DotKernel.

    +
    +
    +
    + What line do I add to application.ini for the API key? + +
    +

    Add a line such as resources.useragent.wurflcloud.api_key = 000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX, using your own API key.

    +
    +
    +
    + How can I test that mobile detection is working? + +
    +

    Change the user agent in your browser to a mobile device and access your project; you should be redirected to the mobile module.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.html.twig index bc90a1ff..0a312206 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.html.twig @@ -48,6 +48,29 @@ But we must keep here
    + +

    Frequently Asked Questions

    + +
    +
    + Why did the WURFL PHP API license become incompatible with DotKernel? + +
    +

    The WURFL PHP API used to be licensed under GNU/GPL, compatible with both Zend Framework's new BSD license and DotKernel's OSL 3.0 license. On June 6th, 2011, version 1.3.0 of the library changed its license to AGPL, turning it into a "trial only" library for product evaluation, which is incompatible with keeping DotKernel free.

    +
    +
    +
    + Which version introduced the license change, and when? + +
    +

    Version 1.3.0 of the WURFL PHP API, released June 6th, 2011, changed the license from GNU/GPL to AGPL. DotKernel had updated to this version in its 1.5.0 release candidate without noticing the license change.

    +
    +
    +
    + What did DotKernel do in response to the license change? + +
    +

    DotKernel planned to remove WURFL PHP API version 1.3.0 from its codebase and reintegrate version 1.2.1, which was still GNU/GPL. It also intended to keep contributing to the free WURFL XML data, and considered forking the 1.2.1 library and renaming it to avoid trademark issues.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-zend-framework-integration-into-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/wurfl-zend-framework-integration-into-dotkernel.html.twig index 554f8242..44805bb0 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-zend-framework-integration-into-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/wurfl-zend-framework-integration-into-dotkernel.html.twig @@ -69,6 +69,41 @@ resources.useragent.wurflapi.wurfl_config_file = APPLICATION_PATH "/configs/wurf
    >$userAgent = new Zend_Http_UserAgent($config->resources->useragent);
     $device = $userAgent->getDevice();
    $userAgent->getDevice() returns all the relevant information about the current user agent ($_SERVER) + +

    Frequently Asked Questions

    + +
    +
    + Which Zend Framework class is used to integrate WURFL into DotKernel? + +
    +

    WURFL is integrated using the Zend_Http_UserAgent class from Zend Framework 1.11.0rc1 (the beta release at the time of the post).

    +
    +
    +
    + What is the first step to integrate WURFL into DotKernel? + +
    +

    Download the WURFL PHP API and unzip it into a folder named wurfl-php-1.1.

    +
    +
    +
    + Which folders need to be created and made writable? + +
    +

    Two folders must be created and made writable by the web server: cache/wurfl/FILE_CACHE_PROVIDER and cache/wurfl/FILE_PERSISTENCE_PROVIDER.

    +
    +
    +
    + What needs to be added to application.ini? + +
    +

    Three lines need to be added: resources.useragent.wurflapi.wurfl_api_version = "1.1", resources.useragent.wurflapi.wurfl_lib_dir pointing to APPLICATION_PATH "/library/Wurfl/", and resources.useragent.wurflapi.wurfl_config_file pointing to APPLICATION_PATH "/configs/wurfl.xml".

    +
    +
    +
    + How do you access WURFL configuration in code? + +
    +

    Instantiate a Zend_Http_UserAgent with the useragent config, then call getDevice() on it, for example $userAgent = new Zend_Http_UserAgent($config->resources->useragent); $device = $userAgent->getDevice();. getDevice() returns all the relevant information about the current user agent.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.html.twig index 63fe5ca0..c8362e81 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.html.twig @@ -75,6 +75,41 @@ $this->acl = new Zend_Acl(); IndexController.php is calling Dot_Auth
    >$dotAuth = Dot_Auth::getInstance();
     $dotAuth->checkIdentity('user');
    + +

    Frequently Asked Questions

    + +
    +
    + When were Zend_Auth and Zend_Acl integrated into DotKernel? + +
    +

    Zend_Auth and Zend_Acl were integrated starting with DotKernel version 1.5.0, as part of a refactor of the User and Admin models using the new Dot_Auth and Dot_Acl classes.

    +
    +
    +
    + How does Dot_Auth authenticate a user? + +
    +

    Dot_Auth authenticates the user by checking the database using Zend_Auth_Adapter_DbTable, setting the table name, the identity column to "username", and the credential column to "password".

    +
    +
    +
    + What roles exist for ACL in DotKernel? + +
    +

    DotKernel defines 3 ACL roles (user types): guest, user, and admin. These roles and their permissions are configured in the configs/acl/role.xml file.

    +
    +
    +
    + Where do the ACL resources come from? + +
    +

    The resources for ACL are taken from configs/router.xml, specifically the controllers tag.

    +
    +
    +
    + What does the Dot_Acl class do? + +
    +

    Dot_Acl controls user access (Access Controller Layer), is used for setting and checking a user's permissions, and uses Zend_Acl to check whether a role (user) has access to a resource (controller). Dot_Auth calls the isAllowed method from Dot_Acl to authenticate the user.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig index e660adf6..147905aa 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig @@ -47,6 +47,41 @@ The controller bundled with DotKernel has two example actions, "count-users" whi

    Examples

    >/var/www/vhosts/example.com/httpdocs/Cron/index.php -e staging -a count-users
    >/var/www/vhosts/example.com/httpdocs/Cron/index.php -e staging -a send-newsletter "test newsletter"
    + +

    Frequently Asked Questions

    + +
    +
    + What is the purpose of the Console bootstrap in DotKernel? + +
    +

    Starting with version 1.5, DotKernel has a Console bootstrap to easily run PHP scripts from the command line, most commonly used to run cron jobs without using wget or going through Apache.

    +
    +
    +
    + Where is the Console bootstrap located? + +
    +

    The bootstrap is found in Console/index.php, and it has one controller in Controller.php with two example actions.

    +
    +
    +
    + What command line arguments does the Console bootstrap support? + +
    +

    There are two arguments: -a, the name of the action to execute, and -e, the environment as defined in application.ini (development, staging, or production), which is optional and defaults to production.

    +
    +
    +
    + What happens to extra arguments passed to the script? + +
    +

    Any other arguments set when calling the script will be available in the controller in the $registry->arguments array.

    +
    +
    +
    + What example actions are bundled with the Console controller? + +
    +

    The bundled controller has two example actions: "count-users", which demonstrates how to interact with models, and "send-newsletter", which reads the newsletter from the command line.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.html.twig index 676fd4d6..91a8f41f 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.html.twig @@ -45,6 +45,23 @@ from a previous version. So its a good moment to check the old or current projects, and proceed. DotKernel framework is using its own WURFL adapter, so this will not affect projects built with it . + +

    Frequently Asked Questions

    + +
    +
    + Why did Zend Framework drop the WURFL adapter? + +
    +

    According to Matthew Weier O'Phinney, Zend Framework Project Leader, the WURFL adapter was dropped from Zend_Http_UserAgent in the upcoming 1.12.0 branch due to a change in licensing by the WURFL project: the free version became AGPL, which raised too many concerns for end users regarding how their products must be licensed to comply.

    +
    +
    +
    + Does this change affect projects built with DotKernel? + +
    +

    No. DotKernel uses its own WURFL adapter, so the removal of the WURFL adapter from Zend_Http_UserAgent in ZF 1.12.0 will not affect projects built with DotKernel.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-registry-usage-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-registry-usage-in-dotkernel.html.twig index cd4f0a29..c90b7a58 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-registry-usage-in-dotkernel.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/zend-registry-usage-in-dotkernel.html.twig @@ -53,6 +53,35 @@ echo $registry->requestAction; Or if you only need one variable from the registry, you can get it directly using:
    >$action = Zend_Registry::get('requestAction');
    You can find more information about Zend_Registry, in the Zend Framework Documentation. + +

    Frequently Asked Questions

    + +
    +
    + What variables does Zend_Registry contain in DotKernel? + +
    +

    It contains startTime (the result of microtime() at the beginning of the request), configuration (loaded from configs/application.ini), router (loaded from configs/router.xml), database (the database adapter), settings (loaded from the database), requestModule/requestController/requestAction, request (additional request variables), seo (loaded from configs/dots/seo.xml), option (loaded from configs/dots/<moduleName>.xml), and session (the session object).

    +
    +
    +
    + How do you get an instance of the registry? + +
    +

    Use $registry = Zend_Registry::getInstance(); and then access variables such as $registry->startTime or $registry->requestAction.

    +
    +
    +
    + How do you retrieve just one variable from the registry? + +
    +

    If you only need one variable, you can get it directly using $action = Zend_Registry::get('requestAction');.

    +
    +
    +
    + What does the "seo" entry in the registry contain? + +
    +

    It contains seo information loaded from configs/dots/seo.xml, such as site name, default description, and keywords.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.html.twig index 624b6ceb..5a4b9b7e 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.html.twig @@ -54,6 +54,35 @@ if you encounter the same issue in IE8 and IE9 , then with all regret, you need     + +

    Frequently Asked Questions

    + +
    +
    + What session bug was found in DotKernel 1.5.0? + +
    +

    In unknown circumstances, and only in IE8 and IE9, the session cookie was not saved on the client machine, forcing the user to log in over and over again. The only workaround found was to open a new tab with the same page.

    +
    +
    +
    + What caused the session cookie to be generated multiple times? + +
    +

    Investigation of the Dot_Session class showed the session cookie was generated 3 times, because the code called both regenerateID() and rememberMe() methods of Zend_Session. According to the Zend Framework documentation, this is unnecessary: if you call rememberMe(), you should not also call regenerateId(), since rememberMe() already calls it internally.

    +
    +
    +
    + What was the fix, and where was it released? + +
    +

    The regenerateID() call was removed, and two new application.ini settings were added: use_only_cookies, which must be ON at all times to avoid session fixation, and remember_me_seconds. These fixes were included in DotKernel version 1.5.1.

    +
    +
    +
    + What should I do if I still see this issue in IE8/IE9? + +
    +

    If you encounter the same issue in IE8 and IE9, the tip given is to deactivate the rememberMe() and regenerateId() method calls.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.html.twig index da762b74..eb678301 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.html.twig @@ -37,6 +37,23 @@ This file follow DotKernel’s Coding standard. Unzip the file. Go to Zend Studio -> Preferences -> PHP -> Code Style -> Formatter Import the xml file. + +

    Frequently Asked Questions

    + +
    +
    + What is this file for? + +
    +

    It is an XML file, downloadable from the linked page, that can be imported into Zend Studio (version 9.x) to set up the PHP code formatter so it follows DotKernel's coding standard.

    +
    +
    +
    + How do I import the formatter file into Zend Studio? + +
    +

    Unzip the downloaded file, then in Zend Studio go to Preferences -> PHP -> Code Style -> Formatter and import the XML file.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/development-report-december-11-2017.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/development-report-december-11-2017.html.twig index 8e0b8799..3907980a 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/development-report-december-11-2017.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/development-report-december-11-2017.html.twig @@ -51,6 +51,23 @@ The JapSeyz + +

    Frequently Asked Questions

    + +
    +
    + What documentation updates does this report cover? + +
    +

    Release notes were added for DotKernel3 frontend and admin, a Webpack tutorial was added to the Prerequisites section, and the Api Endpoint Documentation Guidelines were updated with a slightly different layout and some corrections.

    +
    +
    +
    + Who was credited as a contributor in this report? + +
    +

    JapSeyz was thanked as a contributor.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig index 03bc4a33..2ad0b316 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig @@ -222,6 +222,47 @@

    Note: You`ll find in the #tableToolbar buttons that toggle a modal for adding/editing/deleting admins.

    + +

    Frequently Asked Questions

    + +
    +
    + When was Dotkernel Admin V4 released, and what is it built on? + +
    +

    Dotkernel Admin V4 was officially released on 19 July 2022. It's DotKernel's PSR-7 Admin, an application based on Mezzio, mainly for managing and displaying tabular data from one or more database components.

    +
    +
    +
    + What are the demo credentials for trying out Dotkernel Admin V4? + +
    +

    Username admin and password dotadmin, on the demo at https://admin4.dotkernel.net/.

    +
    +
    +
    + What PHP version does Dotkernel Admin V4 support? + +
    +

    Full support for PHP 8.1, with a minimum requirement of PHP 7.4.

    +
    +
    +
    + How do you register a new module or middleware? + +
    +

    Register a module by adding its ConfigProvider.php in config.php. New middleware goes into pipeline.php, where you can also edit the order in which middleware runs.

    +
    +
    +
    + What packages provide the authorization guards? + +
    +

    dot-rbac-guard and dot-rbac work together to restrict access based on user role. authorization.global.php defines roles and their permissions, while authorization-guards.global.php restricts specific actions to those permissions.

    +
    +
    +
    + What frontend toolkit does Dotkernel Admin V4 use for displaying tables? + +
    +

    Bootstrap 4.5.0 combined with Fontawesome 5.0.6 for the overall design, and Bootstrap Table specifically for listing raw data because it's easy to implement and configurable in many ways.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig index 880a49fd..f5ca8c4f 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig @@ -67,6 +67,29 @@

     

    + +

    Frequently Asked Questions

    + +
    +
    + What is Dotkernel Admin? + +
    +

    A basic admin panel based on Bootstrap ^4.5.0, using Doctrine to perform basic CRUD operations over a database, built as a collection of PSR-7 Middleware applications on top of Mezzio and Laminas components.

    +
    +
    +
    + Where can you see a live demo of version 3? + +
    +

    At admin.dotkernel.net.

    +
    +
    +
    + What are the core requirements listed in the version 3 changelog? + +
    +

    Mezzio ^3.2, PHP ^7.4, Doctrine 2.7.x, and Twig 3.x, plus Bootstrap 4.5, with dot-* packages limited to version 3.x and above.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig index 29d3b1b6..39117c2b 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig @@ -376,6 +376,47 @@

    One of the best ways to ensure the quality of your product is to create and run functional and unit tests. You can find factory-made tests in the tests/AppTest/ folder, and you can also register your own.

    + +

    Frequently Asked Questions

    + +
    +
    + What is Dotkernel API based on? + +
    +

    It's based on Enrico Zimuel's Zend Expressive API - Skeleton example, and runs on the Mezzio microframework and Laminas components, implementing PSR-3, PSR-4, PSR-7, PSR-11 and PSR-15.

    +
    +
    +
    + What ORM does Dotkernel API use for persistence, and why? + +
    +

    Doctrine ORM (object-relational mapper), chosen because it lets the programmer focus on the object-oriented business logic and treat persistence only as a secondary priority.

    +
    +
    +
    + How is Dotkernel API documented? + +
    +

    The documentation is Postman-based, using two files that store information about every available endpoint ready to be tested: documentation/DotKernel_API.postman_collection.json and documentation/DotKernel_API.postman_environment.json.

    +
    +
    +
    + How does CORS handling work in Dotkernel API? + +
    +

    MezzioCorsMiddlewareCorsMiddleware recognizes the CORS preflight request and detects the proper CORS configuration. The Router is used to detect every allowed request method by executing a route match with all possible request methods, so there's at least one Router request for every preflight request.

    +
    +
    +
    + How do you register a new module or add new middleware? + +
    +

    Registering a new module is done by including its ConfigProvider.php in config.php. Brand new middleware should go into pipeline.php, where you can also edit the order in which middleware runs.

    +
    +
    +
    + What does the File Locker System do? + +
    +

    It's enabled by default and creates a command-{command-default-name}.lock file, which prevents another instance of the same command from running until the previous one has finished.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig index 384dce08..7dceb5b3 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig @@ -51,6 +51,29 @@

     

     

    + +

    Frequently Asked Questions

    + +
    +
    + What is Dotkernel Frontend built on? + +
    +

    It's part of the Dotkernel collection of PSR-7 Middleware applications, built on top of the Mezzio microframework using Laminas components.

    +
    +
    +
    + Where can you see a live demo of version 3? + +
    +

    At v3.dotkernel.net.

    +
    +
    +
    + What are the changelog requirements for version 3? + +
    +

    Requires Mezzio ^3.2, PHP ^7.4, Doctrine 2.7.x, and Twig 3.x, with dot-* packages limited to version 3.x and above.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig index 0de2fd8c..b17c5f33 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig @@ -101,6 +101,41 @@ If your project is a DotKernel 3 instance (based on expressive 2), you can migra
  • http-interop/http-middleware
  • webimpress/http-middleware-compatibility
  • + +

    Frequently Asked Questions

    + +
    +
    + What is the headline change in this stable release? + +
    +

    DotKernel was updated to support Zend Expressive 3 alongside PSR-15 middleware, with the core packages updated to support PSR-15 Middleware, making both frontend and admin easier to migrate.

    +
    +
    +
    + What are the new component versions in this release? + +
    +

    frontend 1.0.0 and admin 1.0.1.

    +
    +
    +
    + What PHP version does this release require? + +
    +

    PHP >= 7.1.

    +
    +
    +
    + Do you need to migrate your middleware code for this release? + +
    +

    If your middleware code is based on the dotkernel/dot-controller package, no migration is needed. If it's based on http-interop/http-middleware, you must migrate it to implement the interfaces in psr/http-server-middleware.

    +
    +
    +
    + What packages were removed in this release? + +
    +

    http-interop/http-middleware and webimpress/http-middleware-compatibility.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-admin.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-admin.html.twig index dc6d6fd3..730fb028 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-admin.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-admin.html.twig @@ -152,6 +152,41 @@

    The DotKernel team is focused on the cache that has been removed in the current version because doctrine/cache deleted their cache implementation classes. We are also updating our existing projects to PHP 8.3 and testing the changes on live environments to iron out any other issues.

    + +

    Frequently Asked Questions

    + +
    +
    + Which Dotkernel Admin release added PHP 8.3 support? + +
    +

    Release 4.3.1.

    +
    +
    +
    + Which PHP versions does this release support? + +
    +

    Only PHP 8.2 and PHP 8.3 - support for PHP 8.1 was removed, including from the workflows.

    +
    +
    +
    + Did the logAdminVisit function change? + +
    +

    Yes. src/Admin/src/Service/AdminService.php's logAdminVisit function was updated and no longer returns AddressNotFoundException.

    +
    +
    +
    + Why were cache-related parameters removed from the configuration files? + +
    +

    The PhpFileCache class and its related parameters were removed from local.php.dist (local.php) and doctrine.global.php because doctrine/cache deleted their cache implementation classes.

    +
    +
    +
    + Are there side effects from updating the dot-twigrenderer dependencies? + +
    +

    Yes. doctrine/doctrine-module was removed because of a conflict, which means other packages that depended on it - such as doctrine/doctrine-laminas-hydrator, laminas-cache-storage-adapter-filesystem, laminas-cache-storage-adapter-memory, laminas-mvc and laminas-paginator - may no longer get installed, so you should check whether your project actually relies on them.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-api.html.twig index f4f22ed1..9da3dde0 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-api.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-api.html.twig @@ -140,6 +140,41 @@ + +

    Frequently Asked Questions

    + +
    +
    + What PHP versions does Dotkernel API support after this update? + +
    +

    Dotkernel API, now at v4.2.1, supports only PHP 8.2 and PHP 8.3. Support for PHP 8.1 was removed, including from the project's workflows.

    +
    +
    +
    + What changed with caching in Dotkernel API? + +
    +

    The PhpFileCache class and its related parameters were removed from config/autoload/doctrine.global.php, which previously required it for cache configuration. The dot-cache package was added instead, with corresponding updates to composer.json, config/autoload/doctrine.global.php, and config/config.php.

    +
    +
    +
    + Do I need to update my Doctrine queries after this update? + +
    +

    Yes. Wherever useQueryCache() is used in your queries, it should be replaced with setCacheable().

    +
    +
    +
    + What should I do when upgrading an older Dotkernel API installation to this version? + +
    +

    When upgrading from a version of Dotkernel API older than the PHP 8.3 support, you may need to run composer update and/or install Sodium, for example by running sudo dnf install php-sodium.x86_64.

    +
    +
    +
    + Where can I find the full list of changes for this update? + +
    +

    The full list of file changes is available in the linked pull request on GitHub for the dotkernel/api repository, and the steps taken mirror those used for the PHP 8.3 update of the Dotkernel Frontend.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-frontend.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-frontend.html.twig index f1e65d19..90668659 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-frontend.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-frontend.html.twig @@ -136,6 +136,41 @@

    Next on the menu is to implement these changes in the DotKernel Frontend live projects and process the feedback.

    + +

    Frequently Asked Questions

    + +
    +
    + What update adds PHP 8.3 support to Dotkernel Frontend? + +
    +

    The DotKernel team updated the Frontend application to version 4.2.0 so it can take advantage of PHP 8.3 support in the newest packages.

    +
    +
    +
    + Which PHP versions does Dotkernel Frontend 4.2.0 support? + +
    +

    Version 4.2.0 supports only PHP 8.2 and PHP 8.3. Support for PHP 8.1 was removed, similar to the earlier Admin update.

    +
    +
    +
    + What changed with caching in this update? + +
    +

    The no-longer-available PhpFileCache class is no longer supported until a replacement is implemented. The related parameters were removed from configuration files, including removing the use Doctrine\Common\Cache\PhpFileCache; statement and the configuration and cache parameters in doctrine.global.php.

    +
    +
    +
    + What npm and Node.js versions are recommended for this update? + +
    +

    Alongside updated npm dependencies in packagist.json, it is recommended to use npm v10.0.4 and Node.js v20.11.0.

    +
    +
    +
    + Where can I find the full list of file changes for this update? + +
    +

    The full list of file changes is linked in the article, pointing to the corresponding pull request on the Dotkernel Frontend GitHub repository.

    +
    +
    +
    {{ include('@partial/post-nav.html.twig') }} diff --git a/src/Blog/templates/page/blog-resource/headless-platform/complementary-admin-in-dotkernel-headless-platform.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/complementary-admin-in-dotkernel-headless-platform.html.twig index c05881f6..74959deb 100644 --- a/src/Blog/templates/page/blog-resource/headless-platform/complementary-admin-in-dotkernel-headless-platform.html.twig +++ b/src/Blog/templates/page/blog-resource/headless-platform/complementary-admin-in-dotkernel-headless-platform.html.twig @@ -95,6 +95,41 @@
  • They guarantee a future-proof system using the best practices recommended by the PHP community.
  • +

    Frequently Asked Questions

    + +
    +
    + What is Dotkernel Admin? + +
    +

    Dotkernel Admin is a fully-functional, independent application that lives in its own separate repository. It's built on the same Mezzio + Laminas foundation as Dotkernel API and includes a built-in UI with authentication, user management and a web interface.

    +
    +
    +
    + How do I install Dotkernel Admin? + +
    +

    Install it via Git into the folder of your choice with git clone https://github.com/dotkernel/admin.git ., then follow the step-by-step installation guide to configure the database credentials and run the migrations and fixtures that create and populate the database. Be sure to change the default credentials before going to production.

    +
    +
    +
    + What features does Dotkernel Admin include out of the box? + +
    +

    It ships with Role-Based Access Control powered by dot-rbac and dot-rbac-guard to manage permissions, routes and handler-level security; OpenAPI annotations in each module for documented and testable admin endpoints; and frontend tooling built on Twig and NodeJS/NPM, including build and watch scripts for JavaScript/CSS bundling.

    +
    +
    +
    + What modules make up Dotkernel Admin's architecture? + +
    +

    The file architecture is modular, featuring an Admin module that manages administrator users, an App module that houses core functions like authentication and error reporting, and a Setting module that stores and manages display or application settings for various report pages.

    +
    +
    +
    + How do Dotkernel API and Admin work together? + +
    +

    The REST API serves JSON endpoints for clients, services and mobile apps, while the Admin UI is a web-based interface that communicates with the database via its own endpoints to manage data, users and settings. Both share a unified tech stack - Mezzio, Laminas components, Doctrine ORM and PHP FIG's PSR standards - plus shared modules like authentication and dot-rbac, which keeps backend and admin tooling consistent and lets fewer developers manage both codebases.

    +
    +
    +
    +

    Additional Resources