Live News for Yii Framework News, fresh extensions and wiki articles about Yii framework. Sat, 22 Mar 2025 08:35:12 +0000 Zend_Feed_Writer 2 (http://framework.zend.com) https://www.yiiframework.com/ [extension] neoacevedo/yii2-material Thu, 20 Mar 2025 18:22:34 +0000 https://www.yiiframework.com/extension/neoacevedo/yii2-material https://www.yiiframework.com/extension/neoacevedo/yii2-material NestorAcevedo NestorAcevedo

Yii2 Material 3

Esta es una extensión primaria para [Yii framework 2.0](https://www.yiiframework.com). Encapsula componentes de [Material Design](https://m3.material.io/) en términos de Widgets Yii. **NOTA**: Material Web 3 no tiene los componentes `Card`, `Snackbar`, `TopAppBar` ni `NavigationRail`, así que se han creado desde 0 para intentar seguir los lineamientos del diseño de Material 3. Instalación ------------ La forma preferida de instalar esta extensión es a través de [composer](http://getcomposer.org/download/). Luego ejecute ``` php composer.phar require --prefer-dist neoacevedo/yii2-material3 ``` o agregue ```js "neoacevedo/yii2-material3": "*" ``` a la sección require de su archivo `composer.json`. Uso ---- ### Card e Icon Buttons ```php <?php ... $css = <<registerCss($css); ?> <?php \neoacevedo\yii2\material\widgets\Card::begin([ 'options' => [ 'class' => 'card', 'type' => Card::MD_CARD_TYPE_FILLED ], 'actions' => [ 'icons' => [ Html::iconButton(['icon' => 'dictionary']), Html::iconButton(['icon' => 'bookmark']) ] ] ]); ?>
Agregue su código espagueti
<?php neoacevedo\yii2\material\widgets\Card::end(); ?> ``` ### Material3ActiveForm y Material3ActiveField ```php <?php <?php /** * @var Material3ActiveForm */ $form = Material3ActiveForm::begin([ 'id' => 'form' ]); ?> // Outlined (default) input echo $form->field($model, 'username', [ 'options' => ['class' => 'mb-3'] ])->textInput(options: ['onkeyup' => new JsExpression(expression: "if(event.key == 'Enter') { form.submit(); }")]); // Filled input echo $form->field($model, 'password', [ 'options' => ['class' => 'mb-3'] ])->passwordInput(options: ['variant' => 'filled', 'onkeyup' => new JsExpression(expression: "if(event.key == 'Enter') { form.submit(); }")]); echo $form->field($model, 'remember_me')->checkbox(); echo Html::submitButton('Iniciar sesión', ['variant' => 'filled']); <?php Material3ActiveForm::end(); ?> ?> ``` ### Dialog ```php <?php Dialog::begin([ 'options' => [ 'open' => true, 'no-focus-trap' => "true", 'type' => 'alert', 'quick' => true ], 'headerOptions' => [ 'showCloseButton' => true ], 'title' => 'Dialog', 'buttons' => [ Button::widget([ 'label' => 'Cancelar', 'options' => [ 'type' => Button::TYPE_TEXT, 'form' => 'form', 'value' => 'cancel', ] ]), Button::widget([ 'label' => 'Aceptar', 'options' => [ 'type' => Button::TYPE_TEXT, 'form' => 'form', 'value' => 'ok', ] ]) ] ]); ?> A simple dialog with free-form content. <?php Dialog::end(); ?> ``` ### DropdownList y List ```php <?php // Usando directamente el objeto. echo DropdownList::widget([ 'items' => [ '' => '', 'apple' => 'Apple', 'apricot' => 'Apricot' ], 'options' => [ 'class' => 'select', 'options' => [ '' => [ 'aria-label' => 'blank' ], 'apple' => ['selected' => true] ] ] ]); echo Lists::widget([ 'items' => [ 'Fruits', '', [ 'headline' => 'Apple', 'options' => [ 'type' => Lists::ITEM_TYPE_BUTTON ] ] ], ]); // Usando la clase auxiliar Html. echo \neoacevedo\yii2\material\Html::list([ 'Fruits', [ 'label' => '', 'options' => [ 'type' => 'divider' ] ], [ 'headline' => 'Apple', 'options' => [ 'type' => 'button' ] ] ]); echo \neoacevedo\yii2\material\Html::dropDownList('name', null, [ '' => '', 'apple' => 'Apple', 'apricot' => 'Apricot' ], [ 'class' => 'select', 'options' => [ '' => [ 'aria-label' => 'blank' ], 'apple' => ['selected' => true] ] ]); ?> ``` Estos componentes web de Material también pueden ser usados de manera directa en el html: ```html ... ... ... ``` ]]>
0
[news] Yii View 12.1 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/705/yii-view-12-1 https://www.yiiframework.com/news/705/yii-view-12-1 vjik vjik

Version 12.1 of Yii View package has been released. Here are the list of improvements and fixes included in the new version:

  • changed PHP constraint in composer.json to 8.1 - 8.4;
  • allowed using ../ in the name of the view to refer to parent directory of the directory containing the view currently being rendered;
  • explicitly marked nullable parameters;
  • fixed exception message when relative path is used without currently rendered view.
]]>
0
[news] Yii Hydrator 1.6 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/704/yii-hydrator-1-6 https://www.yiiframework.com/news/704/yii-hydrator-1-6 vjik vjik

Version 1.6 of Yii Hydrator package has been released. Here are the list of improvements included in the new version:

  • added nested mapping support via new ObjectMap class;
  • changed PHP constraint in composer.json to 8.1 - 8.4;
  • improved psalm annotation in HydratorInterface::create() method.
]]>
0
[news] Yii Assets 5.1 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/703/yii-assets-5-1 https://www.yiiframework.com/news/703/yii-assets-5-1 vjik vjik

Version 5.1 of Yii Assets package was released. There a few changes:

  • improve static analyze annotations;
  • Change PHP constraint in composer.json to ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0;
  • remove unnecessary array_filter call in AssetUtil::extractFilePathsForExport() method;
  • fix the nullable parameter declarations for compatibility with PHP 8.4.
]]>
0
[news] Yii Session 3.0 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/702/yii-session-3-0 https://www.yiiframework.com/news/702/yii-session-3-0 vjik vjik

Version 3.0 of Yii Session package was released. There a few changes and fixes:

  • remove sid_length and sid_bits_per_character options;
  • mark SessionException as final;
  • change PHP constraint in composer.json to 8.0 - 8.4;
  • explicitly mark nullable parameters.
]]>
0
[news] Yii Definitions 3.4 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/701/yii-definitions-3-4 https://www.yiiframework.com/news/701/yii-definitions-3-4 vjik vjik

Minor version of Yii Definitions package was released.

  • Change PHP constraint in composer.json to ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0
  • Bump minimal required PHP version to 8.1
  • Improve definition validation for readonly properties and properties with asymmetric visibility
  • Minor performance optimization: use FQN for PHP functions, remove unnecessary conditions
  • Mark readonly properties
  • Explicitly mark nullable parameters
]]>
0
[news] Yii Security 1.1 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/700/yii-security-1-1 https://www.yiiframework.com/news/700/yii-security-1-1 vjik vjik

Version 1.1 of Yii Security package was released. There are some improvements and changes:

  • Bump minimal required PHP version to 8.1
  • Change PHP constraint in composer.json to 8.1 - 8.4
  • Use SensitiveParameter attribute to mark sensitive parameters
  • Mark readonly properties
  • Explicitly mark nullable parameters
]]>
0
[news] Yii Router 4.0 and FastRoute Adapter 4.0 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/699/yii-router-4-0-and-fastroute-adapter-4-0 https://www.yiiframework.com/news/699/yii-router-4-0-and-fastroute-adapter-4-0 vjik vjik

Major versions of Yii Router and Yii Routere FastRoute Adapter adapter package were released.

Yii Router

  • Change UrlGeneratorInterface contract: on URL generation all unused arguments must be moved to query parameters, if query parameter with such name doesn't exist
  • Add debug collector for yiisoft/yii-debug
  • Add $hash parameter to UrlGeneratorInterface methods: generate(), generateAbsolute() and generateFromCurrent()
  • Replace two RouteCollectorInterface methods addRoute() and addGroup() to single addRoute()
  • Make Route, Group and MatchingResult dispatcher-independent
  • Bump minimum PHP version to 8.1
  • Change PHP constraint in composer.json to ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0
  • Add URL arguments' psalm type in UrlGeneratorInterface
  • Mark readonly properties
  • Explicitly mark nullable parameters

FastRoute Adapter

  • Bump minimal PHP version to 8.1 and minor refactoring
  • Change PHP constraint in composer.json to 8.1 - 8.4
  • Adapt to yiisoft/router version ^4.0
  • Add UrlGenerator host and scheme properties to package config params
  • Use FQN for built-in PHP functions
]]>
0
[news] Yii User 2.3 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/698/yii-user-2-3 https://www.yiiframework.com/news/698/yii-user-2-3 vjik vjik

Version 2.3 of Yii User package was released. There are some improvements and changes:

  • Add ApiAuth authentication method
  • Rename UserAuth to WebAuth, mark UserAuth as deprecated
  • Change PHP constraint in composer.json to ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0
  • Improve debug message "Unable to authenticate user…" in LogginMiddleware
  • Explicitly mark nullable parameters
]]>
0
[news] Yii HTTP Runner 3.1 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/697/yii-http-runner-3-1 https://www.yiiframework.com/news/697/yii-http-runner-3-1 vjik vjik

Minor version of Yii HTTP Runner were tagged. In this version made several changes.

  • Add $temporaryErrorHandler parameter to HttpApplicationRunner constructor. Mark parameter $logger and method withTemporaryErrorHandler() as deprecated.
  • Change PHP constraint in composer.json to ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0.
  • Raise the minimum PHP version to 8.1 and minor refactoring.
  • Explicitly mark nullable parameters.
]]>
0
[news] Yii Validator 2.2 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/696/yii-validator-2-2 https://www.yiiframework.com/news/696/yii-validator-2-2 vjik vjik

Version 2.2 of Yii Validator package has been released. Here is the list of changes included in the new version:

  • Add stopOnError parameter to Each rule
  • Change PHP constraint in composer.json to ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0
  • Bump yiisoft/strings version to ^2.6
  • Minor refactoring with PHP 8.1 features usage
  • Fix hook processing for nested objects that implement PostValidationHookInterface when Nested rule with specified rules is used
  • Fix russian translation of error message for FilledAtLeast rule
  • Fix bug in Email rule in edge case when enabled IDN, check DNS and used custom pattern
  • Fix URL handler for PHP 8.4 when value is empty string
]]>
0
[news] Yii2 extension releases Thu, 13 Feb 2025 21:44:01 +0000 https://www.yiiframework.com/news/695/yii2-extension-releases https://www.yiiframework.com/news/695/yii2-extension-releases samdark samdark

Yii2 extensions with pending releases were tagged:

Follow the links above to check individual changelogs. Big thanks to Yii community for pull requests.

]]>
0
[news] Yii 2.0.52 Thu, 13 Feb 2025 20:36:28 +0000 https://www.yiiframework.com/news/694/yii-2-0-52 https://www.yiiframework.com/news/694/yii-2-0-52 samdark samdark

We are pleased to announce the release of Yii Framework version 2.0.52.

Please refer to the instructions at https://www.yiiframework.com/download/ to install or upgrade to this version.

This release fixes PHP 8.4 compatibility, resolves a number of bugs, and adds some minor enhancements:

  • Support for variadic console controller action methods.
  • Support for attaching behaviors in configurations with Closure.
  • Ability to configure CSRF method and headers.
  • Ability to use wildcard when masking logged variables.
  • New yii\helpers\ArrayHelper::flatten().
  • BackedEnum support in AttributeTypecastBehavior.

Thanks to all Yii community members who contribute to the framework, translators who keep documentation translations up to date and community members who answer questions at forums.

There are many active Yii communities so if you need help or want to share your experience, feel free to join them.

A complete list of changes can be found in the CHANGELOG.

]]>
0
[news] Yii Strings 2.6 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/693/yii-strings-2-6 https://www.yiiframework.com/news/693/yii-strings-2-6 vjik vjik

Minor version of Yii Strings package was released.

  • Bump minimal required PHP version to 8.1 and minor refactoring
  • Change PHP constraint in composer.json to ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0
  • Explicitly mark nullable parameters
  • StringHelper::parsePath() for empty string returns path [''] instead of [] before
  • Check string on a valid UTF-8 in StringHelper methods: trim(), ltrim() and rtrim()
]]>
0
[news] Yii Error Handler 4.0 Wed, 05 Feb 2025 11:45:11 +0000 https://www.yiiframework.com/news/692/yii-error-handler-4-0 https://www.yiiframework.com/news/692/yii-error-handler-4-0 vjik vjik

Yii Error Handler package was updated with the following enhancements:

  • Add separate parameters for each of HtmlRenderer settings in constructor. Mark $settings parameter as deprecated
  • Change PHP constraint in composer.json to ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0
  • Add error code & show function arguments
  • Pass exception message instead of rendered exception to logger in ErrorHandler
  • Extract response generator from ErrorCatcher middleware into separate ThrowableResponseFactory class
  • Raise the minimum PHP version to 8.1 and minor refactoring
  • Explicitly mark nullable parameters
]]>
0
[news] Yii Config 1.6 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/691/yii-config-1-6 https://www.yiiframework.com/news/691/yii-config-1-6 vjik vjik

Minor version of Yii Config package was released. There are some new features and improvements:

  • Allow to use option "config-plugin-file" in packages
  • Add yii-config-info composer command
  • Raise minimum Composer version to 2.3
  • Change PHP constraint in composer.json to ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0
  • Refactoring: extract config settings reader to separate class
  • Minor refactoring of internal classes Options and ProcessHelper
  • Raise the minimum PHP version to 8.1 and minor refactoring
  • Explicitly mark nullable parameters
]]>
0
[news] Yii Arrays 3.2 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/690/yii-arrays-3-2 https://www.yiiframework.com/news/690/yii-arrays-3-2 vjik vjik

Minor version of Yii Arrays package is released.

  • Change PHP constraint in composer.json to ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0
  • Improve psalm types in ArrayHelper::getObjectVars(), ArrayableInterface, ArrayableTrait and ArrayAccessTrait
  • Raise the minimum PHP version to 8.1
  • Explicitly mark nullable parameters
  • ArrayHelper::getValue() returns default value on empty array key
  • ArrayHelper::keyExists() returns false on empty array key
]]>
0
[news] Yii Swagger 2.2 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/689/yii-swagger-2-2 https://www.yiiframework.com/news/689/yii-swagger-2-2 vjik vjik

Version 2.2 of Yii Swagger package has been released. Here is the list of changes included in the new version:

  • added support yiisoft/assets version of ^5.0;
  • raised the minimum PHP version to 8.1 and minor refactoring.
]]>
0
[news] Yii Console 2.3 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/688/yii-console-2-3 https://www.yiiframework.com/news/688/yii-console-2-3 vjik vjik

Version 2.3 of Yii Console package was tagged.

  • Added --open option for serve command
  • Printed possible options for serve command
  • Explicitly marked nullable parameters
]]>
0
[news] Yii Strings 2.5 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/687/yii-strings-2-5 https://www.yiiframework.com/news/687/yii-strings-2-5 vjik vjik

Minor version of Yii Strings package was released.

  • Added StringHelper::matchAnyRegex() method as a facade for CombinedRegexp.
  • Added more specific psalm type for result of StringHelper::base64UrlEncode() method.
]]>
0
[news] Yii File Router 1.0.0 Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/news/686/yii-file-router-1-0-0 https://www.yiiframework.com/news/686/yii-file-router-1-0-0 samdark samdark

First stable version of Yii File Router was released. The package provides a convention-based router middleware that chooses controller based on its namespace and class name similar to how it was in worked in Yii2. It could be used either standalone or as a fallback for explicit router.

src
  Controller
    User
      Profile
        IndexController.php
      BlogController.php
    UserController.php
    IndexController.php

Here's how it works:

  • GET /IndexController::index()
  • GET /userUserController::index()
  • POST /userUserController::create()
  • GET /user/blog/viewUser/BlogController::view()
  • GET /user/profileUser/Profile/IndexController::index()

As usual, the package has 100% unit test coverage, 100% MSI score and a good type coverage.

]]>
0
[extension] mgrechanik/yii2-activefield-additional-error Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/mgrechanik/yii2-activefield-additional-error https://www.yiiframework.com/extension/mgrechanik/yii2-activefield-additional-error Pathfinder Pathfinder

Additional span with error class for ActiveField

  1. What is it about?
  2. Installing
  3. How to use

What is it about?

Bootstrap 4 and 5 are expecting `html` like this to decorate validation error:

<input type="text" id="eventform-datetime" class="form-control is-invalid" name="EventForm[datetime]" aria-required="true">
<div class="invalid-feedback">Error message</div>

Element with `div.invalid-feedbackis supposed to be on the same level with yourinput.is-invalid`.

But sometimes when we are using any widgets or custom template we get html like this:

<div class="some-plugin-wrapper">
  <input type="text" id="eventform-datetime" class="form-control is-invalid" name="EventForm[datetime]" aria-required="true">
</div>  
<div class="invalid-feedback">Error message</div>

, so our error message is not shown.

Of cource you can make `div.invalid-feedback` visible by css for this page.

But if that does not suit you, this library propose another solution.

We are adding special `to a field template right before{error}part. And we **synchronize** thiswith the **input field** so it gets.is-invalid` class when input does

Installing

Installing through composer::

The preferred way to install this library is through composer.

Either run composer require --prefer-dist mgrechanik/yii2-activefield-additional-error

or add "mgrechanik/yii2-activefield-additional-error " : "~1.0.0" to the require section of your composer.json.

How to use

in your `viewfile, say it is_form.php`

use mgrechanik\additionalerror\AdditionalErrorBehavior;

<div class="event-form-form">

    <?php $form = ActiveForm::begin([
            'id' => 'event-create-form',
            // Adding behavior
            'as adderror' => [
                'class' => AdditionalErrorBehavior::class,
            ]
    ]); ?>

    <?= $form->field($model, 'datetime', [
            // Adding this hidden span before error block 
            'template' => "{label}\n{input}\n{hint}\n" . $form->getAdditionalErrorSpan($model, 'datetime') . "\n{error}"
    ])->hint('Some hint')
      ->widget(/* Some complicated widget creates a wrapper for the {input} part... */)

It will work for both server and client side.

]]>
0
[extension] mgrechanik/gridviewfilterfix Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/mgrechanik/gridviewfilterfix https://www.yiiframework.com/extension/mgrechanik/gridviewfilterfix Pathfinder Pathfinder

Fix for Yii2 GridView filter functionality to work properly with Bootstrap 4 and Bootstrap 5

  1. What is it about?
  2. Installing
  3. How to use
  4. Similar problems with Forms or GridView and Bootstrap 4 / 5

What is it about?

When you are using Yii2 default GridView you might meet a problem that validation errors for filter model are not displayed properly, like this:

Fix for Yii2 GridView DataColumn for filter validational errors to be properly shown with bootstrap 4 and 5

Installing

Installing through composer::

The preferred way to install this library is through composer.

Either run composer require --prefer-dist mgrechanik/gridviewfilterfix

or add "mgrechanik/gridviewfilterfix" : "~1.0.0" to the require section of your composer.json.

How to use

Add the following lines of code to your main configuration file: 1) For Bootstrap 4 `php

'container' => [
    'definitions' => [
        \yii\grid\GridView::class => [
            'dataColumnClass' => \mgrechanik\gridviewfilterfix\Bs4DataColumn::class
        ]
    ]
],


2) For Bootstrap 5
```php
    'container' => [
        'definitions' => [
            \yii\grid\GridView::class => [
                'dataColumnClass' => \mgrechanik\gridviewfilterfix\Bs5DataColumn::class
            ]
        ]
    ],

Similar problems with Forms or GridView and Bootstrap 4 / 5

Paginator does not look good

Solution:

    'container' => [
        'definitions' => [
            \yii\widgets\LinkPager::class => \yii\bootstrap5\LinkPager::class,
        ],
    ],
Error block under field is now shown, after failed validation, since this block is not at the same level with input.is-invalid

There is a library to solve this problem

]]>
0
[extension] sjaakp/yii2-donate Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/sjaakp/yii2-donate https://www.yiiframework.com/extension/sjaakp/yii2-donate sjaakp sjaakp

yii2-donate

  1. Basic functionality
  2. Prerequisites
  3. Installation
  4. The Donate widget
  5. Module options
  6. Internationalization
  7. Module ID
Donation widget for Yii2

Latest Stable Version Total Downloads License

Yii2-donate is a module for the Yii 2.0 PHP Framework to handle donations. It makes use of the payment service provider Mollie, which is mainly active in Western European countries.

Yii2-donate sports a widget, which can be placed on any page (or even all pages).

Basic functionality

If a visitor selects an amount and presses the 'Donate'-button, she is transfered to a Mollie payment page. If she successfully completes the payment, she is redirected to the site's donate/thanks page, where she is rewarded with a joyful shower of confetti. If the visitor did supply an email address, she receives a 'Thank you' mail. The 'thanks' page also sports a button to resume her visit to the site.

If the visitor cancels the payment, she is redirected to the site's donate/cancel page, from where she can resume her surfing.

At any time, the site's administrator can get an overview of granted donations on the donate page.

Prerequisites

You'll need a Mollie account. It's free, but depending on your country, you may need a valid registration as a (small) business. You'll get two API keys, one for testing purposes and one for the real work. One of the API keys is used tot initialize the module.

It is strongly advised that the app uses Pretty URLs.

Because Yii2-donate may send emails, the mailer component of the application has to be up and running. Be sure that the 'adminEmail' parameter of the application has a sensible value. If you prefer, you may set the 'supportEmail' parameter as well; if set, Yii2-donate will use this.

Installation

Install Yii2-donate in the usual way with Composer. Add the following to the require section of your composer.json file:

"sjaakp/yii2-donate": "*"

or run:

composer require sjaakp/yii2-donate

You can manually install yii2-comus by downloading the source in ZIP-format.

Module

Yii2-donate is a module in the Yii2 framework. It has to be configured in the main configuration file, usually called web.php or main.php in the config directory. Add the following to the configuration array:

<?php
// ...
'modules' => [
    'donate' => [
        'class' => sjaakp\donate\Module::class,
        // several options
    ],
],
// ...

The module has to be bootstrapped. Do this by adding the following to the application configuration array:

<php
// ...
'bootstrap' => [
    'donate',
]
// ...

There probably already is a bootstrap property in your configuration file; just add 'donate' to it.

Important: the module should also be set up in the same way in the console configuration (usually called console.php).

Console command

To complete the installation, a console command have to be run. This will create a database table for the donations:

yii migrate

The migration applied is called sjaakp\donate\migrations\m000000_000000_init.

The Donate widget

Placing the Donate widget in any view is trivial:

<?php
use sjaakp\donate\DonateWidget;
?>
...
<?= DonateWidget::widget() ?>
...

The small, collapsed variant is obtained by:

<?php
use sjaakp\donate\DonateWidget;
?>
...
<?= DonateWidget::widget([
    'small' => true
]) ?>
...

Module options

The Donate module has a range of options. They are set in the application configuration like so:

 <?php
 // ...
 'modules' => [
     'donate' => [
         'class' => sjaakp\donate\Module::class,
         'description' => 'Please, buy me a drink!',
         // ...
         // ... more options ...
     ],
 ],
 // ...

The options (most are optional) are:

  • mollieApiKey string One of the API keys obtained from Mollie. Not optional, must be set.
  • choices array Amounts to select from. Keys are integers representing the amounts in cents, values are textual representations. Example: [ ..., 250 => '€2,50', 500 => '€5', ... ]. Defaults: amounts of 5, 10, 25, 50, and 100.
  • header string|null Text header appearing in the donate-widget. If null (default) no header is rendered.
  • includeMessage bool Whether a 'friendly message' field is included in the widget. Default: true.
  • confetti bool Whether confetti is shown on the 'thanks' page. Default: true.
  • description string|null The textual description displayed on Mollie's payment page. If null (default), defaults to 'Donation for <site name>'.
  • locale string|null The locale sent to the payment site. If null, defaults to site's language property.
  • mailOptions array Options for the app mailer. Default: see source.
  • localTest bool|null If true, performs a dummy-payment on the local system, useful for debugging and testing. If null (default), localTest is set to true if YII_ENV === 'dev', in other words if the site is in the development environment.
  • indexAccess array The access rule for the donations overview (donate page). Default: only accessible to authenticated visitors ([ 'allow' => true, 'roles' => ['@'] ]). For most sites, you'll want to refine this.

Internationalization

All of Yii2-donate's utterances are translatable. The translations are in the 'sjaakp\donate\messages' directory.

You can override Yii2-donate's translations by setting the application's message source in the main configuration, like so:

 <?php
 // ...
 'components' => [
     // ... other components ...     
     'i18n' => [
          'translations' => [
               // ... other translations ...
              'donate' => [    // override donate's standard messages
                  'class' => yii\i18n\PhpMessageSource::class,
                  'basePath' => '@app/messages',  // this is a default
                  'sourceLanguage' => 'en-US',    // this as well
              ],
          ],
     ],
     // ... still more components ...
 ]

The translations should be in a file called 'donate.php'.

If you want a single or only a few messages translated and use Yii2-donate's translations for the main part, the trick is to set up 'i18n' like above and write your translation file something like:

  <?php
  // app/messages/nl/donate.php
  
  $donateMessages = Yii::getAlias('@sjaakp/donate/messages/nl/donate.php');
  
  return array_merge (require($donateMessages), [
     'Amount' => 'Bedrag in euro',   // your preferred translation
  ]);

At the moment, the only language implemented is Dutch. Agreed, it's only the world's 52th language, but it happens to be my native tongue. Please, feel invited to translate Yii2-donate in other languages. I'll be more than glad to include them into Yii2-donate's next release.

Module ID

By default, the Module ID is 'donate'. It is set in the module configuration. If necessary (for instance if there is a conflict with another module or application component), you may set the Module ID to something different. Important: in that case, the moduleId property of the Donate widget must be set to this new value as well.

FAQ

Can I change the layout for the Yii2-donate views?

  • Use the EVENT_BEFORE_ACTION event. One easy way is to incorporate it in the module setup, like so:

    <?php
    // ...
    'modules' => [
       'donate' => [
           'class' => sjaakp\donate\Module::class,
           'description' => 'Please, buy me a drink!',
           'on beforeAction' => function ($event) {
               $event->sender->layout = '@app/views/layouts/one_column';
           },
           // ... more options ...
       ],
    ],
    // ...
    
]]>
0
[extension] duna/qrcode Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/duna/qrcode https://www.yiiframework.com/extension/duna/qrcode davidsgallan davidsgallan

Duna QR Code Library

  1. Overview
  2. Installation
  3. Usage
  4. License
  5. Contributing
  6. Issues and Support

A versatile QR code generation library that supports HTML, PNG, and SVG output formats.

GitHub repo stars

Overview

The Duna QR Code Library is a QR code generation tool originally based on the QrCode library bundled with Duna until v8.0, developed by Laurent Minguet. It is distributed under the LGPL license, providing a flexible and open-source solution for QR code generation.

Installation

To install the library, use Composer:

$ composer require duna/qrcode

Usage

Here's a quick guide to using the Duna QR Code Library:

Generating QR Codes

First, include the necessary classes and create a QR code instance:

<?php

use Duna\Helpers\QrCode\QrCode;
use Duna\Helpers\QrCode\Output;

$qrCode = new QrCode('Lorem ipsum dolor sit amet');
Output Formats
PNG Output

To generate a PNG image of the QR code, specifying dimensions and colors, use:

// Create PNG output
$output = new Output\Png();

// Generate PNG data with a specified width, background color (white), and foreground color (black)
$data = $output->output($qrCode, 100, [255, 255, 255], [0, 0, 0]);

// Save the PNG data to a file
file_put_contents('file.png', $data);
SVG Output

For SVG output, which is useful for scalable vector graphics:

// Create SVG output
$output = new Output\Svg();

// Generate SVG data with a specified width, background color (white), and foreground color (black)
echo $output->output($qrCode, 100, 'white', 'black');
HTML Output

To display the QR code as an HTML table:

// Create HTML output
$output = new Output\Html();

// Generate HTML table representation of the QR code
echo $output->output($qrCode);

License

This library is provided under the GNU Lesser General Public License (LGPL) v3.0. See the LICENSE file for details.

Contributing

Contributions are welcome! Please refer to our CONTRIBUTING guidelines for more information.

Issues and Support

For issues and support, please refer to our issue tracker or reach out to the community.

]]>
0
[wiki] Use Single Login Session on All Your Yii2 Application/Repository Under Same Domain/Sub Domain Tue, 10 Sep 2024 12:26:07 +0000 https://www.yiiframework.com/wiki/2580/use-single-login-session-on-all-your-yii2-applicationrepository-under-same-domainsub-domain https://www.yiiframework.com/wiki/2580/use-single-login-session-on-all-your-yii2-applicationrepository-under-same-domainsub-domain aayushmhu aayushmhu

There are multiple blog that shows how to use seperate login for yii2 application but in this article i will show you how to use a single login screen for all your YII2 Advanced, YII2 Basic, Application, It will also work when your domain on diffrent server or the same server.

Here are few Steps you need to follow ot achive this.

1. For Advanced Templates

Step 1 : Add this into your component inside

/path/common/config/main.php

  'components' => [
        'user' => [
            'identityClass' => 'common\models\User',
            'enableAutoLogin' => true,
            'identityCookie' => ['name' => '_identity', 'httpOnly' => true],
        ],
        'request' => [
            'csrfParam' => '_csrf',
        ],
    ],

Step 2: Add Session and Request into main-local.php

/path/common/config/main-local.php

   'components' => [
        'session' => [
            'cookieParams' => [
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],
        'user' => [
            'identityCookie' => [
                'name' => '_identity',
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],
        'request' => [
            'csrfCookie' => [
                'name' => '_csrf',
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],
    ],

Note: example.com is the main domain. All other domain should be sub domain of this.

Step 3: Now Update the Same Validation Key for all the applications

/path/frontend/config/main-local.php

/path/backend/config/main-local.php

 'components' => [
        'request' => [
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'cookieValidationKey' => 'fFUeb5HDj2P-1a1FTIqya8qOE',
        ],
    ],

Note : Remove the Session and request keys from your main.php of Both frontend and backend application.

Step 4: Note Somethign that you also have and console application so update session, user,and request into the main-local.php of your console application

/path/console/config/main-local.php

 'components' => [
        'session' => null,
        'user' => null,
        'request' => null,
    ]

2. For Basic Templates

Additionaly If you have an basic templates installed for another project and you want to use same login for that templates. To Achive this follow the given steps

Step 1: Update You main-local.php of basic template

/path/basic-app/config/main-local.php


 'components' => [
        'session' => [
            'cookieParams' => [
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],
        'user' => [
            'identityCookie' => [
                'name' => '_identity',
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],
        'request' => [
            'csrfCookie' => [
                'name' => '_csrf',
                'path' => '/',
                'domain' => ".example.com",
            ],
        ],

    ],

I Hope you understand well how to use a single login for all of your domain and subdomain or repository.

:) Thanks for Reading

]]>
0
[extension] pingcrm-yii2-vue3 Wed, 17 Jul 2024 09:28:16 +0000 https://www.yiiframework.com/extension/pingcrm-yii2-vue3 https://www.yiiframework.com/extension/pingcrm-yii2-vue3 toatall toatall

Ping CRM on Yii 2

  1. Demo
  2. Installation
  3. Running tests
  4. Requirements
  5. Extending this project
  6. Credits

A Yii 2 demo application to illustrate how Inertia.js works.

With Inertia you are able to build single-page apps using classic server-side routing and controllers, without building an API.

This application is a port of the original Ping CRM written in Laravel and based on the Yii 2 Basic Project Template.

screenshot.png

Based on the application Ping CRM on Yii 2 github and yii extension.

Changes: Updated Vue to version 3, updated npm packages and composer. Converted Vue files to Composition API (script setup).

Demo

https://pingcrm-yii2.tebe.ch

Installation

Clone the repo locally:

git clone https://github.com/toatall/pingcrm-yii2-vue3 pingcrm-yii2-vue3
cd pingcrm-yii2-vue3

Install PHP dependencies:

composer install

Install NPM dependencies:

npm ci

Build assets:

npm run css-dev
npm run dev

Create an SQLite database. You can also use another database (MySQL, Postgres), simply update your configuration accordingly.

touch database/database.sqlite

Run database migrations:

php yii migrate

Run database seeder:

php yii db/seed

Run the dev server (the output will give the address):

php yii serve

You're ready to go! Visit Ping CRM in your browser, and login with:

Running tests

To run the Ping CRM tests, run:

(to be done)

Requirements

  • PHP >=7.4.0
  • Node.js & NPM
  • SQLite

Extending this project

The following steps are required when extending this project with new features.

In the backend
  • add new controller, that extends from inertia controller
  • add one ore more actions
  • return from the actions with a call to the inertia render method
<?php

namespace app\controllers;

use tebe\inertia\web\Controller;

class CustomController extends Controller
{
    public function actionIndex()
    {
        $params = [
            'data' => [],
        ];
        return $this->inertia('demo/index', $params);
    }
}

You can find more information at https://github.com/tbreuss/yii2-inertia.

In the frontend
  • add a new page under resources/js/Pages for each controller action you added in the backend
  • copy&paste one of the existing page examples
  • implement and/or extend Vue.js stuff as needed
  • use frontend tooling as described here and in package.json

You can find more information at https://inertiajs.com.

Credits

]]>
0
[extension] sjaakp/yii2-random-provider Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/sjaakp/yii2-random-provider https://www.yiiframework.com/extension/sjaakp/yii2-random-provider sjaakp sjaakp

yii2-random-provider

  1. Installation
  2. Using RandomProvider
ActiveDataProvider with random selection

Latest Stable Version Total Downloads License

RandomProvider is derived from ActiveDataProvider of the Yii 2.0 PHP Framework. It selects the records in a random fashion, which in some cases may be more attractive than the orderly way a regular ActiveDataProvider (usually) does it. RandomProvider is intended to co-operate with my LoadMorePager, but it will work with LinkPager or other pagers as well.

Notice that RandomProvider doesn't support CUBRID or dblib database drivers. Moreover, I only tested it with mysql. I'm pretty sure it'll work with other drivers, though. If you have any experiences to share, I'll appreciate that.

Notice also that RandomProvider makes use of an algorithm named 'Order By Rand()'. This is rather slow, and doesn't scale very well. Therefore, it is advised to use RandomProvider only with relatively small data sets (think of less than a few thousands of records). More information here.

A demonstration of RandomProvider is here.

Installation

Install yii2-random-provider in the usual way with Composer. Add the following to the require section of your composer.json file:

"sjaakp/yii2-random-provider": "*"

or run:

composer require sjaakp/yii2-random-provider

You can manually install yii2-random-provider by downloading the source in ZIP-format.

Using RandomProvider

RandomProvider is a drop-in replacement for Yii's ActiveDataProvider. Just use it like ActiveDataProvider.

]]>
0
[extension] yagas/yii2-debug4mongo Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/yagas/yii2-debug4mongo https://www.yiiframework.com/extension/yagas/yii2-debug4mongo yagas yagas

993323

Yii 2 Debug For MongoDB

  1. 目录结构
  2. 安装依赖
  3. 安装说明
  4. 配置说明

本项目为yii2-debug的扩展,使用MongoDB对debug数据进行存储。

目录结构

  src/                 代码目录
  src/models/          数据模型
  src/views/           视图文件
  src/controllers/     控制器

安装依赖

  • PHP支持>=5.4
  • yii2-mongodb
  • yii2-debug支持>=2.1.25(基于此版本构建而来)

安装说明

composer require yagas/yii2-debug4mongo

配置说明

if (YII_ENV_DEV) {
    $config['bootstrap'][] = 'debug';
    $config['modules']['debug'] = [
        'class' => 'yagas\debug\Module',
        'logTarget' => [
            'class' => 'yagas\debug\LogTarget',
            'app_no' => 'localhost_001', // 为当前站点设定标识
        ],
        'percent' => 10, // 百分之十的几率清除历史数据(GC)
    ];
}
]]>
0
[extension] diecoding/yii2-pdfjs Mon, 20 May 2024 17:11:05 +0000 https://www.yiiframework.com/extension/diecoding/yii2-pdfjs https://www.yiiframework.com/extension/diecoding/yii2-pdfjs die-coding die-coding

Yii2 PDF.js

  1. Table of Contents
  2. Instalation
  3. Dependencies
  4. Usage

Previewer PDF File with PDF.js for Yii2

Latest Stable Version Total Downloads Latest Stable Release Date Quality Score Build Status License PHP Version Require

Yii2 PDF.js uses PDF.js
Demo: https://mozilla.github.io/pdf.js/web/viewer.html

Table of Contents

Instalation

Package is available on Packagist, you can install it using Composer.

composer require diecoding/yii2-pdfjs '^1.0'

or add to the require section of your composer.json file.

'diecoding/yii2-pdfjs': '^1.0'

Dependencies

Usage

Setup Module
...
'modules'=>[
  'pdfjs' => [
       'class' => \diecoding\pdfjs\Module::class,
   ],
],
...

Views
Basic Usage
echo \diecoding\pdfjs\PdfJs::widget([
    'url' => '@web/uploads/dummy.pdf',
]);
Direct Url with Full Toolbar Section
echo Url::to(["/pdfjs", 'file' => Url::to('@web/uploads/dummy.pdf', true)], true);
Custom Attribute
echo \diecoding\pdfjs\PdfJs::widget([
    'url' => '@web/uploads/dummy.pdf',
    'options' => [
        'style' => [
            'width' => '100%',
            'height' => '500px',
        ],
    ],
]);
Disable Toolbar Section
echo \diecoding\pdfjs\PdfJs::widget([
    'url' => '@web/uploads/dummy.pdf',
    'sections' => [
        'toolbarContainer' => false,
    ],
]);
]]>
0
[extension] xiaosongshu/rabbitmq Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/xiaosongshu/rabbitmq https://www.yiiframework.com/extension/xiaosongshu/rabbitmq 2723659854 2723659854

rabbitmq queue 消息队列

项目地址:https://github.com/2723659854/rabbitmq

项目介绍

消息队列主要用于业务解耦,本项目采用rabbitmq,支持thinkPHP,laravel,webman,yii等常用框架,也可以单独使用。

安装方法 install
composer require xiaosongshu/rabbitmq
示例 demo
定义一个队列 queue
<?php
namespace app\commands;

require_once __DIR__.'/vendor/autoload.php';

class Demo extends \Xiaosongshu\Rabbitmq\Client
{

    /** 以下是rabbitmq配置 ,请填写您自己的配置 */
    /** @var string $host 服务器地址 */
    public static $host = "127.0.0.1";

    /** @var int $port 服务器端口 */
    public static $port = 5672;

    /** @var string $user 服务器登陆用户 */
    public static $user = "guest";

    /** @var string $pass 服务器登陆密码 */
    public static $pass = "guest";

    /**
     * 业务处理
     * @param array $params
     * @return int
     */
    public static function handle(array $params): int
    {
        //TODO 这里写你的业务逻辑
        // ...
        var_dump($params);
        return self::ACK;
        //return self::NACK;
    }
}

投递消息 publish
\app\commands\Demo::publish(['name'=>'tome','age'=>15]);

你可以在任何地方投递消息。

开启消费
\app\commands\Demo::consume();

你可以把消费者放到command命令行里面,使用命令行执行队列消费。举个例子(这里以yii为例子,你也可以换成laravel,webman,thinkPHP等其他框架): `php <?php

namespace app\commands;

use yii\console\Controller;

/**

  • @purpose 开启队列消费
  • @note 我只是一个例子 */ class QueueController extends Controller {

    /**

    • @api php yii queue/index
    • @return void
    • @throws \Exception
    • @comment 开启消费者 */ public function actionIndex() { Demo::consume(); } }
      开启消费者命令 consume
      ```bash
      php yii queue/index
      

      注:如果你需要开启多个消费者,那么可以在多个窗口执行开启消费者命令即可。当然你也可以使用多进程来处理。

      关闭消费者
\app\commands\Demo::close();
异常 Exception

队列使用过程中请使用 \RuntimeException和\Exception捕获异常

若需要使用延迟队列,那么rabbitmq服务需要安装延迟插件,否则会报错
测试

本项目根目录有一个demo.php的测试文件,可以复制到你的项目根目录,在命令行窗口直接在命令行执行以下命令即可。 `php php demo.php 测试文件代码如下:php <?php

namespace xiaosongshu\test; require_once DIR . '/vendor/autoload.php';

/**

  • demo
  • @purpose 定义一个队列演示 */ class Demo extends \Xiaosongshu\Rabbitmq\Client {

    /* 以下是rabbitmq配置 ,请填写您自己的配置 / /* @var string $host 服务器地址 / public static $host = "127.0.0.1";

    /* @var int $port 服务器端口 / public static $port = 5672;

    /* @var string $user 服务器登陆用户 / public static $user = "guest";

    /* @var string $pass 服务器登陆密码 / public static $pass = "guest";

    /**

    • 业务处理
    • @param array $params
    • @return int */ public static function handle(array $params): int { //TODO 这里写你的业务逻辑 // ... var_dump($params); /* 成功,返回ack / return self::ACK; /* 失败,返回NACK/ //return self::NACK; } }

/ 投递普通消息 */ \xiaosongshu\test\Demo::publish(['name' => 'tom']); \xiaosongshu\test\Demo::publish(['name' => 'jim']); \xiaosongshu\test\Demo::publish(['name' => 'jack']); /* 开启消费,本函数为阻塞,后面的代码不会执行 / \xiaosongshu\test\Demo::consume(); / 关闭消费者 */ \xiaosongshu\test\Demo::close(); `

联系作者:[email protected] ,你也可以直接提issues
]]>
0
[extension] xiaosongshu/yii2-rabbitmq Wed, 24 Apr 2024 09:33:36 +0000 https://www.yiiframework.com/extension/xiaosongshu/yii2-rabbitmq https://www.yiiframework.com/extension/xiaosongshu/yii2-rabbitmq 2723659854 2723659854

rabbitmq queue 延迟队列

安装方法 install

composer require xiaosongshu/yii2-rabbitmq
示例 demo
定义一个队列 queue
<?php
namespace app\commands;

require_once __DIR__.'/vendor/autoload.php';

class Demo extends \Xiaosongshu\Rabbitmq\Client
{

    /** 以下是rabbitmq配置 ,请填写您自己的配置 */
    /** @var string $host 服务器地址 */
    public static $host = "127.0.0.1";

    /** @var int $port 服务器端口 */
    public static $port = 5672;

    /** @var string $user 服务器登陆用户 */
    public static $user = "guest";

    /** @var string $pass 服务器登陆密码 */
    public static $pass = "guest";

    /**
     * 业务处理
     * @param array $params
     * @return int
     */
    public static function handle(array $params): int
    {
        //TODO 这里写你的业务逻辑
        // ...
        var_dump($params);
        return self::ACK;
        //return self::NACK;
    }
}

投递消息 publish
\app\commands\Demo::publish(['name'=>'tome','age'=>15]);

你可以在任何地方投递消息。

开启消费
\app\commands\Demo::consume();

你可以把消费者放到command命令行里面,使用命令行执行队列消费。举个例子: `php <?php

namespace app\commands;

use yii\console\Controller;

/**

  • @purpose 开启队列消费
  • @note 我只是一个例子 */ class QueueController extends Controller {

    /**

    • @api php yii queue/index
    • @return void
    • @throws \Exception
    • @comment 开启消费者 */ public function actionIndex() { Demo::consume(); } }
      开启消费者命令 consume
      ```bash
      php yii queue/index
      
      异常 Exception

队列使用过程中请使用 \RuntimeException和\Exception捕获异常

若需要使用延迟队列,那么rabbitmq服务需要安装延迟插件,否则会报错
联系作者:[email protected]
]]>
0
[extension] xiaosongshu/yii2-elasticsearch Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/xiaosongshu/yii2-elasticsearch https://www.yiiframework.com/extension/xiaosongshu/yii2-elasticsearch 2723659854 2723659854

elasticsearch-YII客户端 elasticsearch client for YII

安装 install
composer require xiaosongshu/yii2-elasticsearch
配置 Configuration

`php

'components' => [

 'ESClient' => [
        'class' => \Xiaosongshu\Elasticsearch\ESClient::class,
        'node'=>['192.168.101.170:9200'],
        'username' => '',
        'password' => '',
    ],

] `

基本用法 example
$res = Yii::$app->ESClient->search('index','_doc','title','测试')['hits']['hits'];
客户端支持的所有方法
创建索引:createIndex
创建表结构:createMappings
删除索引:deleteIndex
获取索引详情:getIndex
新增一行数据:create
批量写入数据:insert
根据id批量删除数据:deleteMultipleByIds
根据Id 删除一条记录:deleteById
获取表结构:getMap
根据id查询数据:find
根据某一个关键字搜索:search
使用原生方式查询es的数据:nativeQuerySearch
多个字段并列查询,多个字段同时满足需要查询的值:andSearch
or查询  多字段或者查询:orSearch
根据条件删除数据:deleteByQuery
根据权重查询:searchByRank
获取所有数据:all
添加脚本:addScript
获取脚本:getScript
使用脚本查询:searchByScript
使用脚本更新文档:updateByScript
索引是否存在:IndexExists
根据id更新数据:updateById
如果单独使用本插件,则需要实例化的时候传入elasticsearch的连接配置
elasticsearch客户端使用实例
<?php
require_once 'vendor/autoload.php';

/** 实例化客户端 */
$client = new \Xiaosongshu\Elasticsearch\ESClient([
    /** 节点列表 */
    'nodes' => ['192.168.4.128:9200'],
    /** 用户名 */
    'username' => '',
    /** 密码 */
    'password' => '',
]);
/** 删除索引 */
$client->deleteIndex('index');
/** 如果不存在index索引,则创建index索引 */
if (!$client->IndexExists('index')) {
    /** 创建索引 */
    $client->createIndex('index', '_doc');
}

/** 创建表 */
$result = $client->createMappings('index', '_doc', [
    'id' => ['type' => 'long',],
    'title' => ['type' => 'text', "fielddata" => true,],
    'content' => ['type' => 'text', 'fielddata' => true],
    'create_time' => ['type' => 'text'],
    'test_a' => ["type" => "rank_feature"],
    'test_b' => ["type" => "rank_feature", "positive_score_impact" => false],
    'test_c' => ["type" => "rank_feature"],
]);
/** 获取数据库所有数据 */
$result = $client->all('index','_doc',0,15);

/** 写入单条数据 */
$result = $client->create('index', '_doc', [
    'id' => rand(1,99999),
    'title' => '我只是一个测试呢',
    'content' => '123456789',
    'create_time' => date('Y-m-d H:i:s'),
    'test_a' => 1,
    'test_b' => 2,
    'test_c' => 3,
]);
/** 批量写入数据 */
$result = $client->insert('index','_doc',[
    [
        'id' => rand(1,99999),
        'title' => '我只是一个测试呢',
        'content' => '你说什么',
        'create_time' => date('Y-m-d H:i:s'),
        'test_a' => rand(1,10),
        'test_b' => rand(1,10),
        'test_c' => rand(1,10),
    ],
    [
        'id' => rand(1,99999),
        'title' => '我只是一个测试呢',
        'content' => '你说什么',
        'create_time' => date('Y-m-d H:i:s'),
        'test_a' => rand(1,10),
        'test_b' => rand(1,10),
        'test_c' => rand(1,10),
    ],
    [
        'id' => rand(1,99999),
        'title' => '我只是一个测试呢',
        'content' => '你说什么',
        'create_time' => date('Y-m-d H:i:s'),
        'test_a' => rand(1,10),
        'test_b' => rand(1,10),
        'test_c' => rand(1,10),
    ],
]);
/** 使用关键字搜索 */
$result = $client->search('index','_doc','title','测试')['hits']['hits'];

/** 使用id更新数据 */
$result1 = $client->updateById('index','_doc',$result[0]['_id'],['content'=>'今天你测试了吗']);
/** 使用id 删除数据 */
$result = $client->deleteById('index','_doc',$result[0]['_id']);
/** 使用条件删除 */
$client->deleteByQuery('index','_doc','title','测试');
/** 使用关键字搜索 */
$result = $client->search('index','_doc','title','测试')['hits']['hits'];
/** 使用条件更新 */
$result = $client->updateByQuery('index','_doc','title','测试',['content'=>'哇了个哇,这么大的种子,这么大的花']);
/** 添加脚本 */
$result = $client->addScript('update_content',"doc['content'].value+'_'+'谁不说按家乡好'");
/** 添加脚本 */
$result = $client->addScript('update_content2',"(doc['content'].value)+'_'+'abcdefg'");
/** 获取脚本内容 */
$result = $client->getScript('update_content');
/** 使用脚本搜索 */
$result = $client->searchByScript('index', '_doc', 'update_content', 'title', '测试');
/** 删除脚本*/
$result = $client->deleteScript('update_content2');
/** 使用id查询 */
$result = $client->find('index','_doc','7fitkYkBktWURd5Uqckg');
/** 原生查询 */
$result = $client->nativeQuerySearch('index',[
    'query'=>[
        'bool'=>[
            'must'=>[
                [
                    'match_phrase'=>[
                        'title'=>'测试'
                    ],
                ],
                [
                    'script'=>[
                        'script'=>"doc['content'].value.length()>2"
                    ]
                ]
            ]
        ]
    ]

]);
/** and并且查询 */
$result = $client->andSearch('index','_doc',['title','content'],'测试');
/** or或者查询 */
$result = $client->orSearch('index','_doc',['title','content'],'今天');

测试

将本扩展包的phpunit.xml文件复制到项目的根目录下面然后执行下面的命令 `bash php ./vendor/bin/phpunit -c phpunit.xml `

联系作者

[email protected]

]]>
0
[extension] jatin Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/jatin https://www.yiiframework.com/extension/jatin jatin_sharma jatin_sharma

$config['components']['mailer'] = [

'class' => 'jatin\resend\Mailer',
'useFileTransport' => false,
'viewPath' => '@app/mail',
'transport' => [
    'apiKey' => '<YOUR_API_KEY>'
],

];

]]>
0
[extension] asminog/yii2-proxy Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/asminog/yii2-proxy https://www.yiiframework.com/extension/asminog/yii2-proxy asminog asminog

HTTP Proxy Extension for Yii 2

  1. Installation
  2. Usage on domain.com
  3. Example request through proxy on domain.com

This is a simple proxy for Yii2 framework. This extension provides the HTTP proxy action for the Yii framework 2.0.

For license information check the LICENSE-file.

Build Status Build Status

GitHub repo file count GitHub code size in bytes

Installation

composer require asminog/yii2-proxy

Usage on domain.com

use asminog\proxy\ProxyAction;

class SiteController extends Controller
{
    public function actions()
    {
        return [
            'proxy' => [
                'class' => ProxyAction::class,
                // 'accessToken' => 'your-access-token', // - set access token for secure requests
                // 'throw404Exception' => true, // - show 404 error if access token is not valid or request url is not valid
                // 'proxyHeaders' => ['User-Agent', 'Content-Type'], // - set headers for proxy request
                'proxyHeaders' => ['Authorization', 'Content-Type'], // - set headers for chatgpt proxy request
                // 'proxyCookies' => ['cookie1', 'cookie2'], // - set cookies for proxy request
            ],
        ];
    }
}

Example request through proxy on domain.com


        $this->client = new Client([
            'transport' => CurlTransport::class,
            'baseUrl' => 'https://domain.com/site/proxy', // - set url to your proxy action
            'requestConfig' => [
                'format' => Client::FORMAT_JSON,
                'headers' => [
                    'Authorization' => 'Bearer ' . $token,
                    'Content-Type' => 'application/json',
                    'X-Proxy-Url' => 'https://api.openai.com/v1/chat/completions', // - set url to your api
//                    'X-Access-Token' => 'your-access-token' // - set access token for secure requests
                ],
            ],
        ]);

        $response = $this->client->post('', [
            'model' => 'gpt-3.5-turbo',
            'messages' => [
                [
                    'role' => 'user',
                    'content' => 'Hello, how are you?',
                ],
            ],
        ]);

        if ($response->isOk) {
            $data = $response->data;
            // - do something with response data
        } else {
            // - handle error
        }
        $this->client->close();
]]>
0
[extension] sandritsch91/yii2-widget-flatpickr Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/sandritsch91/yii2-widget-flatpickr https://www.yiiframework.com/extension/sandritsch91/yii2-widget-flatpickr Sandritsch91 Sandritsch91

yii2-flatpickr

A flatpickr widget for Yii2

]]>
0
[extension] sandritsch91/yii2-widget-form-wizard Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/sandritsch91/yii2-widget-form-wizard https://www.yiiframework.com/extension/sandritsch91/yii2-widget-form-wizard Sandritsch91 Sandritsch91

yii2-form-wizard

  1. Features
  2. Installation
  3. Usage
  4. Contributing

A Yii2 form-wizard widget for bootstrap 5

Alt preview

Features

  • Bootstrap 5
  • Client side validation, with the option to validate each step separately

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist sandritsch91/yii2-form-wizard

or add

"sandritsch91/yii2-form-wizard": "*"

to the require section of your composer.json

Usage

use sandritsch91\yii2-form-wizard\FormWizard;

echo FormWizard::widget([
    // required
    'model' => $model,                                                          // The model to be used in the form
    'tabOptions' => [                                                           // These are the options for the Bootstrap Tab widget                                        
        'items' => [
            [
                'label' => 'Step 1',                                            // The label of the tab, if omitted, a default-label will be used (Step 1, Step 2, ...)
                'content' => $this->render('_step1', ['model' => $model]),      // Either the content of the tab
            ],
            [
                'label' => 'Step 2',
                'view' => '/test/_step2',                                       // or a view to be rendered. $model and $form are passed to the view
                'params' => ['a' => 1, 'b' => 2]                                // Pass additional parameters to the view
            ]
        ],
        'navType' => 'nav-pills'
    ],
    // optional
    'validateSteps' => [                                                        // Optional, pass the fields to be validated for each step.                 
        ['name', 'surname'],
        [],                                                                     // Leave array empty if no validation is needed  
        ['email', 'password']
    ],
    'options' => [],                                                            // Wizard-container html options
    'formOptions' => [],                                                        // Form html options
    'buttonOptions' => [                                                        // Button html options
        'previous' => [
            'class' => ['btn', 'btn-secondary'],
            'data' => [
                'formwizard' => 'previous'                                      // If you change this, make sure the clientOptions match
            ]
        ],
        'next' => [...],
        'finish' => [...]
    ],
    'clientOptions' => [                                                        // Client options for the form wizard, if you need to change them
        // 'finishSelector' => '...',
        // 'nextSelector' => '...',
        // 'previousSelector' => '...',
        // 'keepPosition' => true                                               // Keep scroll position on step change.
                                                                                // Set to false to disable, or pass a selector if you have a custom scroll container.
                                                                                // Defaults to true.
    ],
    'clientEvents' => [                                                         // Client events for the form wizard
        // 'onNext' => 'function () {...}',
        // 'onPrevious' => 'function () {...}',
        // 'onFinish' => 'function (){...}'
    ]
]);

Contributing

Contributions are welcome.

If you have any questions, ideas, suggestions or bugs, please open an issue.

Testing

This package uses codeception for testing. To run the tests, run the following commands:


#### Unit tests

run ```php.exe .\vendor\bin\codecept run Unit``` in the root directory of this repository.

#### Functional tests

run ```php.exe .\vendor\bin\codecept run Functional``` in the root directory of this repository.

#### Accpetance tests

To be able to run acceptance tests, a few requirements are needed:

For Windows:\

- install java runtime environment
- install nodejs
- install selenium-standalone: `npm install -g selenium-standalone`
- start selenium-standalone: `selenium-standalone install && selenium-standalone start`
- host a yii2 application on a server or locally via ```./yii serve```
    - add this plugin as a dependency to your ```composer.json``` and update dependencies
    - site must be reachable over http://formwizard.com/
    - add an action ```actionTest``` to the ```SiteController```, as described below
    - this action must return a view file, as described below
    - run ```php.exe .\vendor\bin\codecept run Acceptance```

For Linux:  
Never did that before, but I think it is similar to the Windows setup.

The action in the SiteController:

```php
public function actionTest(): string
{
    include __DIR__ . '/../vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/models/User.php';

    $model = new User();

    if (Yii::$app->request->post() && $model->load(Yii::$app->request->post()) && $model->validate()) {
        return 'success';
    }

    return $this->render('test', [
        'model' => new User()
    ]);
}
```

The view returned by the action:

```php
/** @var User $model */

use sandritsch91\yii2\formwizard\FormWizard;
use sandritsch91\yii2\formwizard\tests\Support\Data\models\User;

$wizard = FormWizard::widget([
    'model' => $model,
    'tabOptions' => [
        'options' => [
            'class' => 'mb-3'
        ],
        'items' => [
            [
                'label' => 'Step 1',
                'view' => '@app/vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/views/site/step1',
                'linkOptions' => [
                    'id' => 'step1-link',,
                    'params' => [
                        'test' => 'some test variable'
                    ]
                ]
            ],
            [
                'label' => 'Step 2',
                'view' => '@app/vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/views/site/step2',
                'linkOptions' => [
                    'id' => 'step2-link',
                ]
            ],
            [
                'label' => 'Step 3',
                'view' => '@app/vendor/sandritsch91/yii2-widget-form-wizard/tests/Support/Data/views/site/step3',
                'linkOptions' => [
                    'id' => 'step3-link',
                ]
            ]
        ],
        'navType' => 'nav-pills'
    ],
    'validateSteps' => [
        ['firstname', 'lastname'],
        ['username', 'password', 'password_validate'],
        ['email']
    ],
    'clientOptions' => [
        'keepPosition' => true
    ]
]);

echo \yii\helpers\Html::tag('div', $wizard, [
    'class' => 'col-4'
]);
```

After the initial installation, you only have to start the selenium-standalone server ```selenium-standalone start```
and run the tests ```php.exe .\vendor\bin\codecept run Acceptance``` in the root directory of this repository.

If you do not want to setup an application, just run the unit and functional tests by
running ```php.exe .\vendor\bin\codecept run Unit,Functional```, I can modify and run the acceptance tests for you,
after you opened a pull request.

]]>
0
[wiki] Integrating Yii3 packages into WordPress Mon, 04 Mar 2024 16:34:16 +0000 https://www.yiiframework.com/wiki/2579/integrating-yii3-packages-into-wordpress https://www.yiiframework.com/wiki/2579/integrating-yii3-packages-into-wordpress glpzzz glpzzz
  1. Source code available
  2. Goal
  3. Approach
  4. Conclusion

I was recently assigned with the task of integrating several extensive forms into a WordPress website. These forms comprised numerous fields, intricate validation rules, dynamic fields (one to many relationships) and even interdependencies, where employing PHP inheritance could mitigate code duplication.

Upon initial exploration, it became evident that the conventional approach for handling forms in WordPress typically involves either installing a plugin or manually embedding markup using the editor or custom page templates. Subsequently, one largely relies on the plugin's functionality to manage form submissions or resorts to custom coding.

Given that part of my task entailed logging data, interfacing with API endpoints, sending emails, and more, I opted to develop the functionality myself, rather than verifying if existing plugins supported these requirements.

Furthermore, considering the current landscape (as of March 2024) where most Yii 3 packages are deemed production-ready according to official sources, and being a long-time user of the Yii framework, I deemed it an opportune moment to explore and acquaint myself with these updates.

Source code available

You can explore the entire project and review the code by accessing it on Github.

Additionally, you can deploy it effortlessly using Docker by simply executing docker-compose up from the project's root directory. Check the Dockerfile for the WordPress setup and content generation which is done automatically.

Goal

My objective was to render and manage forms within a WordPress framework utilizing Yii3 packages. For demonstration purposes, I chose to implement a basic Rating Form, where the focus is solely on validating the data without executing further actions.

Approach

To proceed, let's start with a minimalistic classic theme as an example. I created a WordPress page named "The Rating Form" within the dashboard. Then, a file named page-the-rating-form.php is to be created within the theme's root folder to display this specific page.

This designated file serves as the blueprint for defining our form's markup.

Adding Yii3 Packages to the Project:

To harness Yii3's functionalities, we'll incorporate the following packages:

To begin, let's initialize a Composer project in the root of our theme by executing composer init. This process will generate a composer.json file. Subsequently, we'll proceed to include the Yii3 packages in our project.

composer require yiisoft/form-model:dev-master yiisoft/validator yiisoft/form:dev-master

and instruct the theme to load the composer autoload by adding the following line to the functions.php file:

require __DIR__ . '/vendor/autoload.php';
Create the form model

Following the execution of the composer init command, a src directory has been created in the root directory of the theme. We will now proceed to add our form model class within this directory.

Anticipating the expansion of the project, it's imperative to maintain organization. Thus, we shall create the directory src/Forms and place the RatingForm class inside it.

<?php

namespace Glpzzz\Yii3press\Forms;

use Yiisoft\FormModel\FormModel;

class RatingForm extends FormModel
{

	private ?string $name = null;
	private ?string $email = null;
	private ?int $rating = null;
	private ?string $comment = null;
	private string $action = 'the_rating_form';

	public function getPropertyLabels(): array
	{
		return [
			'name' => 'Name',
			'email' => 'Email',
			'rating' => 'Rating',
			'comment' => 'Comment',
		];
	}

}

Beyond the requisite fields for our rating use case, it's crucial to observe the action class attribute. This attribute is significant as it instructs WordPress on which theme hook should manage the form submission. Further elaboration on this will follow.

Adding Validation Rules to the Model:

Now, let's incorporate some validation rules into the model to ensure input integrity. Initially, we'll configure the class to implement the RulesProviderInterface. This enables the form package to access these rules and augment the HTML markup with native validation attributes.

class RatingForm extends FormModel implements RulesProviderInterface

Now we need to implement the getRules() method on the class.

public function getRules(): iterable
{
	return [
		'name' => [
			new Required(),
		],
		'email' => [
			new Required(),
			new Email(),
		],
		'rating' => [
			new Required(),
			new Integer(min: 0, max: 5),
		],
		'comment' => [
			new Length(min: 100),
		],
	];
}
Create the form markup

To generate the form markup, we require an instance of RatingForm to be passed to the template. In WordPress, the approach I've adopted involves creating a global variable (admittedly not the most elegant solution) prior to rendering the page.


$hydrator = new Hydrator(
	new CompositeTypeCaster(
		new NullTypeCaster(emptyString: true),
		new PhpNativeTypeCaster(),
		new HydratorTypeCaster(),
	)
);

add_filter('template_redirect', function () use ($hydrator) {
	// Get the queried object
	$queried_object = get_queried_object();

	// Check if it's a page
	if ($queried_object instanceof WP_Post && is_page()) {
		if ($queried_object->post_name === 'the-rating-form') {
			global $form;
			if ($form === null) {
				$form = $hydrator->create(RatingForm::class, []);
			}
		}
	}
});

It's worth noting that we've instantiated the Hydrator class outside any specific function, enabling us to reuse it for all necessary callbacks. With the RatingForm instance now available, we'll proceed to craft the markup for the form within the page-the-rating-form.php file.


<?php

use Glpzzz\Yii3press\Forms\RatingForm;
use Yiisoft\FormModel\Field;
use Yiisoft\Html\Html;

/** @var RatingForm $form */
global $form;

?>


<?php get_header(); ?>

<h1><?php the_title(); ?></h1>

<?php the_content(); ?>

<?= Html::form()
  ->post(esc_url(admin_url('admin-post.php')))
  ->open()
?>

<?= Field::hidden($form, 'action')->name('action') ?>
<?= Field::text($form, 'name') ?>
<?= Field::email($form, 'email') ?>
<?= Field::range($form, 'rating') ?>
<?= Field::textarea($form, 'comment') ?>

<?= Html::submitButton('Send') ?>

<?= "</form>" ?>

<?php get_footer(); ?>

In the markup generation of our form, we've leveraged a combination of Yii3's Html helpers and the Field class. Notable points include:

  • The form employs the POST method with the action specified as the admin-post.php WordPress endpoint.
  • To include the action value in the form submission, we utilized a hidden field named 'action'. We opted to rename the input to 'action' as the Field::hidden method generates field names in the format TheFormClassName[the_field_name], whereas we required it to be simply named 'action'.

This adjustment facilitates hooking into a theme function to handle the form request, as elucidated in the subsequent section.

Before delving further, let's capitalize on Yii's capabilities to enhance the form. Although we've already defined validation rules in the model for validating input post-submission, it's advantageous to validate input within the browser as well. While we could reiterate defining these validation rules directly on the input elements, Yii offers a streamlined approach. By incorporating the following code snippet into the functions.php file:

add_action('init', function () {
	ThemeContainer::initialize([
			'default' => [
				'enrichFromValidationRules' => true,
			]
		], 'default', new ValidationRulesEnricher()
	);
});

By implementing this code snippet, we activate the ValidationRulesEnricher for the default form theme. Upon activation, we'll notice that the form fields are now enriched with validation rules such as 'required', 'min', and ' max', aligning with the validation rules previously defined in the model class. This feature streamlines the process, saving us valuable time and minimizing the need for manual code composition. Indeed, this showcases some of the remarkable functionality offered by Yii3.

Process the POST request

When the form is submitted, it is directed to admin-post.php, an endpoint provided by WordPress. However, when dealing with multiple forms, distinguishing the processing of each becomes essential. This is where the inclusion of the action value in the POST request proves invaluable.

Take note of the initial two lines in the following code snippet: the naming convention for the hook is admin_post_<action_name>. Therefore, if a form has action = 'the-rating-form', the corresponding hook name will be admin_post_the_rating_form.

As for the inclusion of both admin_post_<action_name> and admin_post_nopriv_<action_name>, this is because WordPress allows for different handlers depending on whether the user is logged in or not. In our scenario, we require the same handler regardless of the user's authentication status.

add_action('admin_post_the_rating_form', fn() => handleForms($hydrator));
add_action('admin_post_nopriv_the_rating_form', fn() => handleForms($hydrator));

function handleForms(Hydrator $hydrator): void
{
  global $form;
  $form = $hydrator->create(RatingForm::class, $_POST['RatingForm']);
  $result = (new Yiisoft\Validator\Validator())->validate($form);

  if ($form->isValid()) {
    // handle the form
  }

  get_template_part('page-the-rating-form');
}

Returning to the Yii aspect: we instantiate and load the posted data into the form utilizing the hydrator. We then proceed to validate the data. If the validation passes successfully, we can proceed with the intended actions using the validated data. However, if validation fails, we re-render the form, populating it with the submitted data and any error messages generated during validation.

Conclusion

  • This was my first attempt at mixing Yii3 packages with a WordPress site. While I'm satisfied with the result, I think it can be improved, especially regarding the use of global variables. Since I'm not very experienced with WordPress, I'd appreciate any suggestions for improvement.
  • The Yii3 packages I used are ready for real-world use and offer the same quality and features as their older versions.
  • Now you can use these Yii packages independently. This means you can apply your Yii skills to any PHP project.
  • This project shows how we can enhance a WordPress site by tapping into the powerful features of Yii, while still keeping the simplicity of the CMS.

Originally posted on https://glpzzz.dev/2024/03/03/integrating-yii3-packages-into-wordpress.html

]]>
0
[extension] neoacevedo/yii2-auditing Wed, 28 Feb 2024 00:31:28 +0000 https://www.yiiframework.com/extension/neoacevedo/yii2-auditing https://www.yiiframework.com/extension/neoacevedo/yii2-auditing NestorAcevedo NestorAcevedo

Yii2 Auditing

  1. Instalación
  2. Uso
  3. Desplegando la información

Registra cambios de sus modelos ActiveRecord de Yii2.

Este paquete permite mantener un historial de cambios de los modelos proveyendo información sobre posibles discrepancias o anomalías en la información que puedan indicar actividades sospechosas. La información recibida y almacenada se puede posteriormente desplegar de diversas maneras.

Instalación

La forma preferida de instalar esta extensión es a través de composer.

Luego ejecute

php composer.phar require --prefer-dist neoacevedo/yii2-auditing "*"

o agregue

"neoacevedo/yii2-auditing": "*"

a la sección require de su archivo composer.json.

Uso

Una vez que la extensión está instalada, en el archivo de configuración de la consola de su aplicación, agregue en la zona migrationPath

...
'@vendor/neoacevedo/yii2-auditing/neoacevedo/auditing/migrations',
...

luego, agregue en el código de su modelo dentro del método behaviors:

public function behaviors()
{
    return [
        [
            'class' => \neoacevedo\auditing\behaviors\AuditBehavior::class,
            'deleteOldData' => true, // Para borrar datos antiguos del registro de eventos
            'deleteNumRows' => 20, // Borra esta cantidad de registros
            'ignored' => ['foo', 'bar'], // No registra en el registro de eventos estos atributos del modelo
        ],
        ...
    ];
}

Desplegando la información

Puede desplegar la información como cualquier modelo que haya implementado dentro de su aplicación web.

Puede hacer uso de un controlador y una vista que use GridView para listar el historial. Por ejemplo, puede crear un controllador que se llame AuditingController y crear el método actionIndex como lo siguiente:

    /**
     * Lists all Auditing models.
     *
     * @return string
     */
    public function actionIndex()
    {
        $searchModel = new AuditingSearch();
        $dataProvider = $searchModel->search($this->request->queryParams);

        return $this->render('index', [
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
        ]);
    }

Para visualizar los datos, crear el método actionView:

    /**
     * Displays a single Auditing model.
     * @param int $id ID
     * @return string
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionView($id)
    {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }

Dentro de la vista view puede agregar el GridView para listar el histórico:

...
    <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],
            'id',
            'user_id',
            'description',
            'event',
            'model',
            'attribute',
            'old_value',
            'new_value',
            'action',
            'ip',
            'created_at',
        ],
    ]); ?>
...
]]>
0
[extension] luguohuakai/yii2-dm Sun, 29 Dec 2024 06:22:07 +0000 https://www.yiiframework.com/extension/luguohuakai/yii2-dm https://www.yiiframework.com/extension/luguohuakai/yii2-dm luguohuakai luguohuakai

Database extension for DM

  1. Installation
  2. Usage

A database extension for DM database

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist luguohuakai/yii2-dm "*"

or add

"luguohuakai/yii2-dm": "*"

to the require section of your composer.json file.

Usage

Once the extension is installed, simply use it in your code by :

'components' => [
    'db' => [
        'class' => 'luguohuakai\db\dm\Connection',
        'dsn' => 'dm:host=localhost:xxx;schema=xxx',
        'username' => 'SYSDBA',
        'password' => 'SYSDBA',
    ]
]
]]>
0
[extension] rashedalkhatib/yii2-datatables Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/rashedalkhatib/yii2-datatables https://www.yiiframework.com/extension/rashedalkhatib/yii2-datatables RashedAlkhatib RashedAlkhatib

DataTable Widget

  1. Overview
  2. installation
  3. Usage Example (PHP Widget)
  4. Usage Example (Java Script)
  5. Usage API Side
  6. Feel Free to contact me : [email protected]

Overview

The DataTable widget is used to create interactive and dynamic data tables. The provided JavaScript code demonstrates how to initialize DataTable with server-side processing, custom data handling, and column rendering and with full serverside Export .

installation

in your Yii2 application :
  • Run : `composer require rashedalkhatib/yii2-datatables:1.0.0`
  • go to your `../frontend/assets/AppAsset.php`
    • add rashedalkhatib\datatables\DataTableAsset your $depends array
    • Ex:
              public $depends = [
                'yii\web\YiiAsset',
                'yii\bootstrap\BootstrapAsset',
                'yii\bootstrap\BootstrapPluginAsset',
                'rashedalkhatib\datatables\DataTableAsset'
        ];
      

Usage Example (PHP Widget)

- application side
$searchFormSelector = '#searchForm';
$ajaxUrl = Url::to(['api/yourEndPoint']); // Adjust the URL based on your routes

// Define your DataTable columns
$columns = [
    [
        'title' => 'ID',
        'data' => 'id',
        'visible' => true,
        'render' => new JsExpression('function(data, type, row) {
            return "demo";
        }'),
    ],
];

// Configure other DataTable parameters
$processing = true;
$serverSide = true;
$pageLength = 10;
$dom = 'Btip';
$buttons = [
    [
        'extend' => 'excel',
        'text' => 'Excel',
        'titleAttr' => 'Excel',
        'action' => new JsExpression('exportAll') // this is required 
    ],
];

// Configure Ajax settings
$ajaxConfig = [
    'url' => $ajaxUrl,
    'bdestroy' => true,
    'type' => 'POST',
    'data' => new JsExpression('function(d) {
            var searchForm = $('body').find('#searchForm').serializeArray();
            
            searchForm[searchForm.length] = { name: 'YourModel[page]', value: d.start }; // required
            searchForm[searchForm.length] = { name: 'YourModel[length]', value: d.length }; // required
            searchForm[searchForm.length] = { name: 'YourModel[draw]', value: d.draw }; // required
            
            var order = {
                'attribute': d.columns[d.order[0]['column']]['data'],
                'dir': d.order[0]['dir']
            }; // required
            
            searchForm[searchForm.length] = { name: 'YourModel[order]', value: JSON.stringify(order) };
            return searchForm;
    }'),
    'dataSrc' => new JsExpression('function(d) {
        var searchForm = $("' . $searchFormSelector . '").serializeArray();
        if (d.validation) {
            searchForm.yiiActiveForm("updateMessages", d.validation, true);
            return [];
        }
        return d.data;
    }'),
];

// Use the DataTableWidget with configured parameters
DataTable::widget([
    'id' => 'yourDataTable',
    'ajaxConfig' => $ajaxConfig,
    'columns' => $columns,
    'processing' => $processing,
    'serverSide' => $serverSide,
    'pageLength' => $pageLength,
    'dom' => $dom,
    'buttons' => $buttons,
]);

// The HTML container for your DataTable
echo '<form id="searchForm">// your inputs </form>';
echo '<table id="yourDataTable" class="display"></table>';

Usage Example (Java Script)

- application side
front end
<form id="searchForm">
// your inputs 
</form>

<table id="yourDataTable" class="display" style="width:100%">


</table>
var arrayToExport = [0,1];
$('#yourDataTable').DataTable({
    "ajax": {
        // Server-side processing configuration
        "url": "../api/yourEndPoint",
        "bdestroy": true, // this allows you to re init the dataTabel and destory it 
        "type": "POST", // request method
        "data": function (d) { // this represent the data you are sending with your ajax request
            // Custom function for sending additional parameters to the server
            var searchForm = $('body').find('#searchForm').serializeArray();
            
            searchForm[searchForm.length] = { name: "YourModel[page]", value: d.start }; // required
            searchForm[searchForm.length] = { name: "YourModel[length]", value: d.length }; // required
            searchForm[searchForm.length] = { name: "YourModel[draw]", value: d.draw }; // required
            
            var order = {
                'attribute': d.columns[d.order[0]['column']]['data'],
                'dir': d.order[0]['dir']
            }; // required
            
            searchForm[searchForm.length] = { name: "YourModel[order]", value: JSON.stringify(order) };
            return searchForm;
        },
        dataSrc: function (d) {
            // Custom function to handle the response data
            // EX:
            var searchForm = $('body').find('#searchForm').serializeArray();
            if (d.validation) {
                searchForm.yiiActiveForm('updateMessages', d.validation, true);
                return [];
            }
            return d.data;
        }
    },
    "columns": [{
        // Column configurations
        "title": "ID",
        "data": "id",
        "visible": true // visablity of column 
    },
    // ... (other columns)
    {
        "title": "Actions",
        "data": "id",
        "visible": actionCol,
        "render": function (data, type, row) {
            // Custom rendering function for the "Actions" column
            return '<a class="showSomething" data-id="' + row.id + '">View</a>';
        }
    }],
    processing: true,
    serverSide: true,
    "pageLength": 10,
    dom: "Btip",
    "buttons": [{
        // "Excel" button configuration
        "extend": 'excel',
        exportOptions: {
            columns: arrayToExport
        },
        "text": '  Excel',
        "titleAttr": 'Excel',
        "action": exportAll // newexportaction this action is to allow you exporting with server side without rendaring data 
    }],
});
application back end
these params should be sent to the API
// in your HTTP request you want to include these params 
   $_postData = [
   'page' => $this->page == 0 ? 0 : $this->page / $this->length, // this equation is required to handle Yii2 Data provider Logic
   'limit' => $this->length,
   'export' => $this->export,
   'order' => $this->order,
   // add your custom params .....
   ];
these params should be returned to the Datatable endpoint
return $this->asJson(
                    [
                        'data' => $_scoreData->data,
                        'draw' => $_scoreSearchForm->draw,
                        'recordsTotal' => $_scoreData->count, 
                        'recordsFiltered' => $_scoreData->count
                ]);

Usage API Side

yourEndPoint action
    public function actionYourEndPoint()
    {

        $searchModel = new SearchModel();

        $dataProvider = $searchModel->search(Yii::$app->request->get());
        return $this->asJson(
            array(
                'data' => $dataProvider['data'],
                'count' => $dataProvider['count']
            )
        );

    }
search function
    public function search($params)
    {
        $this->load($params, ''); // load your values into the model
        $query = Data::find(); // Data model is your link to the database

        $_order = json_decode($this->order);
        if ($this->export == 'true') {
            $dataProvider = new ActiveDataProvider([
                'query' => $query
                // we removed the page and pageSize keys to allow all data to be exported
            ]);
        } else {
            $_orderType = SORT_ASC;
            if ($_order->dir == 'desc')
                $_orderType = SORT_DESC;
            $query->orderBy([$_order->attribute => $_orderType]);
            $dataProvider = new ActiveDataProvider([
                'query' => $query,
                'pagination' => [
                    'pageSize' => $this->limit,
                    'page' => $this->page,
                ],
            ]);
        }


        return array(
            'data' => $dataProvider->getModels(),
            'count' => $dataProvider->getTotalCount()
        );
    }

Feel Free to contact me : [email protected]

]]>
0
[extension] eluhr/yii2-json-attribute-behavior Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/extension/eluhr/yii2-json-attribute-behavior https://www.yiiframework.com/extension/eluhr/yii2-json-attribute-behavior eluhr eluhr

Yii2 JSON Attribute Behavior

  1. Installation
  2. Usage
  3. Testing

This behavior automatically decodes attributes from JSON to arrays before validation, handling errors and re-encoding if validation fails. With this a "real" json string can be further processed.

CI Workflow

Installation

The preferred way to install this extension is through composer.

Either run

composer require --prefer-dist eluhr/yii2-json-attribute-behavior "*"

or add

"eluhr/yii2-json-attribute-behavior": "*"

to the require section of your composer.json file.

Usage

In a yii\base\Model or a derivation thereof, the behavior can be used as follows:

public function behaviors(): array
{
    $behaviors = parent::behaviors();
    $behaviors['json-attribute'] = [
        'class' => eluhr\jsonAttributeBehavior\JsonAttributeBehavior::class,
        'attributes' => [
            'data_json'
        ]
    ];
    return $behaviors;
}

By using this behavior it does not matter if the attribute is a string or an array. The behavior will always ensure, that the attribute is an array before saving the data to the database and yii will handle the rest.

This behavior supports i18n. By adding the json-attribute-behavior category in your config you can overwrite the default error messages.

Testing

After installing dependencies via composer you can run the tests with:

make test
]]>
0
[wiki] Create Bootstrap5 based Image carousel with thumbnails Mon, 04 Dec 2023 13:03:38 +0000 https://www.yiiframework.com/wiki/2578/create-bootstrap5-based-image-carousel-with-thumbnails https://www.yiiframework.com/wiki/2578/create-bootstrap5-based-image-carousel-with-thumbnails pravi pravi

Use the following css styles for carousel to work as expected.


  .product_img_slide {
    padding: 100px 0 0 0;
  }

  .product_img_slide > .carousel-inner > .carousel-item {
    overflow: hidden;
    max-height: 650px;
  }

  .carousel-inner {
    position: relative;
    width: 100%;
  }

  .product_img_slide > .carousel-indicators {
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    bottom: auto;
    margin: auto;
    font-size: 0;
    cursor: e-resize;
    /* overflow-x: auto; */
    text-align: left;
    padding: 10px 5px;
    /*  overflow-y: hidden;*/
    white-space: nowrap;
    position: absolute;
  }

  .product_img_slide > .carousel-indicators li {
    padding: 0;
    width: 76px;
    height: 76px;
    margin: 0 5px;
    text-indent: 0;
    cursor: pointer;
    background: transparent;
    border: 3px solid #333331;
    -webkit-border-radius: 0;
    border-radius: 0;
    -webkit-transition: all 0.7s cubic-bezier(0.22, 0.81, 0.01, 0.99);
    transition: all 1s cubic-bezier(0.22, 0.81, 0.01, 0.99);
  }

  .product_img_slide > .carousel-indicators .active {
    width: 76px;
    border: 0;
    height: 76px;
    margin: 0 5px;
    background: transparent;
    border: 3px solid #c13c3d;
  }

  .product_img_slide > .carousel-indicators > li > img {
    display: block;
    /*width:114px;*/
    height: 76px;
  }

  .product_img_slide .carousel-inner > .carousel-item > a > img, .carousel-inner > .carousel-item > img, .img-responsive, .thumbnail a > img, .thumbnail > img {
    display: block;
    max-width: 100%;
    line-height: 1;
    margin: auto;
  }

  .product_img_slide .carousel-control-prev {
    top: 58%;
    /*left: auto;*/
    right: 76px;
    opacity: 1;
    width: 50px;
    bottom: auto;
    height: 50px;
    font-size: 50px;
    cursor: pointer;
    font-weight: 700;
    overflow: hidden;
    line-height: 50px;
    text-shadow: none;
    text-align: center;
    position: absolute;
    background: transparent;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.6);
    -webkit-box-shadow: none;
    box-shadow: none;
    -webkit-border-radius: 0;
    border-radius: 0;
    -webkit-transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
    transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
  }

  .product_img_slide .carousel-control-next {
    top: 58%;
    left: auto;
    right: 25px;
    opacity: 1;
    width: 50px;
    bottom: auto;
    height: 50px;
    font-size: 50px;
    cursor: pointer;
    font-weight: 700;
    overflow: hidden;
    line-height: 50px;
    text-shadow: none;
    text-align: center;
    position: absolute;
    background: transparent;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.6);
    -webkit-box-shadow: none;
    box-shadow: none;
    -webkit-border-radius: 0;
    border-radius: 0;
    -webkit-transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
    transition: all 0.6s cubic-bezier(0.22, 0.81, 0.01, 0.99);
  }

  .product_img_slide .carousel-control-next:hover, .product_img_slide .carousel-control-prev:hover {
    color: #c13c3d;
    background: transparent;
  }

Here is a Corousel widget that is an extension of yii\bootstrap5\Carousel, to show image thumbnails as indicators for the carousel.

Here is the widget code.

<?php
namespace app\widgets;
use Yii;
use yii\bootstrap5\Html;

class Carousel extends \yii\bootstrap5\Carousel
{
    public $thumbnails = [];

    public function init()
    {
        parent::init();     
        Html::addCssClass($this->options, ['data-bs-ride' => 'carousel']);
        if ($this->crossfade) {
            Html::addCssClass($this->options, ['animation' => 'carousel-fade']);
        }
    }

    public function renderIndicators(): string
    {
        if ($this->showIndicators === false){
            return '';
        }
        $indicators = [];
        for ($i = 0, $count = count($this->items); $i < $count; $i++){
            $options = [
                'data' => [
                    'bs-target' => '#' . $this->options['id'],
                    'bs-slide-to' => $i
                ],
                'type' => 'button',
                'thumb' => $this->thumbnails[$i]['thumb']
            ];
            if ($i === 0){
                Html::addCssClass($options, ['activate' => 'active']);
                $options['aria']['current'] = 'true';
            }       

             $indicators[] = Html::tag('li',Html::img($options['thumb']), $options);
        }
        return Html::tag('ol', implode("\n", $indicators), ['class' => ['carousel-indicators']]);
    } }

You can use the above widget in your view file as below:

    <?php  
$indicators = [
   '0' =>[ 'thumb' => "https://placehold.co/150X150?text=A"],
   '1' => ['thumb' => 'https://placehold.co/150X150?text=B'],
   '2' => [ 'thumb' => 'https://placehold.co/150X150?text=C']
];
$items = [
    [ 'content' =>Html::img('https://live.staticflickr.com/8333/8417172316_c44629715e_w.jpg')],
    [ 'content' =>Html::img('https://live.staticflickr.com/3812/9428789546_3a6ba98c49_w.jpg')],
    [ 'content' =>Html::img('https://live.staticflickr.com/8514/8468174902_a8b505a063_w.jpg')]   
];

echo Carousel::widget([
    'items' => 
        $items,
     'thumbnails'  => $indicators,
     'options' => [       
          'data-interval' => 3, 'data-bs-ride' => 'scroll','class' => 'carousel product_img_slide',
      ],

]);
]]>
0
[wiki] How to add a DropDown Language Picker (i18n) to the Menu Sat, 16 Dec 2023 15:42:40 +0000 https://www.yiiframework.com/wiki/2577/how-to-add-a-dropdown-language-picker-i18n-to-the-menu https://www.yiiframework.com/wiki/2577/how-to-add-a-dropdown-language-picker-i18n-to-the-menu JQL JQL

How To Add Internationalisation to the NavBar Menu in Yii2

  1. Create the required Files
  2. Edit the /config/web.php file
  3. Edit all the files in the "views" folder and any sub folders
  4. Create the texts to be translated
  5. Create a Menu Item (Dropdown) to Change the Language
  6. Optional Items

Yii comes with internationalisation (i18n) "out of the box". There are instructions in the manual as to how to configure Yii to use i18n, but little information all in one place on how to fully integrate it into the bootstrap menu. This document attempts to remedy that.

Screenshot_i18n_s.png

The Github repository also contains the language flags, some country flags, a list of languages codes and their language names and a list of the languages Yii recognises "out of the box". A video will be posted on YouTube soon.

Ensure that your system is set up to use i18n. From the Yii2 Manual:

Yii uses the PHP intl extension to provide most of its I18N features, such as the date and number formatting of the yii\i18n\Formatter class and the message formatting using yii\i18n\MessageFormatter. Both classes provide a fallback mechanism when the intl extension is not installed. However, the fallback implementation only works well for English target language. So it is highly recommended that you install intl when I18N is needed.

Create the required Files

First you need to create a configuration file.

Decide where to store it (e.g. in the ./messages/ directory with the name create_i18n.php). Create the directory in the project then issue the following command from Terminal (Windows: CMD) from the root directory of your project:

./yii message/config-template ./messages/create_i18n.php

or for more granularity:

./yii message/config --languages=en-US --sourcePath=@app --messagePath=messages ./messages/create_i18n.php

In the newly created file, alter (or create) the array of languages to be translated:

  // array, required, list of language codes that the extracted messages
  // should be translated to. For example, ['zh-CN', 'de'].
  'languages' => [
    'en-US',
    'fr',
    'pt'
  ],

If necessary, change the root directory in create_i18n.php to point to the messages directory - the default is messages. Note, if the above file is in the messages directory (recommended) then don't alter this 'messagePath' => __DIR__,. If you alter the directory for messages to, say, /config/ (not a good idea) you can use the following:

  // Root directory containing message translations.
  'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'config',

The created file should look something like this after editing the languages you need:

<?php

return [
  // string, required, root directory of all source files
  'sourcePath' => __DIR__ . DIRECTORY_SEPARATOR . '..',
  // array, required, list of language codes (in alphabetical order) that the extracted messages
  // should be translated to. For example, ['zh-CN', 'de'].
  'languages' => [
    // to localise a particular language use the language code followed by the dialect in CAPS
    'en-US',  // USA English
    'es',
    'fr',
    'it',
    'pt',
  ],
  /* 'languages' => [
    'af', 'ar', 'az', 'be', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'es', 'et', 'fa', 'fi', 'fr', 'he', 'hi',
    'pt-BR', 'ro', 'hr', 'hu', 'hy', 'id', 'it', 'ja', 'ka', 'kk', 'ko', 'kz', 'lt', 'lv', 'ms', 'nb-NO', 'nl',
    'pl', 'pt', 'ru', 'sk', 'sl', 'sr', 'sr-Latn', 'sv', 'tg', 'th', 'tr', 'uk', 'uz', 'uz-Cy', 'vi', 'zh-CN',
    'zh-TW'
    ], */
  // string, the name of the function for translating messages.
  // Defaults to 'Yii::t'. This is used as a mark to find the messages to be
  // translated. You may use a string for single function name or an array for
  // multiple function names.
  'translator' => ['\Yii::t', 'Yii::t'],
  // boolean, whether to sort messages by keys when merging new messages
  // with the existing ones. Defaults to false, which means the new (untranslated)
  // messages will be separated from the old (translated) ones.
  'sort' => false,
  // boolean, whether to remove messages that no longer appear in the source code.
  // Defaults to false, which means these messages will NOT be removed.
  'removeUnused' => false,
  // boolean, whether to mark messages that no longer appear in the source code.
  // Defaults to true, which means each of these messages will be enclosed with a pair of '@@' marks.
  'markUnused' => true,
  // array, list of patterns that specify which files (not directories) should be processed.
  // If empty or not set, all files will be processed.
  // See helpers/FileHelper::findFiles() for pattern matching rules.
  // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
  'only' => ['*.php'],
  // array, list of patterns that specify which files/directories should NOT be processed.
  // If empty or not set, all files/directories will be processed.
  // See helpers/FileHelper::findFiles() for pattern matching rules.
  // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
  'except' => [
    '.*',
    '/.*',
    '/messages',
    '/migrations',
    '/tests',
    '/runtime',
    '/vendor',
    '/BaseYii.php',
  ],
  // 'php' output format is for saving messages to php files.
  'format' => 'php',
  // Root directory containing message translations.
  'messagePath' => __DIR__,
  // boolean, whether the message file should be overwritten with the merged messages
  'overwrite' => true,
  /*
    // File header used in generated messages files
    'phpFileHeader' => '',
    // PHPDoc used for array of messages with generated messages files
    'phpDocBlock' => null,
   */

  /*
    // Message categories to ignore
    'ignoreCategories' => [
    'yii',
    ],
   */

  /*
    // 'db' output format is for saving messages to database.
    'format' => 'db',
    // Connection component to use. Optional.
    'db' => 'db',
    // Custom source message table. Optional.
    // 'sourceMessageTable' => '{{%source_message}}',
    // Custom name for translation message table. Optional.
    // 'messageTable' => '{{%message}}',
   */

  /*
    // 'po' output format is for saving messages to gettext po files.
    'format' => 'po',
    // Root directory containing message translations.
    'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages',
    // Name of the file that will be used for translations.
    'catalog' => 'messages',
    // boolean, whether the message file should be overwritten with the merged messages
    'overwrite' => true,
   */
];

Edit the /config/web.php file

In the web.php file, below 'id' => 'basic', add:

  'language' => 'en',
  'sourceLanguage' => 'en',

Note: you should always use the 'sourceLanguage' => 'en' as it is, usually, easier and cheaper to translate from English into another language. If the sourceLanguage is not set it defaults to 'en'.

Add the following to the 'components' => [...] section:

    'i18n' => [
      'translations' => [
        'app*' => [
          'class' => 'yii\i18n\PhpMessageSource',  // Using text files (usually faster) for the translations
          //'basePath' => '@app/messages',  // Uncomment and change this if your folder is not called 'messages'
          'sourceLanguage' => 'en',
          'fileMap' => [
            'app' => 'app.php',
            'app/error' => 'error.php',
          ],
          //  Comment out in production version
          //  'on missingTranslation' => ['app\components\TranslationEventHandler', 'handleMissingTranslation'],
        ],
      ],
    ],

Edit all the files in the "views" folder and any sub folders

Now tell Yii which text you want to translate in your view files. This is done by adding Yii::t('app', 'text to be translated') to the code.

For example, in /views/layouts/main.php, change the menu labels like so:

    'items' => [
          //  ['label' => 'Home', 'url' => ['/site/index']],	// Orignal code
          ['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
          ['label' => Yii::t('app', 'About'), 'url' => ['/site/about']],
          ['label' => Yii::t('app', 'Contact'), 'url' => ['/site/contact']],
          Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'Login'), 'url' => ['/site/login']] : '<li class="nav-item">'
            . Html::beginForm(['/site/logout'])
            . Html::submitButton(
             // 'Logout (' . Yii::$app->user->identity->username . ')', // change this line as well to the following:
              Yii::t('app', 'Logout ({username})'), ['username' => Yii::$app->user->identity->username]),
              ['class' => 'nav-link btn btn-link logout']
            )
            . Html::endForm()
            . '</li>',
        ],

Create the texts to be translated

To create the translation files, run the following, in Terminal, from the root directory of your project:

./yii message ./messages/create_i18n.php

Now, get the messages translated. For example in the French /messages/fr/app.php

  'Home' => 'Accueil',
  'About' => 'À propos',
  ...

Create a Menu Item (Dropdown) to Change the Language

This takes a number of steps.

1. Create an array of languages required

A key and a name is required for each language.

The key is the ICU language code ISO 639.1 in lowercase (with optional Country code ISO 3166 in uppercase) e.g.

French: fr or French Canada: fr-CA

Portuguese: pt or Portuguese Brazil: pt-BR

The name is the name of the language in that language. e.g. for French: 'Français', for Japanese: '日本の'. This is important as the user may not understand the browser's current language.

In /config/params.php create an array named languages with the languages required. For example:

  /* 		List of languages and their codes
   *
   * 		format:
   * 		'Language Code' => 'Language Name',
   * 		e.g.
   * 		'fr' => 'Français',
   *
   * 		please use alphabetical order of language code
   * 		Use the language name in the "user's" Language
   *            e.g.
   *            'ja' => '日本の',
   */
  'languages' => [
//    'da' => 'Danske',
//    'de' => 'Deutsche',
//    'en' => 'English', // NOT REQUIRED the sourceLanguage (i.e. the default)
    'en-GB' => 'British English',
    'en-US' => 'American English',
    'es' => 'Español',
    'fr' => 'Français',
    'it' => 'Italiano',
//    'ja' => '日本の',  // Japanese with the word "Japanese" in Kanji
//    'nl' => 'Nederlandse',
//    'no' => 'Norsk',
//    'pl' => 'Polski',
    'pt' => 'Português',
//    'ru' => 'Русский',
//    'sw' => 'Svensk',
//    'zh' => '中国的',
  ],
2. Create an Action

In /controllers/SiteController.php, the default controller, add an "Action" named actionLanguage(). This "Action" changes the language and sets a cookie so the browser "remembers" the language for page requests and return visits to the site.

  /**
   * Called by the ajax handler to change the language and
   * Sets a cookie based on the language selected
   *
   */
  public function actionLanguage()
  {
    $lang = Yii::$app->request->post('lang');
    // If the language "key" is not NULL and exists in the languages array in params.php, change the language and set the cookie
    if ($lang !== NULL && array_key_exists($lang, Yii::$app->params['languages']))
    {
      $expire = time() + (60 * 60 * 24 * 365); //  1 year - alter accordingly
      Yii::$app->language = $lang;
      $cookie = new yii\web\Cookie([
        'name' => 'lang',
        'value' => $lang,
        'expire' => $expire,
      ]);
      Yii::$app->getResponse()->getCookies()->add($cookie);
    }
    Yii::$app->end();
  }

Remember to set the method to POST. In behaviors(), under actions, set 'language' => ['post'], like so:

      'verbs' => [
        'class' => VerbFilter::class,
        'actions' => [
          'logout' => ['post'],
          'language' => ['post'],
        ],
      ],
3. Create a Language Handler

Make sure that the correct language is served for each request.

In the /components/ directory, create a file named: LanguageHandler.php and add the following code to it:

<?php

/*
 * Copyright ©2023 JQL all rights reserved.
 * http://www.jql.co.uk
 */
/*
  Created on : 19-Nov-2023, 13:23:54
  Author     : John Lavelle
  Title      : LanguageHandler
 */

namespace app\components;

use yii\helpers\Html;

class LanguageHandler extends \yii\base\Behavior
{

	public function events()
	{
		return [\yii\web\Application::EVENT_BEFORE_REQUEST => 'handleBeginRequest'];
	}

	public function handleBeginRequest($event)
	{
		if (\Yii::$app->getRequest()->getCookies()->has('lang') && array_key_exists(\Yii::$app->getRequest()->getCookies()->getValue('lang'), \Yii::$app->params['languages']))
		{
      //  Get the language from the cookie if set
			\Yii::$app->language = \Yii::$app->getRequest()->getCookies()->getValue('lang');
		}
		else
		{
			//	Use the browser language - note: some systems use an underscore, if used, change it to a hyphen
			\Yii::$app->language = str_replace('_', '-', HTML::encode(locale_accept_from_http($_SERVER['HTTP_ACCEPT_LANGUAGE'])));
		}
	}

}

/* End of file LanguageHandler.php */
/* Location: ./components/LanguageHandler.php */
4. Call LanguageHandler.php from /config/web.php

"Call" the LanguageHandler.php file from /config/web.php by adding the following to either just above or just below 'params' => $params,

  //	Update the language on selection
  'as beforeRequest' => [
    'class' => 'app\components\LanguageHandler',
  ],
5. Add the Language Menu Item to /views/layouts/main.php

main.php uses Bootstrap to create the menu. An item (Dropdown) needs to be added to the menu to allow the user to select a language.

Add use yii\helpers\Url; to the "uses" section of main.php.

Just above echo Nav::widget([...]) add the following code:

// Get the languages and their keys, also the current route
      foreach (Yii::$app->params['languages'] as $key => $language)
      {
        $items[] = [
          'label' => $language, // Language name in it's language - already translated
          'url' => Url::to(['site/index']), // Route
          'linkOptions' => ['id' => $key, 'class' => 'language'], // The language "key"
        ];
      }

In the section:

echo Nav::widget([...])`

between

'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right`

and

'items' => [...]

add:

'encodeLabels' => false, // Required to enter HTML into the labels

like so:

      echo Nav::widget([
        'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right
        'encodeLabels' => false, // Required to enter HTML into the labels
        'items' => [
          ['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
        ...

Now add the Dropdown. This can be placed anywhere in 'items' => [...].

// Dropdown Nav Menu: https://www.yiiframework.com/doc/api/2.0/yii-widgets-menu
        [
          'label' => Yii::t('app', 'Language')),
          'url' => ['#'],
          'options' => ['class' => 'language', 'id' => 'languageTop'],
          'encodeLabels' => false, // Optional but required to enter HTML into the labels for images
          'items' => $items, // add the languages into the Dropdown
        ],

The code in main.php for the NavBar should look something like this:

      NavBar::begin([
        'brandLabel' => Yii::$app->name,  // set in /config/web.php
        'brandUrl' => Yii::$app->homeUrl,
        'options' => ['class' => 'navbar-expand-md navbar-dark bg-dark fixed-top']
      ]);
      // Get the languages and their keys, also the current route
      foreach (Yii::$app->params['languages'] as $key => $language)
      {
        $items[] = [
          'label' => $language, // Language name in it's language
          'url' => Url::to(['site/index']), // Current route so the page refreshes
          'linkOptions' => ['id' => $key, 'class' => 'language'], // The language key
        ];
      }
      echo Nav::widget([
        'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right
        'encodeLabels' => false, // Required to enter HTML into the labels
        'items' => [
          ['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
          ['label' => Yii::t('app', 'About'), 'url' => ['/site/about']],
          ['label' => Yii::t('app', 'Contact'), 'url' => ['/site/contact']],
          // Dropdown Nav Menu: https://www.yiiframework.com/doc/api/2.0/yii-widgets-menu
          [
            'label' => Yii::t('app', 'Language') ,
            'url' => ['#'],
            'options' => ['class' => 'language', 'id' => 'languageTop'],
            'encodeLabels' => false, // Required to enter HTML into the labels
            'items' => $items, // add the languages into the Dropdown
          ],
          Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'Login'), 'url' => ['/site/login']] : '<li class="nav-item">'
            . Html::beginForm(['/site/logout'])
            . Html::submitButton(
//              'Logout (' . Yii::$app->user->identity->username . ')',
              Yii::t('app', 'Logout ({username})', ['username' => Yii::$app->user->identity->username]),
              ['class' => 'nav-link btn btn-link logout']
            )
            . Html::endForm()
            . '</li>',
        ],
      ]);
      NavBar::end();

If Language flags or images are required next to the language name see Optional Items at the end of this document.

6. Trigger the Language change with an Ajax call

To call the Language Action actionLanguage() make an Ajax call in a JavaScript file.

Create a file in /web/js/ named language.js.

Add the following code to the file:

/*
 * Copyright ©2023 JQL all rights reserved.
 * http://www.jql.co.uk
 */

/**
 * Set the language
 *
 * @returns {undefined}
 */
$(function () {
  $(document).on('click', '.language', function (event) {
    event.preventDefault();
    let lang = $(this).attr('id');  // Get the language key
    /* if not the top level, set the language and reload the page */
    if (lang !== 'languageTop') {
      $.post(document.location.origin + '/site/language', {'lang': lang}, function (data) {
        location.reload(true);
      });
    }
  });
});

To add the JavaScript file to the Assets, alter /assets/AppAsset.php in the project directory. In public $js = [] add 'js/language.js', like so:

     public $js = [
       'js/language.js',
     ];

Internationalisation should now be working on your project.

Optional Items

The following are optional but may help both you and/or the user.

1. Check for Translations

Yii can check whether a translation is present for a particular piece of text in a Yii::t('app', 'text to be translated') block.

There are two steps:

A. In /config/web.php uncomment the following line:

  //  'on missingTranslation' => ['app\components\TranslationEventHandler', 'handleMissingTranslation'],

B. Create a TranslationEventHandler:

In /components/ create a file named: TranslationEventHandler.php and add the following code to it:


<?php

/**
 * TranslationEventHandler
 *
 * @copyright © 2023, John Lavelle  Created on : 14 Nov 2023, 16:05:32
 *
 *
 * Author     : John Lavelle
 * Title      : TranslationEventHandler
 */
// Change the Namespace (app, frontend, backend, console etc.) if necessary (default in Yii Basic is "app").

namespace app\components;

use yii\i18n\MissingTranslationEvent;

/**
 * TranslationEventHandler
 *
 *
 * @author John Lavelle
 * @since 1.0 // Update version number
 */
class TranslationEventHandler
{

  /**
   * Adds a message to missing translations in Development Environment only
   *
   * @param MissingTranslationEvent $event
   */
  public static function handleMissingTranslation(MissingTranslationEvent $event)
  {
    // Only check in the development environment
    if (YII_ENV_DEV)
    {
      $event->translatedMessage = "@MISSING: {$event->category}.{$event->message} FOR LANGUAGE {$event->language} @";
    }
  }
}

If there is a missing translation, the text is replaced with a message similar to the following text:

@MISSING: app.Logout (John) FOR LANGUAGE fr @

Here Yii has found that there is no French translation for:

Yii::t('app', 'Logout ({username})', ['username' => Yii::$app->user->identity->username]),
2. Add Language Flags to the Dropdown Menu

This is very useful and recommended as it aids the User to locate the correct language. There are a number of steps for this.

a. Create images of the flags.

The images should be 25px wide by 15px high. The images must have the same name as the language key in the language array in params.php. For example: fr.png or en-US.png. If the images are not of type ".png" change the code in part b. below to the correct file extension.

Place the images in a the directory /web/images/flags/.

b. Alter the code in /views/layouts/main.php so that the code for the "NavBar" reads as follows:

<header id="header">
      <?php
      NavBar::begin([
        'brandLabel' => Yii::$app->name,
        'brandUrl' => Yii::$app->homeUrl,
        'options' => ['class' => 'navbar-expand-md navbar-dark bg-dark fixed-top']
      ]);
      // Get the languages and their keys, also the current route
      foreach (Yii::$app->params['languages'] as $key => $language)
      {
        $items[] = [
	// Display the image before the language name
          'label' => Html::img('/images/flags/' . $key . '.png', ['alt' => 'flag ' . $language, 'class' => 'inline-block align-middle', 'title' => $language,]) . ' ' . $language, // Language name in it's language
          'url' => Url::to(['site/index']), // Route
          'linkOptions' => ['id' => $key, 'class' => 'language'], // The language key
        ];
      }
      echo Nav::widget([
        'options' => ['class' => 'navbar-nav ms-auto'], // ms-auto aligns the menu right
        'encodeLabels' => false, // Required to enter HTML into the labels
        'items' => [
          ['label' => Yii::t('app', 'Home'), 'url' => ['/site/index']],
          ['label' => Yii::t('app', 'About'), 'url' => ['/site/about']],
          ['label' => Yii::t('app', 'Contact'), 'url' => ['/site/contact']],
          // Dropdown Nav Menu: https://www.yiiframework.com/doc/api/2.0/yii-widgets-menu
          [
	  // Display the current language "flag" after the Dropdown title (before the caret)
            'label' => Yii::t('app', 'Language') . ' ' . Html::img('@web/images/flags/' . Yii::$app->language . '.png', ['class' => 'inline-block align-middle', 'title' => Yii::$app->language]),
            'url' => ['#'],
            'options' => ['class' => 'language', 'id' => 'languageTop'],
            'encodeLabels' => false, // Required to enter HTML into the labels
            'items' => $items, // add the languages into the Dropdown
          ],
          Yii::$app->user->isGuest ? ['label' => Yii::t('app', 'Login'), 'url' => ['/site/login']] : '<li class="nav-item">'
            . Html::beginForm(['/site/logout'])
            . Html::submitButton(
//              'Logout (' . Yii::$app->user->identity->username . ')',
              Yii::t('app', 'Logout ({username})', ['username' => Yii::$app->user->identity->username]),
              ['class' => 'nav-link btn btn-link logout']
            )
            . Html::endForm()
            . '</li>',
        ],
      ]);
      NavBar::end();
      ?>
    </header>

That's it! Enjoy...

For further reading and information see:

i18ntutorial on Github

Yii2 Internationalization Tutorial

PHP intl extensions

If you use this code, please credit me as follows:

Internationalization (i18n) Menu code provided by JQL, https://visualaccounts.co.uk ©2023 JQL

Licence (BSD-3-Clause Licence)

Copyright Notice

Internationalization (i18n) Menu code provided by JQL, https://visualaccounts.co.uk ©2023 JQL all rights reserved

Redistribution and use in source and binary forms with or without modification are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

Neither the names of John Lavelle, JQL, Visual Accounts nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

"ALL JQL CODE & SOFTWARE INCLUDING WORLD WIDE WEB PAGES (AND THOSE OF IT'S AUTHORS) ARE SUPPLIED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE AUTHOR AND PUBLISHER AND THEIR AGENTS SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. WITH RESPECT TO THE CODE, THE AUTHOR AND PUBLISHER AND THEIR AGENTS SHALL HAVE NO LIABILITY WITH RESPECT TO ANY LOSS OR DAMAGE DIRECTLY OR INDIRECTLY ARISING OUT OF THE USE OF THE CODE EVEN IF THE AUTHOR AND/OR PUBLISHER AND THEIR AGENTS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. WITHOUT LIMITING THE FOREGOING, THE AUTHOR AND PUBLISHER AND THEIR AGENTS SHALL NOT BE LIABLE FOR ANY LOSS OF PROFIT, INTERRUPTION OF BUSINESS, DAMAGE TO EQUIPMENT OR DATA, INTERRUPTION OF OPERATIONS OR ANY OTHER COMMERCIAL DAMAGE, INCLUDING BUT NOT LIMITED TO DIRECT, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL OR OTHER DAMAGES."

]]>
0
[wiki] How to Create and Use Validator Using Regular expressions Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/wiki/2575/how-to-create-and-use-validator-using-regular-expressions https://www.yiiframework.com/wiki/2575/how-to-create-and-use-validator-using-regular-expressions aayushmhu aayushmhu

There are Multiple Ways to Create a Validator But here we use Regular Expression or JavaScript Regular Expression or RegExp for Creation Validators. In this article, we will see the most Frequently Used Expression

Step 1 : Create a New Class for Validator like below or Validator

See First Example 10 Digit Mobile Number Validation

<?php

namespace common\validators;

use yii\validators\Validator;

class MobileValidator extends Validator {

    public function validateAttribute($model, $attribute) {
        if (isset($model->$attribute) and $model->$attribute != '') {
             if (!preg_match('/^[123456789]\d{9}$/', $model->$attribute)) {
                $this->addError($model, $attribute, 'In Valid Mobile / Phone number');
            }
        }
    }

}

Here We can Writee Diffrent Diffrent Regular Expression as Per Requirement `php preg_match('/^[123456789]\d{9}$/', $model->$attribute) `

Step 2: How tO Use Validator

I Hope Everyone Know How to use a validator but here is a example how to use it.

Add a New Rule in your Model Class Like this `php [['mobile'],\common\validators\MobileValidator::class], [['mobile'], 'string', 'max' => 10],


So It's Very Simple to use a Custom Validator.


As I Told you Earlier that i show you some more Example for Using Regular Expression  Validator Just Replace these string in preg_match.

1. Aadhar Number Validator
```php
preg_match('/^[2-9]{1}[0-9]{3}[0-9]{4}[0-9]{4}$/', $model->$attribute)
  1. Bank Account Number Validator `php preg_match("/^[0-9]{9,18}+$/", $model->$attribute) `

  2. Bank IFSC Code Validator `php preg_match("/^[A-Z]{4}0[A-Z0-9]{6}$/", $model->$attribute) `

  3. Pan Card Number Validator `php preg_match('/^([a-zA-Z]){5}([0-9]){4}([a-zA-Z]){1}?$/', $model->$attribute) `

  4. Pin Code Validator `php preg_match('/^[0-9]{6}+$/', $model->$attribute) `

  5. GSTIN Validator `php preg_match("/^([0][1-9]|[1-2][0-9]|[3][0-5])([a-zA-Z]{5}[0-9]{4}[a-zA-Z]{1}[1-9a-zA-Z]{1}[zZ]{1}[0-9a-zA-Z]{1})+$/", $model->$attribute) `

This is Other Type of Custom Validator

  1. 500 Word Validator for a String
<?php

namespace common\validators;

use yii\validators\Validator;

/**
 * Class Word500Validator
 * @author Aayush Saini <aayushsaini9999@gmail.com>
 */
class Word500Validator extends Validator
{

    public function validateAttribute($model, $attribute)
    {
        if ($model->$attribute != '') {
            if (str_word_count($model->$attribute) > 500) {
                $this->addError($model, $attribute, $model->getAttributeLabel($attribute) . ' length can not exceeded 500 words.');
                \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
                return $model->errors;
            }
        }
    }
}

Now I assume that after reading this article you can create any type of validator as per your Requirement.

:) Thanks for Reading

]]>
0
[wiki] GridView show sum of columns in footer. Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/wiki/2574/gridview-show-sum-of-columns-in-footer https://www.yiiframework.com/wiki/2574/gridview-show-sum-of-columns-in-footer shivam4u shivam4u

GridView show sum of columns in footer `PHP use yii\grid\DataColumn;

/**

  • Sum of all the values in the column
  • @author shiv / class TSumColumn extends DataColumn { public function getDataCellValue($model, $key, $index) {

     $value = parent::getDataCellValue($model, $key, $index);
     if ( is_numeric($value))
     {
         $this->footer += $value;
     }
        
     return $value;
    

    } } `

Now you have to enable footer in GridView

echo GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'showFooter' => true,

Also change the coulmn class

            [
                'class' => TSumColumn::class,
                'attribute' => 'amount'
            ],

You would see the total in footer of the grid. you can apply this to multiple columns if need

]]>
0
[wiki] Convert JSON data to html table for display on page Tue, 24 Dec 2024 21:24:53 +0000 https://www.yiiframework.com/wiki/2573/convert-json-data-to-html-table-for-display-on-page https://www.yiiframework.com/wiki/2573/convert-json-data-to-html-table-for-display-on-page shivam4u shivam4u

I have a calls which help me display json directly in html table.

Json2Table::formatContent($json);

The code of Json2Table class:

/**
 * Class convert Json to html table. It help view json data directly.
 * @author shiv
 *
 */
class Json2Table
{

    public static function formatContent($content, $class = 'table table-bordered')
    {
        $html = "";
        if ($content != null) {
            $arr = json_decode(strip_tags($content), true);
            
            if ($arr && is_array($arr)) {
                $html .= self::arrayToHtmlTableRecursive($arr, $class);
            }
        }
        return $html;
    }

    public static function arrayToHtmlTableRecursive($arr, $class = 'table table-bordered')
    {
        $str = "<table class='$class'><tbody>";
        foreach ($arr as $key => $val) {
            $str .= "<tr>";
            $str .= "<td>$key</td>";
            $str .= "<td>";
            if (is_array($val)) {
                if (! empty($val)) {
                    $str .= self::arrayToHtmlTableRecursive($val, $class);
                }
            } else {
                $val = nl2br($val);
                $str .= "<strong>$val</strong>";
            }
            $str .= "</td></tr>";
        }
        $str .= "</tbody></table>";
        
        return $str;
    }
}
]]>
0
[wiki] Aadhar Number Validator Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/wiki/2572/aadhar-number-validator https://www.yiiframework.com/wiki/2572/aadhar-number-validator shivam4u shivam4u

In India have Aadhar number an we may need to valid it a input. So I created a validator for yii2

use yii\validators\Validator;

class TAadharNumberValidator extends Validator
{

    public $regExPattern = '/^\d{4}\s\d{4}\s\d{4}$/';

    public function validateAttribute($model, $attribute)
    {
        if (preg_match($this->regExPattern, $model->$attribute)) {
            $model->addError($attribute, 'Not valid Aadhar Card Number');
        }
    }
}
]]>
0
[wiki] Interview Questions For YII2 Sun, 26 Jan 2025 13:41:22 +0000 https://www.yiiframework.com/wiki/2570/interview-questions-for-yii2 https://www.yiiframework.com/wiki/2570/interview-questions-for-yii2 aayushmhu aayushmhu

Hey Everyone, In this post I Just shared my Experience what most of interviewer ask in YII2 Interview.

  1. What is Active Record? and How we use that?
  2. What is Components ?
  3. What is Helpers Functions?
  4. How to Update Data Model?
  5. Diffrence Between Authentication and Authorization ?
  6. How to Speed Up a Website?
  7. What is GII? or do you Use GII Module?
  8. What is diffrence between YII and YII2?
  9. How to Use Multiple Databases?
  10. How to Intergate a theme into Website?
  11. What is OOPS?
  12. What is final class in php?
  13. What is abstract class?
  14. What is inheritance?
  15. What is Interface?
  16. Do you have knowledege of Javascript and Jquery?
  17. What is trait?
  18. What is Bootstrapping?
  19. What is Diffrence Between advanced and basic of YII2?
  20. How to use YII2 as a Micro framework?
  21. What is REST APIs?, How to write in YII2?
  22. Directory Structure of YII2 Project?
  23. Diffrence Between render, renderFile, renderPartial, renderAjax, renderContent?

These are most common question a interviewer can be asked to you if you are going to a Interview.

If anyone have other question please share in comments!!!!

Searching the Answers of these Question Find on Dynamic Duniya

]]>
0
[wiki] How to send email via Gmail SMTP in Yii2 framework Wed, 04 Aug 2021 13:00:37 +0000 https://www.yiiframework.com/wiki/2569/how-to-send-email-via-gmail-smtp-in-yii2-framework https://www.yiiframework.com/wiki/2569/how-to-send-email-via-gmail-smtp-in-yii2-framework PELock PELock
  1. Gmail won't unblock your domain... thanks Google
  2. How to send emails to @gmail.com boxes anyway?
  3. 1. Setup a helper @gmail.com account
  4. 2. Add custom component in your configuration file
  5. 3. Add helper function
  6. 4. Usage
  7. 5. Know the limits
  8. 6. Gmail is not your friend

One of my sites has been flooded with spam bots and as a result - Gmail gave my mailing domain a bad score and I couldn't send emails to @gmail addresses anymore, not from my email, not from my system, not from any of other domains and websites I host...

Gmail won't unblock your domain... thanks Google

I did remove all the spambots activity from one of my sites, appealed the decision via Gmail support forums, but still, I'm blocked from contacting my customers that has mailboxes at @gmail.com and there seems to be no way to change the domain score back to where it was.

It's been almost 2 weeks and my domain score is stuck at bad in https://postmaster.google.com/

Thanks @Google :(

How to send emails to @gmail.com boxes anyway?

As a result, I had to figure way out to send purchases, expired licenses, and other notifications to my customers.

I'm using PHP Yii2 framework and it turns out it was a breeze.

1. Setup a helper @gmail.com account

We need a @gmail.com account to send the notifications. One thing is important. After you create the account, you need to enable Less Secure Apps Access option:

Gmail options

It allows us to send emails via Gmail SMTP server.

2. Add custom component in your configuration file

In your Yii2 framework directory, modify your configuration file /common/config/Main.php (I'm using Advanced Theme) and include custom mailing component (name it however you want):

<?php
return [
	'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',

	...

	'components' => [

		'mailerGmail' => [
			'class' => 'yii\swiftmailer\Mailer',
			'viewPath' => '@common/mail',
			'useFileTransport' => false,

			'transport' => [
				'class' => 'Swift_SmtpTransport',
				'host' => 'smtp.gmail.com',
				'username' => 'gmail.helper.account',
				'password' => 'PUT-YOUR-PASSWORD-HERE',
				'port' => '587',
				'encryption' => 'tls',
			],
		],
    ],
];

3. Add helper function

I have added a helper function to one of my components registered as Yii::$app->Custom. It returns default mailer instance depending on the delivery email domain name.

I have also updated the code to detect the cases where the email doesn't contain @gmail.com string in it but still is using Gmail MX servers to handle emailing.

Detection is based on checking domain mailing server records using PHP built-in function getmxrr() and if that fails I send remote GET query to Google DNS service API to check the MX records.

////////////////////////////////////////////////////////////////////////////////
//
// get default mailer depending on the provided email address
//
////////////////////////////////////////////////////////////////////////////////

public function getMailer($email)
{
	// detect if the email or domain is using Gmail to send emails
	if (Yii::$app->params['forwardGmail'])
	{
		// detect @gmail.com domain first
		if (str_ends_with($email, "@gmail.com"))
		{
			return Yii::$app->mailerGmail;
		}

		// extract domain name
		$parts = explode('@', $email);
		$domain = array_pop($parts);

		// check DNS using local server requests to DNS
		// if it fails query Google DNS service API (might have limits)
		if (getmxrr($domain, $mx_records))
		{
			foreach($mx_records as $record)
			{
				if (stripos($record, "google.com") !== false || stripos($record, "googlemail.com") !== false)
				{
					return Yii::$app->mailerGmail;
				}
			}

			// return default mailer (if there were records detected but NOT google)
			return Yii::$app->mailer;
		}

		// make DNS request
		$client = new Client();

		$response = $client->createRequest()
			->setMethod('GET')
			->setUrl('https://dns.google.com/resolve')
			->setData(['name' => $domain, 'type' => 'MX'])
			->setOptions([
				'timeout' => 5, // set timeout to 5 seconds for the case server is not responding
			])
			->send();

		if ($response->isOk)
		{
			$parser = new JsonParser();

			$data = $parser->parse($response);

			if ($data && array_key_exists("Answer", $data))
			{
				foreach ($data["Answer"] as $key => $value)
				{
					if (array_key_exists("name", $value) && array_key_exists("data", $value))
					{
						if (stripos($value["name"], $domain) !== false)
						{
							if (stripos($value["data"], "google.com") !== false || stripos($value["data"], "googlemail.com") !== false)
							{
								return Yii::$app->mailerGmail;
							}
						}
					}
				}
			}
		}
	}

	// return default mailer
	return Yii::$app->mailer;
}

If the domain ends with @gmail.com or the domain is using Gmail mailing systems the mailerGmail instance is used, otherwise the default mailing component Yii::$app->mailer is used.

4. Usage

    /**
     * Sends an email to the specified email address using the information collected by this model.
     *
     * @return boolean whether the email was sent
     */
    public function sendEmail()
    {
		// find all active subscribers
		$message = Yii::$app->Custom->getMailer($this->email)->compose();
	
		$message->setTo([$this->email => $this->name]);
		$message->setFrom([\Yii::$app->params['supportEmail'] => "Bartosz Wójcik"]);
		$message->setSubject($this->subject);
		$message->setTextBody($this->body);
	
		$headers = $message->getSwiftMessage()->getHeaders();
	
		// message ID header (hide admin panel)
		$msgId = $headers->get('Message-ID');
		$msgId->setId(md5(time()) . '@pelock.com');
	
		$result = $message->send();
	
		return $result;
    }

5. Know the limits

This is only the temporary solution and you need to be aware you won't be able to send bulk mail with this method, Gmail enforces some limitations on fresh mailboxes too.

6. Gmail is not your friend

It seems if your domain lands on that bad reputation scale there isn't any easy way out of it. I read on Gmail support forums, some people wait for more than a month for Gmail to unlock their domains without any result and communication back. My domain is not listed in any other blocked RBL lists (spam lists), it's only Gmail blocking it, but it's enough to understand how influential Google is, it can ruin your business in a second without a chance to fix it...

]]>
0
[wiki] JWT authentication tutorial Sun, 03 Oct 2021 17:59:49 +0000 https://www.yiiframework.com/wiki/2568/jwt-authentication-tutorial https://www.yiiframework.com/wiki/2568/jwt-authentication-tutorial allanbj allanbj

How to implement JWT

  1. The JWT Concept
  2. Scenarios
  3. User logs in for the first time, via the /auth/login endpoint:
  4. Token expired:
  5. My laptop got stolen:
  6. Why do we trust the JWT blindly?
  7. Implementation Steps
  8. Prerequisites
  9. Step-by-step setup
  10. Client-side examples

The JWT Concept

JWT is short for JSON Web Token. It is used eg. instead of sessions to maintain a login in a browser that is talking to an API - since browser sessions are vulnerable to CSRF security issues. JWT is also less complicated than setting up an OAuth authentication mechanism.

The concept relies on two tokens:

  • AccessToken - a short-lived JWT (eg. 5 minutes)

This token is generated using \sizeg\jwt\Jwt::class It is not stored server side, and is sent on all subsequent API requests through the Authorization header How is the user identified then? Well, the JWT contents contain the user ID. We trust this value blindly.

  • RefreshToken - a long-lived, stored in database

This token is generated upon login only, and is stored in the table user_refresh_token. A user may have several RefreshToken in the database.

Scenarios

User logs in for the first time, via the /auth/login endpoint:

In our actionLogin() method two things happens, if the credentials are correct:

  • The JWT AccessToken is generated and sent back through JSON. It is not stored anywhere server-side, and contains the user ID (encoded).
  • The RefreshToken is generated and stored in the database. It's not sent back as JSON, but rather as a httpOnly cookie, restricted to the /auth/refresh-token path.

The JWT is stored in the browser's localStorage, and have to be sent on all requests from now on. The RefreshToken is in your cookies, but can't be read/accessed/tempered with through Javascript (since it is httpOnly).

Token expired:

After some time, the JWT will eventually expire. Your API have to return 401 - Unauthorized in this case. In your app's HTTP client (eg. Axios), add an interceptor, which detects the 401 status, stores the failing request in a queue, and calls the /auth/refresh-token endpoint.

When called, this endpoint will receive the RefreshToken via the cookie. You then have to check in your table if this is a valid RefreshToken, who is the associated user ID, generate a new JWT and send it back as JSON.

Your HTTP client must take this new JWT, replace it in localStorage, and then cycle through the request queue and replay all failed requests.

My laptop got stolen:

If you set up an /auth/sessions endpoint, that returns all the current user's RefreshTokens, you can then display a table of all connected devices.

You can then allow the user to remove a row (i.e. DELETE a particular RefreshToken from the table). When the compromised token expires (after eg. 5 min) and the renewal is attempted, it will fail. This is why we want the JWT to be really short lived.

Why do we trust the JWT blindly?

This is by design the purpose of JWT. It is secure enough to be trustable. In big setups (eg. Google), the Authentication is handled by a separate authentication server. It's responsible for accepting a login/password in exchange for a token.

Later, in Gmail for example, no authentication is performed at all. Google reads your JWT and give you access to your email, provided your JWT is not dead. If it is, you're redirected to the authentication server.

This is why when Google authentication had a failure some time ago - some users were able to use Gmail without any problems, while others couldn't connect at all - JWT still valid versus an outdated JWT.

Implementation Steps

Prerequisites

  • Yii2 installed
  • An https enabled site is required for the HttpOnly cookie to work cross-site
  • A database table for storing RefreshTokens:
CREATE TABLE `user_refresh_tokens` (
	`user_refresh_tokenID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	`urf_userID` INT(10) UNSIGNED NOT NULL,
	`urf_token` VARCHAR(1000) NOT NULL,
	`urf_ip` VARCHAR(50) NOT NULL,
	`urf_user_agent` VARCHAR(1000) NOT NULL,
	`urf_created` DATETIME NOT NULL COMMENT 'UTC',
	PRIMARY KEY (`user_refresh_tokenID`)
)
COMMENT='For JWT authentication process';
  • Install package: composer require sizeg/yii2-jwt
  • For the routes login/logout/refresh etc we'll use a controller called AuthController.php. You can name it what you want.

Step-by-step setup

  • Create an ActiveRecord model for the table user_refresh_tokens. We'll use the class name app\models\UserRefreshToken.

  • Disable CSRF validation on all your controllers:

Add this property: public $enableCsrfValidation = false;

  • Add JWT parameters in /config/params.php:
'jwt' => [
	'issuer' => 'https://api.example.com',  //name of your project (for information only)
	'audience' => 'https://frontend.example.com',  //description of the audience, eg. the website using the authentication (for info only)
	'id' => 'UNIQUE-JWT-IDENTIFIER',  //a unique identifier for the JWT, typically a random string
	'expire' => 300,  //the short-lived JWT token is here set to expire after 5 min.
],
  • Add JwtValidationData class in /components which uses the parameters we just set:
<?php
namespace app\components;

use Yii;

class JwtValidationData extends \sizeg\jwt\JwtValidationData {
	/**
	 * @inheritdoc
	 */
	public function init() {
		$jwtParams = Yii::$app->params['jwt'];
		$this->validationData->setIssuer($jwtParams['issuer']);
		$this->validationData->setAudience($jwtParams['audience']);
		$this->validationData->setId($jwtParams['id']);

		parent::init();
	}
}
  • Add component in configuration in /config/web.php for initializing JWT authentication:
	$config = [
		'components' => [
			...
			'jwt' => [
				'class' => \sizeg\jwt\Jwt::class,
				'key' => 'SECRET-KEY',  //typically a long random string
				'jwtValidationData' => \app\components\JwtValidationData::class,
			],
			...
		],
	];
  • Add the authenticator behavior to your controllers
    • For AuthController.php we must exclude actions that do not require being authenticated, like login, refresh-token, options (when browser sends the cross-site OPTIONS request).
	public function behaviors() {
    	$behaviors = parent::behaviors();

		$behaviors['authenticator'] = [
			'class' => \sizeg\jwt\JwtHttpBearerAuth::class,
			'except' => [
				'login',
				'refresh-token',
				'options',
			],
		];

		return $behaviors;
	}
  • Add the methods generateJwt() and generateRefreshToken() to AuthController.php. We'll be using them in the login/refresh-token actions. Adjust class name for your user model if different.
	private function generateJwt(\app\models\User $user) {
		$jwt = Yii::$app->jwt;
		$signer = $jwt->getSigner('HS256');
		$key = $jwt->getKey();
		$time = time();

		$jwtParams = Yii::$app->params['jwt'];

		return $jwt->getBuilder()
			->issuedBy($jwtParams['issuer'])
			->permittedFor($jwtParams['audience'])
			->identifiedBy($jwtParams['id'], true)
			->issuedAt($time)
			->expiresAt($time + $jwtParams['expire'])
			->withClaim('uid', $user->userID)
			->getToken($signer, $key);
	}

	/**
	 * @throws yii\base\Exception
	 */
	private function generateRefreshToken(\app\models\User $user, \app\models\User $impersonator = null): \app\models\UserRefreshToken {
		$refreshToken = Yii::$app->security->generateRandomString(200);

		// TODO: Don't always regenerate - you could reuse existing one if user already has one with same IP and user agent
		$userRefreshToken = new \app\models\UserRefreshToken([
			'urf_userID' => $user->id,
			'urf_token' => $refreshToken,
			'urf_ip' => Yii::$app->request->userIP,
			'urf_user_agent' => Yii::$app->request->userAgent,
			'urf_created' => gmdate('Y-m-d H:i:s'),
		]);
		if (!$userRefreshToken->save()) {
			throw new \yii\web\ServerErrorHttpException('Failed to save the refresh token: '. $userRefreshToken->getErrorSummary(true));
		}

		// Send the refresh-token to the user in a HttpOnly cookie that Javascript can never read and that's limited by path
		Yii::$app->response->cookies->add(new \yii\web\Cookie([
			'name' => 'refresh-token',
			'value' => $refreshToken,
			'httpOnly' => true,
			'sameSite' => 'none',
			'secure' => true,
			'path' => '/v1/auth/refresh-token',  //endpoint URI for renewing the JWT token using this refresh-token, or deleting refresh-token
		]));

		return $userRefreshToken;
	}
  • Add the login action to AuthController.php:
	public function actionLogin() {
		$model = new \app\models\LoginForm();
		if ($model->load(Yii::$app->request->getBodyParams()) && $model->login()) {
			$user = Yii::$app->user->identity;

			$token = $this->generateJwt($user);

			$this->generateRefreshToken($user);

			return [
				'user' => $user,
				'token' => (string) $token,
			];
		} else {
			return $model->getFirstErrors();
		}
	}
  • Add the refresh-token action to AuthController.php. Call POST /auth/refresh-token when JWT has expired, and call DELETE /auth/refresh-token when user requests a logout (and then delete the JWT token from client's localStorage).
	public function actionRefreshToken() {
		$refreshToken = Yii::$app->request->cookies->getValue('refresh-token', false);
		if (!$refreshToken) {
			return new \yii\web\UnauthorizedHttpException('No refresh token found.');
		}

		$userRefreshToken = \app\models\UserRefreshToken::findOne(['urf_token' => $refreshToken]);

		if (Yii::$app->request->getMethod() == 'POST') {
			// Getting new JWT after it has expired
			if (!$userRefreshToken) {
				return new \yii\web\UnauthorizedHttpException('The refresh token no longer exists.');
			}

			$user = \app\models\User::find()  //adapt this to your needs
				->where(['userID' => $userRefreshToken->urf_userID])
				->andWhere(['not', ['usr_status' => 'inactive']])
				->one();
			if (!$user) {
				$userRefreshToken->delete();
				return new \yii\web\UnauthorizedHttpException('The user is inactive.');
			}

			$token = $this->generateJwt($user);

			return [
				'status' => 'ok',
				'token' => (string) $token,
			];

		} elseif (Yii::$app->request->getMethod() == 'DELETE') {
			// Logging out
			if ($userRefreshToken && !$userRefreshToken->delete()) {
				return new \yii\web\ServerErrorHttpException('Failed to delete the refresh token.');
			}

			return ['status' => 'ok'];
		} else {
			return new \yii\web\UnauthorizedHttpException('The user is inactive.');
		}
	}
  • Adapt findIdentityByAccessToken() in your user model to find the authenticated user via the uid claim from the JWT:
	public static function findIdentityByAccessToken($token, $type = null) {
		return static::find()
			->where(['userID' => (string) $token->getClaim('uid') ])
			->andWhere(['<>', 'usr_status', 'inactive'])  //adapt this to your needs
			->one();
	}
  • Also remember to purge all RefreshTokens for the user when the password is changed, eg. in afterSave() in your user model:
	public function afterSave($isInsert, $changedOldAttributes) {
		// Purge the user tokens when the password is changed
		if (array_key_exists('usr_password', $changedOldAttributes)) {
			\app\models\UserRefreshToken::deleteAll(['urf_userID' => $this->userID]);
		}

		return parent::afterSave($isInsert, $changedOldAttributes);
	}
  • Make a page where user can delete his RefreshTokens. List the records from user_refresh_tokens that belongs to the given user and allow him to delete the ones he chooses.

Client-side examples

The Axios interceptor (using React Redux???):


let isRefreshing = false;
let refreshSubscribers: QueuedApiCall[] = [];
const subscribeTokenRefresh = (cb: QueuedApiCall) =>
  refreshSubscribers.push(cb);

const onRefreshed = (token: string) => {
  console.log("refreshing ", refreshSubscribers.length, " subscribers");
  refreshSubscribers.map(cb => cb(token));
  refreshSubscribers = [];
};

api.interceptors.response.use(undefined,
  error => {
    const status = error.response ? error.response.status : false;
    const originalRequest = error.config;

    if (error.config.url === '/auth/refresh-token') {
      console.log('REDIRECT TO LOGIN');
      store.dispatch("logout").then(() => {
          isRefreshing = false;
      });
    }

    if (status === API_STATUS_UNAUTHORIZED) {


      if (!isRefreshing) {
        isRefreshing = true;
        console.log('dispatching refresh');
        store.dispatch("refreshToken").then(newToken => {
          isRefreshing = false;
          onRefreshed(newToken);
        }).catch(() => {
          isRefreshing = false;
        });
      }

      return new Promise(resolve => {
        subscribeTokenRefresh(token => {
          // replace the expired token and retry
          originalRequest.headers["Authorization"] = "Bearer " + token;
          resolve(axios(originalRequest));
        });
      });
    }
    return Promise.reject(error);


  }
);

Thanks to Mehdi Achour for helping with much of the material for this tutorial.

]]>
0
[wiki] Yii v2 snippet guide III Mon, 17 Jul 2023 11:37:41 +0000 https://www.yiiframework.com/wiki/2567/yii-v2-snippet-guide-iii https://www.yiiframework.com/wiki/2567/yii-v2-snippet-guide-iii rackycz rackycz
  1. My articles
  2. Switching languages and Language in URL
  3. Search and replace
  4. Virtualization - Vagrant and Docker - why and how
  5. Running Yii project in Vagrant. (Simplified version)
  6. Running Yii project in Docker (Update: xDebug added below!)
  7. Enabling xDebug in Docker, yii demo application
  8. Docker - Custom php.ini
  9. How to enter Docker's bash (cli, command line)
  10. AdminLTE - overview & general research on the theme
  11. Creating custom Widget
  12. Tests - unit + functional + acceptance (opa) + coverage
  13. Microsoft Access MDB
  14. Migration batch insert csv

My articles

Articles are separated into more files as there is the max lenght for each file on wiki.

Switching languages and Language in URL

I already wrote how translations work. Here I will show how language can be switched and saved into the URL. So let's add the language switcher into the main menu:

echo Nav::widget([
 'options' => ['class' => 'navbar-nav navbar-right'],
 'items' => [
  ['label' => 'Language', 'items' => [
    ['label' => 'German' , 'url' => \yii\helpers\Url::current(['sys_lang' => 'de']) ],
    ['label' => 'English', 'url' => \yii\helpers\Url::current(['sys_lang' => 'en']) ],
   ],
  ]

Now we need to process the new GET parameter "sys_lang" and save it to Session in order to keep the new language. Best is to create a BaseController which will be extended by all controllers. Its content looks like this:

<?php
namespace app\controllers;
use yii\web\Controller;
class _BaseController extends Controller {
  public function beforeAction($action) {
    if (isset($_GET['sys_lang'])) {
      switch ($_GET['sys_lang']) {
        case 'de':
          $_SESSION['sys_lang'] = 'de-DE';
          break;
        case 'en':
          $_SESSION['sys_lang'] = 'en-US';
          break;
      }
    }
    if (!isset($_SESSION['sys_lang'])) {
      $_SESSION['sys_lang'] = \Yii::$app->sourceLanguage;
    }
    \Yii::$app->language = $_SESSION['sys_lang'];
    return true;
  }
}

If you want to have the sys_lang in the URL, right behind the domain name, following URL rules can be created in config/web.php:

'components' => [
 // ...
 'urlManager' => [
  'enablePrettyUrl' => true,
  'showScriptName' => false,
  'rules' => [
   // https://www.yiiframework.com/doc/api/2.0/yii-web-urlmanager#$rules-detail
   // https://stackoverflow.com/questions/2574181/yii-urlmanager-language-in-url
   // https://www.yiiframework.com/wiki/294/seo-conform-multilingual-urls-language-selector-widget-i18n
   '<sys_lang:[a-z]{2}>' => 'site',
   '<sys_lang:[a-z]{2}>/<controller:\w+>' => '<controller>',
   '<sys_lang:[a-z]{2}>/<controller:\w+>/<action:\w+>' => '<controller>/<action>',
  ],
 ],
],

Now the language-switching links will produce URL like this: http://myweb.com/en/site/index . Without the rules the link would look like this: http://myweb.com/site/index?sys_lang=en . So the rule works in both directions. When URL is parsed and controllers are called, but also when a new URL is created using the URL helper.

Search and replace

I am using Notepad++ for massive changes using Regex. If you press Ctrl+Shift+F you will be able to replace in all files.

Yii::t()

Yii::t('text'  ,  'text'   ) // NO
Yii::t('text','text') // YES

search: Yii::t\('([^']*)'[^']*'([^']*)'[^\)]*\)
replace with: Yii::t\('$1','$2'\)

URLs (in Notepad++)

return $this->redirect('/controller/action')->send(); // NO
return $this->redirect(['controller/action'])->send(); // YES

search: ->redirect\(['][/]([^']*)[']\)
replace: ->redirect\(['$1']\)

====

return $this->redirect('controller/action')->send(); // NO
return $this->redirect(['controller/action'])->send(); // YES

search: ->redirect\((['][^']*['])\)
replace: ->redirect\([$1]\)

PHP short tags

search: (<\?)([^p=]) // <?if ...
replace: $1php $2 // <?php if ...
// note that sometimes <?xml can be found and it is valid, keep it

View usage

search: render(Ajax|Partial)?\s*\(\s*['"]\s*[a-z0-9_\/]*(viewName)

Virtualization - Vagrant and Docker - why and how

Both Vagrant and Docker create a virtual machine using almost any OS or SW configuration you specify, while the source codes are on your local disk so you can easily modify them in your IDE under your OS.

Can be used not only for PHP development, but in any other situation.

What is this good for? ... Your production server runs a particular environment and you want to develop/test on the same system. Plus you dont have to install XAMPP, LAMP or other servers locally. You just start the virtual and its ready. Plus you can share the configuration of the virtual system with other colleagues so you all work on indentical environment. You can also run locally many different OS systems with different PHP versions etc.

Vagrant and Docker work just like composer or NPM. It is a library of available OS images and other SW and you just pick some combination. Whole configuration is defined in one text-file, named Vagrantfile or docker-compose.yml, and all you need is just a few commands to run it. And debugging is no problem.

Running Yii project in Vagrant. (Simplified version)

Info: This chapter works with PHP 7.0 in ScotchBox. If you need PHP 7.4, read next chapter where CognacBox is used (to be added when tested)

Basic overview and Vagrant configuration:

List of all available OS images for Vagrant is here:

Both Yii demo-applications already contain the Vagrantfile, but its setup is unclear to me - it is too PRO. So I wanted to publish my simplified version which uses OS image named scotch/box and you can use it also for non-yii PHP projects. (It has some advantages, the disadvantage is older PHP in the free version)

The Vagrantfile is stored in the root-folder of your demo-project. My Vagrantfile contains only following commands.

Vagrant.configure("2") do |config|
    config.vm.box = "scotch/box"
    config.vm.network "private_network", ip: "11.22.33.44"
    config.vm.hostname = "scotchbox"
    config.vm.synced_folder ".", "/var/www/public", :mount_options => ["dmode=777", "fmode=777"]
    config.vm.provision "shell", path: "./vagrant/vagrant.sh", privileged: false
end

# Virtual machine will be available on IP A.B.C.D (in our case 11.22.33.44, see above)
# Virtual can access your host machine on IP A.B.C.1 (this rule is given by Vagrant)

It requires file vagrant/vagrant.sh, because I wanted to enhance the server a bit. It contains following:


# Composer:
# (In case of composer errors, it can help to delete the vendor-folder and composer.lock file)
cd /var/www/public/
composer install

# You can automatically import your SQL (root/root, dbname scotchbox)
#mysql -u root -proot scotchbox < /var/www/public/vagrant/db.sql

# You can run migrations:
#php /var/www/public/protected/yiic.php migrate --interactive=0

# You can create folder and set 777 rights:
#mkdir /var/www/public/assets
#sudo chmod -R 777 /var/www/public/assets

# You can copy a file:
#cp /var/www/public/from.php /var/www/public/to.php

# Installing Xdebug v2 (Xdebug v3 has renamed config params!):
sudo apt-get update
sudo apt-get install php-xdebug

# Configuring Xdebug in php.ini:
# If things do not work, disable your firewall and restart IDE. It might help.
echo "" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "[XDebug]" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_enable=1" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_port=9000" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_autostart=1" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_log=/var/www/public/xdebug.log" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.remote_connect_back=1" | sudo tee -a /etc/php/7.0/apache2/php.ini
echo "xdebug.idekey=netbeans-xdebug" | sudo tee -a /etc/php/7.0/apache2/php.ini

# Important: Make sure that your IDE has identical settings: idekey and remote_port.
# NetBeans: Make sure your project is correctly setup. Right-click the project and select Properties / Run Cofigurations. "Project URL" and "Index file" must have correct values.

# Note:
# Use this if remote_connect_back does not work. 
# IP must correspond to the Vagrantfile, only the last number must be 1
#echo "xdebug.remote_handler=dbgp" | sudo tee -a /etc/php/7.0/apache2/php.ini
#echo "xdebug.remote_host=11.22.33.1" | sudo tee -a /etc/php/7.0/apache2/php.ini 

sudo service apache2 restart

... so create both files in your project ...

If you want to manually open php.ini and paste this text, you can copy it from here:

// sudo nano /etc/php/7.0/apache2/php.ini
// (Xdebug v3 has renamed config params!)

[XDebug]
xdebug.remote_enable=1
xdebug.remote_port=9000
xdebug.remote_autostart=1
xdebug.remote_log=/var/www/public/xdebug.log
xdebug.remote_connect_back=1
xdebug.idekey=netbeans-xdebug

// Important: Make sure that your IDE has identical settings: idekey and remote_port.
// NetBeans: Make sure your project is correctly setup. Right-click the project and select Properties / Run Cofigurations. "Project URL" and "Index file" must have correct values.

To debug in PhpStorm check this video.

To connect to MySQL via PhpStorm check this comment by MilanG

Installing and using Vagrant:

First install Vagrant and VirtualBox, please.

Note: Sadly, these days VirtualBox does not work on the ARM-based Macs with the M1 chip. Use Docker in that case.

Important: If command "vagrant ssh" wants a password, enter "vagrant".

Now just open your command line, navigate to your project and you can start:

  • "vagrant -v" should show you the version if things work.
  • "vagrant init" creates a new project (You won't need it now)
  • "vagrant up" runs the Vagrantfile and creates/starts the virtual

Once virtual is running, you can call also these:

  • "vagrant ssh" opens Linux shell - use password "vagrant" is you are prompted.
  • "vagrant halt" stops the virtual
  • "vagrant reload" restarts the virtual and does NOT run config.vm.provision OR STARTS EXISTING VAGRANT VIRTUAL - you do not have to call "vagrant up" whenever you reboot your PC
  • "vagrant reload --provision" restarts the virtual and runs config.vm.provision

In the Linux shell you can call any command you want.

  • To find what Linux version is installed: "cat /etc/os-release" or "lsb_release -a" or "hostnamectl"
  • To get PHP version call: "php -version"
  • If you are not allowed to run "mysql -v", you can run "mysql -u {username} -p" .. if you know the login
  • Current IP: hostname -I

In "scotch/box" I do not use PhpMyAdmin , but Adminer. It is one simple PHP script and it will run without any installations. Just copy the adminer.php script to your docroot and access it via browser. Use the same login as in configurafion of Yii. Server will be localhost.

Running Yii project in Docker (Update: xDebug added below!)

Note: I am showing the advanced application. Basic application will not be too different I think. Great Docker tutorial is here

Yii projects are already prepared for Docker. To start you only have to install Docker from www.docker.com and you can go on with this manual.

  • Download the application template and extract it to any folder
  • Open command line and navigate to the project folder
  • Run command docker-compose up -d
    • Argument -d will run docker on the background as a service
    • Advantage is that command line will not be blocked - you will be able to call more commands
  • Run command init to initialize the application
  • You can also call composer install using one of following commands:
    • docker-compose run --rm frontend composer install
    • docker-compose run --rm backend composer install

Note: init and composer can be called locally, not necessarily via Docker. They only add files to your folder.

Now you will be able to open URLs:

Open common/config/main-local.php and set following DB connection:

  • host=mysql !!
  • dbname=yii2advanced
  • username=yii2advanced
  • password=secret
  • Values are taken from docker-compose.yml

Run migrations using one of following commands:

  • docker-compose run --rm frontend php yii migrate
  • docker-compose run --rm backend php yii migrate

Now go to Frontend and click "signup" in the right upper corner

Second way is to directly modify table in DB:

Now you have your account and you can log in to Backend

Enabling xDebug in Docker, yii demo application

Just add section environment to docker-compose.yml like this:

services:

  frontend:
    build: frontend
    ports:
      - 20080:80
    volumes:
      # Re-use local composer cache via host-volume
      - ~/.composer-docker/cache:/root/.composer/cache:delegated
      # Mount source-code for development
      - ./:/app
    environment:
      PHP_ENABLE_XDEBUG: 1
      XDEBUG_CONFIG: "client_port=9000 start_with_request=yes idekey=netbeans-xdebug log_level=1 log=/app/xdebug.log discover_client_host=1"
      XDEBUG_MODE: "develop,debug"

This will allow you to see nicely formatted var_dump values and to debug your application in your IDE.

Note: You can/must specify the idekey and client_port based on your IDE settings. Plus your Yii project must be well configured in the IDE as well. In NetBeans make sure that "Project URL" and "index file" are correct in "Properties/Run Configuration" (right click the project)

Note 2: Please keep in mind that xDebug2 and xDebug3 have different settings. Details here.

I spent on this approximately 8 hours. Hopefully someone will enjoy it :-) Sadly, this configuration is not present in docker-compose.yml. It would be soooo handy.

Docker - Custom php.ini

Add into section "volumes" this line:

- ./myphp.ini:/usr/local/etc/php/conf.d/custom.ini

And create file myphp.ini the root of your Yii application. You can enter for example html_errors=on and html_errors=off to test if the file is loaded. Restart docker and check results using method phpinfo() in a PHP file.

How to enter Docker's bash (cli, command line)

Navigate in command line to the folder of your docker-project and run command:

  • docker ps
  • This will list all services you defined in docker-compose.yml

The last column of the list is NAMES. Pick one and copy its name. Then run command:

  • docker exec -it {NAME} /bin/bash
  • ... where {NAME} is your service name. For example:
  • docker exec -it yii-advanced_backend_1 /bin/bash

To findout what Linux is used, you can call cat /etc/os-release. (or check the Vagrant chapter for other commands)

If you want to locate the php.ini, type php --ini. Once you find it you can copy it to your yii-folder like this:

cp path/to/php.ini /app/myphp.ini

AdminLTE - overview & general research on the theme

AdminLTE is one of available admin themes. It currently has 2 versions:

  • AdminLTE v2 = based on Bootstrap 3 = great for Yii v2 application
  • AdminLTE v3 = based on Bootstrap 4 (it is easy to upgrade Yii2 from Bootstrap3 to Bootstrap4 *)

* Upgrading Yii2 from Bootstrap3 to Bootstrap4: https://www.youtube.com/watch?v=W1xxvngjep8

Documentation for AdminLTE <= 2.3, v2.4, v3.0 Note that some AdminLTE functionalities are only 3rd party dependencies. For example the map.

There are also many other admin themes:

There are also more Yii2 extensions for integration of AdminLTE into Yii project:

I picked AdminLTE v2 (because it uses the same Bootstrap as Yii2 demos) and I tested some extensions which should help with implementation.

But lets start with quick info about how to use AdminLTE v2 without extensions in Yii2 demo application.

Manual integration of v2.4 - Asset File creation

  • Open documentation and run composer or download all dependencies in ZIP.
  • Open preview page and copy whole HTML code to your text editor.
  • Delete those parts of BODY section which you do not need (at least the content of: section class="content")
  • Also delete all SCRIPT and LINK tags. We will add them using the AssetBundle later.

  • Open existing file views/layouts/main.php and copy important PHP calls to the new file. (Asset, beginPage, $content, Breadcrumbs etc)
  • Now your layout is complete, you can replace the original layout file.

We only need to create the Asset file to link all SCRIPTs and LINKs:

  • Copy file assets/AppAsset into assets/LteAsset and rename the class inside.
  • Copy all LINK- and SCRIPT- URLs to LteAsset.
  • Skip jQuery and Bootstrap, they are part of Yii. Example:
namespace app\assets;
use yii\web\AssetBundle;
class LteAsset extends AssetBundle
{
    public $sourcePath = '@vendor/almasaeed2010/adminlte/';
    public $jsOptions = ['position' => \yii\web\View::POS_HEAD];  // POS_END cause conflict with YiiAsset  
    public $css = [
        'bower_components/font-awesome/css/font-awesome.min.css',
        'https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic',
        // etc
    ];
    public $js = [
        'bower_components/jquery-ui/jquery-ui.min.js',
        // etc
    ];
    public $depends = [
        'yii\web\YiiAsset',
        'yii\bootstrap\BootstrapAsset',
    ];
}
  • Refresh your Yii page and check "developer tools" for network errors. Fix them.

This error can appear: "Headers already sent"

  • It means you forgot to copy some PHP code from the old layout file to the new one.

Now you are done, you can start using HTML and JS stuff from AdminLTE. So lets check extensions which will do it for us

Insolita extension

Works good for many UI items: Boxes, Tile, Callout, Alerts and Chatbox. You only have to prepare the main layout file and Asset bundle, see above. It hasn't been updated since 2018.

Check its web for my comment. I showed how to use many widgets.

Imperfections in the sources:

vendor\insolita\yii2-adminlte-widgets\LteConst.php

  • There is a typo: COLOR_LIGHT_BLUE should be 'lightblue', not 'light-blue'

vendor\insolita\yii2-adminlte-widgets\CollapseBox.php

  • Class in $collapseButtonTemplate should be "btn btn-box-tool", not "btn {btnType} btn-xs"
  • (it affects the expand/collapse button in expandable boxes)
  • $collapseButtonTemplate must be modified in order to enable removing Boxes from the screen. Namely data-widget and iconClass must be changed in method prepareBoxTools()

LteBox

  • Boxes can be hidden behind the "waiting icon" overlay. This is done using following HTML at the end of the box's div:
    <div class="overlay"><i class="fa fa-refresh fa-spin"></i></div>
    
  • This must be added manually or by modifying LteBox

Yiister

Its web explains everything. Very usefull: http://adminlte.yiister.ru You only need the Asset File from this article and then install Yiister. Sadly it hasn't been updated since 2015. Provides widgets for rendering Menu, GridView, Few boxes, Fleshalerts and Callouts. Plus Error page.

dmstr/yii2-adminlte-asset

Officially mentioned on AdminLTE web. Renders only Menu and Alert. Provides mainly the Asset file and Gii templates. Gii templates automatically fix the GridView design, but you can find below how to do it manually.

Other enhancements

AdminLTE is using font Source Sans Pro. If you want a different one, pick it on Google Fonts and modify the layout file like this:

<link href="https://fonts.googleapis.com/css2?family=Palanquin+Dark:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
 body {
    font-family: 'Palanquin Dark', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  } 
  
  h1,h2,h3,h4,h5,h6,
  .h1,.h2,.h3,.h4,.h5,.h6 {
    font-family: 'Palanquin Dark', sans-serif;
  }
</style>

To display GridView as it should be, wrap it in this HTML code:

<div class="box box-primary">
  <div class="box-header">
    <h3 class="box-title"><i class="fa fa-table"></i>&nbsp;Grid caption</h3>
  </div>
  <div class="box-body"

  ... grid view ...

  </div>
</div>

You can also change the glyphicon in web/css/site.css:

a.asc:after {
    content: "\e155";
}

a.desc:after {
    content: "\e156";
}

And this is basically it. Now we know how to use AdminLTE and fix the GridView. At least one extension will be needed to render widgets, see above.

Creating custom Widget

See official reading about Widgets or this explanation. I am presenting this example, but I added 3 rows. Both types of Widgets can be coded like this:

namespace app\components;
use yii\base\Widget;
use yii\helpers\Html;

class HelloWidget extends Widget{
 public $message;
 public function init(){
  parent::init();
  if($this->message===null){
   $this->message= 'Welcome User';
  }else{
   $this->message= 'Welcome '.$this->message;
  }
  // ob_start();
  // ob_implicit_flush(false);
 }
 public function run(){
  // $content = ob_get_clean();
  return Html::encode($this->message); // . $content;
 }
}

// This widget is called like this:
echo HelloWidget::widget(['message' => ' Yii2.0']);

// After uncommenting my 4 comments you can use this
HelloWidget::begin(['message' => ' Yii2.0']);
echo 'My content';
HelloWidget::end();

Tests - unit + functional + acceptance (opa) + coverage

It is easy to run tests as both demo-applications are ready. Use command line and navigate to your project. Then type:

php ./vendor/bin/codecept run

This will run Unit and Functional tests. They are defined in folder tests/unit and tests/functional. Functional tests run in a hidden browser and do not work with JavaScript I think. In order to test complex JavaScript, you need Acceptance Tests. How to run them is to be found in file README.md or in documentation in both demo applications. If you want to run these tests in your standard Chrome or Firefox browser, you will need Java JDK and file selenium-server*.jar. See links in README.md. Once you have the JAR file, place is to your project and run it:

java -jar selenium-server-4.0.0.jar standalone

Now you can rerun your tests. Make sure that you have working URL of your project in file acceptance.suite.yml, section WebDriver. For example http://localhost/yii-basic/web. It depends on your environment. Also specify browser. For me works well setting "browser: chrome". If you receive error "WebDriver is not installed", you need to call this composer command:

composer require codeception/module-webdriver --dev

PS: There is also this file ChromeDriver but I am not really sure if it is an alternative to "codeception/module-webdriver" or when to use it. I havent studied it yet.

If you want to see the code coverage, do what is described in the documentation (link above). Plus make sure that your PHP contains xDebug! And mind the difference in settings of xDebug2 and xDebug3! If xDebug is missing, you will receive error "No code coverage driver available".

Microsoft Access MDB

Under Linux I haven't suceeded, but when I install a web server on Windows (for example XAMPP Server) I am able to install "Microsoft Access Database Engine 2016 Redistributable" and use *.mdb file.

So first of all you should install the web server with PHP and you should know wheather you are installing 64 or 32bit versions. Probably 64. Then go to page Microsoft Access Database Engine 2016 Redistributable (or find newer if available) and install corresponding package (32 vs 64bit).

Note: If you already have MS Access installed in the identical bit-version, you might not need to install the engine.

Then you will be able to use following DSN string in DB connection. (The code belongs to file config/db.php):

<?php

$file = "C:\\xampp\\htdocs\\Database1.mdb";

return [
  'class' => 'yii\db\Connection',
	
  'dsn' => "odbc:DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=$file;Uid=;Pwd=;",
  'username' => '',
  'password' => '',
  'charset' => 'utf8',
	
  //'schemaMap' => [
  //  'odbc'=> [
  //    'class'=>'yii\db\pgsql\Schema',
  //    'defaultSchema' => 'public' //specify your schema here
  //  ]
  //], 

  // Schema cache options (for production environment)
  //'enableSchemaCache' => true,
  //'schemaCacheDuration' => 60,
  //'schemaCache' => 'cache',
];

Then use this to query a table:

$data = Yii::$app->db->createCommand("SELECT * FROM TableX")->queryAll();
var_dump($data);

Note: If you already have MS Access installed in different bit-version then your PHP, you will not be able to install the engine in the correct bit-version. You must uninstall MS Access in that case.

Note2: If you do not know what your MDB file contains, Google Docs recommended me MDB, ACCDB Viewer and Reader and it worked.

Note3: There are preinstalled applications in Windows 10 named:

  • "ODBC Data Sources 32-bit"
  • "ODBC Data Sources 64-bit"
  • (Just hit the Win-key and type "ODBC")

Open the one you need, go to tab "System DSN" and click "Add". You will see what drivers are available - only these drivers can be used in the DSN String!!

If only "SQL Server" is present, then you need to install the Access Engine (or MS Access) with drivers for your platform. You need driver named cca "Microsoft Access Driver (*.mdb, *.accdb)"

In my case the Engine added following 64bit drivers:

  • Microsoft Access dBASE Driver (*.dbf, *.ndx, *.mdx)
  • Microsoft Access Driver (*.mdb, *.accdb)
  • Microsoft Access Text Driver (*.txt, *.csv)
  • Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)

And how about Linux ?

You need the MS Access Drivers as well, but Microsoft does not provide them. There are some 3rd party MdbTools or EasySoft, but their are either not-perfect or expensive. Plus there is Unix ODBC.

For Java there are Java JDBC, Jackcess and Ucanaccess.

And how about Docker ? As far as I know you cannot run Windows images under Linux so you will not be able to use the ODBC-advantage of Windows in this case. You can use Linux images under Windows, but I think there is no way how to access the ODBC drivers from virtual Linux. You would have to try it, I haven't tested it yet.

Migration batch insert csv

If you want to import CSV into your DB in Yii2 migrations, you can create this "migration base class" and use it as a parent of your actual migration. Then you can use method batchInsertCsv().

<?php

namespace app\components;

use yii\db\Migration;

class BaseMigration extends Migration
{
    /**
     * @param $filename Example: DIR_ROOT . DIRECTORY_SEPARATOR . "file.csv"
     * @param $table The target table name
     * @param $csvToSqlColMapping [csvColName => sqlColName] (if $containsHeaderRow = true) or [csvColIndex => sqlColName] (if $containsHeaderRow = false)
     * @param bool $containsHeaderRow If the header with CSV col names is present
     * @param int $batchSize How many rows will be inserted in each batch
     * @throws Exception
     */
    public function batchInsertCsv($filename, $table, $csvToSqlColMapping, $containsHeaderRow = false, $batchSize = 10000, $separator = ';')
    {
        if (!file_exists($filename)) {
            throw new \Exception("File " . $filename . " not found");
        }

        // If you see number 1 in first inserted row and column, most likely BOM causes this.
        // Some Textfiles begin with 239 187 191 (EF BB BF in hex)
        // bite order mark https://en.wikipedia.org/wiki/Byte_order_mark
        // Let's trim it on the first row.
        $bom = pack('H*', 'EFBBBF');

        $handle = fopen($filename, "r");
        $lineNumber = 1;
        $header = [];
        $rows = [];
        $sqlColNames = array_values($csvToSqlColMapping);
        $batch = 0;

        if ($containsHeaderRow) {
            if (($raw_string = fgets($handle)) !== false) {
                $header = str_getcsv(trim($raw_string, $bom), $separator);
            }
        }

        // Iterate over every line of the file
        while (($raw_string = fgets($handle)) !== false) {
            $dataArray = str_getcsv(trim($raw_string, $bom), $separator);

            if ($containsHeaderRow) {
                $dataArray = array_combine($header, $dataArray);
            }

            $tmp = [];
            foreach ($csvToSqlColMapping as $csvCol => $sqlCol) {
                $tmp[] = trim($dataArray[$csvCol]);
            }
            $rows[] = $tmp;

            $lineNumber++;
            $batch++;

            if ($batch >= $batchSize) {
                $this->batchInsert($table, $sqlColNames, $rows);
                $rows = [];
                $batch = 0;
            }
        }
        fclose($handle);

        $this->batchInsert($table, $sqlColNames, $rows);
    }
}
]]>
0
[wiki] How to redirect all emails to one inbox on Yii2 applications Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/wiki/2566/how-to-redirect-all-emails-to-one-inbox-on-yii2-applications https://www.yiiframework.com/wiki/2566/how-to-redirect-all-emails-to-one-inbox-on-yii2-applications glpzzz glpzzz

\yii\mail\BaseMailer::useFileTransport is a great tool. If you activate it, all emails sent trough this mailer will be saved (by default) on @runtime/mail instead of being sent, allowing the devs to inspect thre result.

But what happens if we want to actually receive the emails on our inboxes. When all emails are suppose to go to one account, there is no problem: setup it as a param and the modify it in the params-local.php (assuming advaced application template).

The big issue arises when the app is supposed to send emails to different accounts and make use of replyTo, cc and bcc fields. It's almost impossible try to solve it with previous approach and without using a lot of if(YII_DEBUG).

Well, next there is a solution:

'useFileTransport' => true,
'fileTransportCallback' => function (\yii\mail\MailerInterface $mailer, \yii\mail\MessageInterface $message) {
    $message->attachContent(json_encode([
            'to' => $message->getTo(),
            'cc' => $message->getCc(),
            'bcc' => $message->getBcc(),
            'replyTo' => $message->getReplyTo(),
        ]), ['fileName' => 'metadata.json', 'contentType' => 'application/json'])
        ->setTo('[email protected]') // account to receive all the emails
        ->setCc(null)
        ->setBcc(null)
        ->setReplyTo(null);

    $mailer->useFileTransport = false;
    $mailer->send($message);
    $mailer->useFileTransport = true;

    return $mailer->generateMessageFileName();
}

How it works? fileTransportCallback is the callback to specify the filename that should be used to create the saved email on @runtime/mail. It "intercepts" the send email process, so we can use it for our porpuses.

  1. Attach a json file with the real recipients information so we can review it
  2. Set the recipient (TO) as the email address where we want to receive all the emails.
  3. Set the others recipients fields as null
  4. Deactivate useFileTransport
  5. Send the email
  6. Activate useFileTransport
  7. Return the defaut file name (datetime of the operation)

This way we both receive all the emails on the specified account and get them stored on @runtime/mail.

Pretty simple helper to review emails on Yii2 applications.

Originally posted on: https://glpzzz.github.io/2020/10/02/yii2-redirect-all-emails.html

]]>
0
[wiki] Api of Multiple File Uploading in Yii2 Tue, 05 Jul 2022 03:01:39 +0000 https://www.yiiframework.com/wiki/2565/api-of-multiple-file-uploading-in-yii2 https://www.yiiframework.com/wiki/2565/api-of-multiple-file-uploading-in-yii2 fezzymalek fezzymalek

After getting lot's of error and don't know how to perform multiple images api in yii2 finally I get it today

This is my question I asked on forum and it works for me https://forum.yiiframework.com/t/multiple-file-uploading-api-in-yii2/130519

Implement this code in model for Multiple File Uploading

public function rules()
    {
        return [
            [['post_id', 'media'], 'required'],
            [['post_id'], 'integer'],
            [['media'], 'file', 'maxFiles' => 10],//here is my file field
            [['created_at'], 'string', 'max' => 25],
            [['post_id'], 'exist', 'skipOnError' => true, 'targetClass' => Post::className(), 'targetAttribute' => ['post_id' => 'id']],
        ];
    }
    

You can add extension or any skiponempty method also in model.

And this is my controller action where I performed multiple file uploading code.

public function actionMultiple(){
        $model = new Media;
        $model->post_id = '2';
        if (Yii::$app->request->ispost) {
            $model->media = UploadedFile::getInstances($model, 'media');
            if ($model->media) {
                foreach ($model->media as $value) {
                    $model = new Media;
                    $model->post_id = '2';
                    $BasePath = Yii::$app->basePath.'/../images/post_images';
                    $filename = time().'-'.$value->baseName.'.'.$value->extension;
                    $model->media = $filename;
                    if ($model->save()) {
                        $value->saveAs($BasePath.$filename);
                    }
                }
                return array('status' => true, 'message' => 'Image Saved'); 
            }
        }
        return array('status' => true, 'data' => $model);
    }

If any query or question I will respond.

]]>
0
[wiki] How to email error logs to developer on Yii2 apps Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/wiki/2564/how-to-email-error-logs-to-developer-on-yii2-apps https://www.yiiframework.com/wiki/2564/how-to-email-error-logs-to-developer-on-yii2-apps glpzzz glpzzz

Logging is a very important feature of the application. It let's you know what is happening in every moment. By default, Yii2 basic and advanced application have just a \yii\log\FileTarget target configured.

To receive emails with messages from the app, setup the log component to email (or Telegram, or slack) transport instead (or besides) of file transport:

'components' => [
    // ...
    'log' => [
         'targets' => [
             [
                 'class' => 'yii\log\EmailTarget',
                 'mailer' => 'mailer',
                 'levels' => ['error', 'warning'],
                 'message' => [
                     'from' => ['[email protected]'],
                     'to' => ['[email protected]', '[email protected]'],
                     'subject' => 'Log message',
                 ],
             ],
         ],
    ],
    // ...
],

The \yii\log\EmailTarget component is another way to log messages, in this case emailing them via the mailer component of the application as specified on the mailer attribute of EmailTarget configuration. Note that you can also specify messages properties and which levels of messages should be the sent trough this target.

If you want to receive messages via other platforms besides email, there are other components that represents log targets:

Or you can implement your own by subclassing \yii\log\Target

]]>
0
[wiki] How to add Schema.org markup to Yii2 pages Fri, 11 Sep 2020 22:09:55 +0000 https://www.yiiframework.com/wiki/2560/how-to-add-schema-org-markup-to-yii2-pages https://www.yiiframework.com/wiki/2560/how-to-add-schema-org-markup-to-yii2-pages glpzzz glpzzz

https://schema.org is a markup system that allows to embed structured data on their web pages for use by search engines and other applications. Let's see how to add Schema.org to our pages on Yii2 based websites using JSON-LD.

Basically what we need is to embed something like this in our pages:

<script type="application/ld+json">
{ 
  "@context": "http://schema.org/",
  "@type": "Movie",
  "name": "Avatar",
  "director": 
    { 
       "@type": "Person",
       "name": "James Cameron",
       "birthDate": "1954-08-16"
    },
  "genre": "Science fiction",
  "trailer": "../movies/avatar-theatrical-trailer.html" 
}
</script>

But we don't like to write scripts like this on Yii2, so let's try to do it in other, more PHP, way.

In the layout we can define some general markup for our website, so we add the following snippet at the beginning of the@app/views/layouts/main.php file:

<?= \yii\helpers\Html::script(isset($this->params['schema'])
    ? $this->params['schema']
    : \yii\helpers\Json::encode([
        '@context' => 'https://schema.org',
        '@type' => 'WebSite',
        'name' => Yii::$app->name,
        'image' => $this->image,
        'url' => Yi::$app->homeUrl,
        'descriptions' => $this->description,
        'author' => [
            '@type' => 'Organization',
            'name' => Yii::$app->name,
            'url' => 'https://www.hogarencuba.com',
            'telephone' => '+5352381595',
        ]
    ]), [
    'type' => 'application/ld+json',
]) ?>

Here we are using the Html::script($content, $options) to include the script with the necessary type option, and Json::encode($value, $options) to generate the JSON. Also we use a page parameter named schema to allow overrides on the markup from other pages. For example, in @app/views/real-estate/view.php we are using:

$this->params['schema'] = \yii\helpers\Json::encode([
    '@context' => 'https://schema.org',
    '@type' => 'Product',
    'name' => $model->title,
    'description' => $model->description,
    'image' => array_map(function ($item) {
        return $item->url;
    }, $model->images),
    'category' => $model->type->description_es,
    'productID' => $model->code,
    'identifier' => $model->code,
    'sku' => $model->code,
    'url' => \yii\helpers\Url::current(),
    'brand' => [
        '@type' => 'Organization',
        'name' => Yii::$app->name,
        'url' => 'https://www.hogarencuba.com',
        'telephone' => '+5352381595',
    ],
    'offers' => [
        '@type' => 'Offer',
        'availability' => 'InStock',
        'url' => \yii\helpers\Url::current(),
        'priceCurrency' => 'CUC',
        'price' => $model->price,
        'priceValidUntil' => date('Y-m-d', strtotime(date("Y-m-d", time()) . " + 365 day")),
        'itemCondition' => 'https://schema.org/UsedCondition',
        'sku' => $model->code,
        'identifier' => $model->code,
        'image' => $model->images[0],
        'category' => $model->type->description_es,
        'offeredBy' => [
            '@type' => 'Organization',
            'name' => Yii::$app->name,
            'url' => 'https://www.hogarencuba.com',
            'telephone' => '+5352381595',
        ]
    ]
]);

Here we redefine the schema for this page with more complex markup: a product with an offer.

This way all the pages on our website will have a schema.org markup defined: in the layout we have a default and in other pages we can redefine setting the value on $this->params['schema'].

]]>
0
[wiki] How to add Open Graph and Twitter Card tags to Yii2 website. Sat, 22 Mar 2025 08:35:12 +0000 https://www.yiiframework.com/wiki/2559/how-to-add-open-graph-and-twitter-card-tags-to-yii2-website https://www.yiiframework.com/wiki/2559/how-to-add-open-graph-and-twitter-card-tags-to-yii2-website glpzzz glpzzz

OpenGraph and Twitter Cards are two metadata sets that allow to describe web pages and make it more understandable for Facebook and Twitter respectively.

There a lot of meta tags to add to a simple webpage, so let's use TaggedView

This component overrides the yii\web\View adding more attributes to it, allowing to set the values on every view. Usually we setup page title with

$this->title = $model->title;

Now, with TaggedView we are able to set:

$this->title = $model->title;
$this->description = $model->abstract;
$this->image = $model->image;
$this->keywords = ['foo', 'bar'];

And this will generate the proper OpenGraph, Twitter Card and HTML meta description tags for this page.

Also, we can define default values for every tag in the component configuration that will be available for every page and just will be overriden if redefined as in previous example.

'components' => [
    //...
    'view' => [
        'class' => 'daxslab\taggedview\View',
        'site_name' => '',
        'author' => '',
        'locale' => '',
        'generator' => '',
        'updated_time' => '',
    ],
    //...
]

Some of this properties have default values assigned, like site_name that gets Yii::$app->name by default.

Result of usage on a website:

<title>¿Deseas comprar o vender una casa en Cuba? | HogarEnCuba, para comprar y vender casas en Cuba</title>
<meta name="author" content="Daxslab (https://www.daxslab.com)">
<meta name="description" content="Hay 580 casas...">
<meta name="generator" content="Yii2 PHP Framework (http://www.yiiframework.com)">
<meta name="keywords" content="HogarEnCuba, ...">
<meta name="robots" content="follow">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:description" content="Hay 580 casas...">
<meta name="twitter:image" content="https://www.hogarencuba.com/images/main-identifier_es.png">
<meta name="twitter:site" content="HogarEnCuba">
<meta name="twitter:title" content="¿Deseas comprar o vender una casa en Cuba?">
<meta name="twitter:type" content="website">
<meta name="twitter:url" content="https://www.hogarencuba.com/">
<meta property="og:description" content="Hay 580 casas...">
<meta property="og:image" content="https://www.hogarencuba.com/images/main-identifier_es.png">
<meta property="og:locale" content="es">
<meta property="og:site_name" content="HogarEnCuba">
<meta property="og:title" content="¿Deseas comprar o vender una casa en Cuba?">
<meta property="og:type" content="website">
<meta property="og:updated_time" content="10 sept. 2020 9:43:00">
]]>
0
[wiki] Yii v2 snippet guide II Thu, 11 Nov 2021 13:47:12 +0000 https://www.yiiframework.com/wiki/2558/yii-v2-snippet-guide-ii https://www.yiiframework.com/wiki/2558/yii-v2-snippet-guide-ii rackycz rackycz
  1. My articles
  2. Connection to MSSQL
  3. Using MSSQL database as the 2nd DB in the Yii2 project
  4. Creating models in Gii for remote MSSQL tables
  5. PhpExcel/PhpSpreadsheet in Yii 2 and sending binary content to the browser
  6. PDF - UTF + 1D & 2D Barcodes - TCPDF
  7. Custom formatter - asDecimalOrInteger
  8. Displaying SUM of child models in a GridView with parent models
  9. Sort and search by related column
  10. Sending binary data as a file to browser - decoded base64

My articles

Articles are separated into more files as there is the max lenght for each file on wiki.

Connection to MSSQL

You will need MSSQL drivers in PHP. Programatically you can list them or test their presence like this:

var_dump(\PDO::getAvailableDrivers());

if (in_array('sqlsrv', \PDO::getAvailableDrivers())) {
  // ... MsSQL driver is available, do something
}

Based on your system you have to download different driver. The differences are x64 vs x86 and ThreadSafe vs nonThreadSafe. In Windows I always use ThreadSafe. Explanation.

Newest PHP drivers are here.

  • Drivers v5.8 = PHP 7.2 - 7.4

Older PHP drivers here.

  • Drivers v4.0 = PHP 7.0 - 7.1
  • Drivers v3.2 = PHP 5.x

Once drivers are downloaded and extracted, pick one DLL file and place it into folder "php/ext". On Windows it might be for example here: "C:\xampp\php\ext"

Note: In some situations you could also need these OBDC drivers, but I am not sure when:

Now file php.ini must be modified. On Windows it might be placed here: "C:\xampp\php\php.ini". Open it and search for rows starting with word "extension" and paste there cca this:

extension={filename.dll}
// Example:
extension=php_pdo_sqlsrv_74_ts_x64.dll

Now restart Apache and visit phpinfo() web page. You should see section "pdo_sqlsrv". If you are using XAMPP, it might be on this URL: http://localhost/dashboard/phpinfo.php.

Then just add connection to your MSSQL DB in Yii2 config. In my case the database was remote so I needed to create 2nd DB connection. Read next chapter how to do it.

Using MSSQL database as the 2nd DB in the Yii2 project

Adding 2nd database is done like this in yii-config:

'db' => $db, // the original DB
'db2'=>[
  'class' => 'yii\db\Connection',
  'driverName' => 'sqlsrv',
  // I was not able to specify database like this: 
  // 'dsn' => 'sqlsrv:Server={serverName};Database={dbName}',
  'dsn' => 'sqlsrv:Server={serverName}', 
  'username' => '{username}',
  'password' => '{pwd}',
  'charset' => 'utf8',
],

That's it. Now you can test your DB like this:

$result = Yii::$app->db2->createCommand('SELECT * FROM {tblname}')->queryAll();
var_dump($result);

Note that in MSSQL you can have longer table names. Example: CATEGORY.SCHEMA.TBL_NAME

And your first test-model can look like this (file MyMsModel.php):

namespace app\models;
use Yii;
use yii\helpers\ArrayHelper;
class MyMsModel extends \yii\db\ActiveRecord
{
  public static function getDb()
  {
    return \Yii::$app->db2; // or Yii::$app->get('db2');
  }
  public static function tableName()
  {
    return 'CATEGORY.SCHEMA.TBL_NAME'; // or SCHEMA.TBL_NAME
  }
}

Usage:

$result = MyMsModel::find()->limit(2)->all();
var_dump($result);

Creating models in Gii for remote MSSQL tables

Once you have added the 2nd database (read above) go to the Model Generator in Gii. Change there the DB connection to whatever you named the connection in yii-config (in the example above it was "db2") and set tablename in format: SCHEMA.TBL_NAME. If MSSQL server has more databases, one of them is set to be the main DB. This will be used I think. I haven't succeeded to change the DB. DB can be set in the DSN string, but it had no effect in my case.

PhpExcel/PhpSpreadsheet in Yii 2 and sending binary content to the browser

In previous chapters I showed how to use PhpExcel in Yii 1. Now I needed it also in Yii 2 and it was extremely easy.

Note: PhpExcel is deprecated and was replaced with PhpSpreadsheet.

// 1) Command line:
// This downloads everything to folder "vendor"
composer require phpoffice/phpspreadsheet --prefer-source
// --prefer-source ... also documentation and samples are downloaded 
// ... adds cca 40MB and 1400 files 
// ... only for devel system

// 2) PHP:
// Now you can directly use the package without any configuration:
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();

// Uncomment following rows if you want to set col width:
//$sheet->getColumnDimension('A')->setAutoSize(false);
//$sheet->getColumnDimension('A')->setWidth("50");

$sheet->setCellValue('A1', 'Hello World !');

$writer = new Xlsx($spreadsheet);

// You can save the file on the server:
// $writer->save('hello_world.xlsx'); 

// Or you can send the file directly to the browser so user can download it:
// header('Content-Type: application/vnd.ms-excel'); // This is probably for older XLS files.
header('Content-Type: application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); // This is for XLSX files (they are basically zip files).
header('Content-Disposition: attachment;filename="filename.xlsx"');
header('Cache-Control: max-age=0');
$writer->save('php://output');
exit();

Thanks to DbCreator for the idea how to send XLSX to browser. Nevertheless exit() or die() should not be called. Read the link.

Following is my idea which originates from method renderPhpFile() from Yii2:

ob_start();
ob_implicit_flush(false);
$writer->save('php://output');
$file = ob_get_clean();

return \Yii::$app->response->sendContentAsFile($file, 'file.xlsx',[
  'mimeType' => 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'inline' => false
]);

This also worked for me:

$tmpFileName = uniqid('file_').'.xlsx';
$writer->save($tmpFileName);    
header('Content-Type: application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); 
header('Content-Disposition: attachment;filename="filename.xlsx"');
header('Cache-Control: max-age=0');
echo file_get_contents($tmpFileName);
unlink($tmpFileName);
exit();

Note: But exit() or die() should not be called. Read the "DbCreator" link above.

PDF - UTF + 1D & 2D Barcodes - TCPDF

See part I of this guide for other PDF creators:

TCPDF was created in 2002 (I think) and these days (year 2020) is being rewritten into a modern PHP application. I will describe both, but lets begin with the older version.

Older version of TCPDF

Download it from GitHub and save it into folder

{projectPath}/_tcpdf

Into web/index.php add this:

require_once('../_tcpdf/tcpdf.php');

Now you can use any Example to test TCPDF. For example: https://tcpdf.org/examples/example_001/

Note: You have to call constructor with backslash:

$pdf = new \TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);

Note: Texts are printed using more methods - see file tcpdf.php for details:

  • writeHTMLCell()
  • Multicell()
  • writeHTML()
  • Write()
  • Cell()
  • Text()

Note: Store your files in UTF8 no BOM format so diacritics is correct in PDF.

Importing new TTF fonts is done like this:

// this command creates filed in folder _tcpdf\fonts. Use the filename as the fontname in other commands.
$fontname = \TCPDF_FONTS::addTTFfont("path to TTF file", 'TrueTypeUnicode', '', 96);

Now you can use it in PHP like this:

$pdf->SetFont($fontname, '', 24, '', true);

Or in HTML:

<font size="9" face="fontName" style="color: rgb(128, 128, 128);">ABC</font>

Rendering is done like this:

$pdf->writeHTML($html);

Note: When printing pageNr and totalPageCount to the footer, writeHTML() was not able to correctly interpret methods getAliasNumPage() and getAliasNbPages() as shown in Example 3. I had to use rendering method Text() and position the numbers correctly like this:

$this->writeHTML($footerHtmlTable);
$this->SetTextColor('128'); // I have gray pageNr
$this->Text(185, 279, 'Page ' . $this->getAliasNumPage() . '/' . $this->getAliasNbPages());
$this->SetTextColor('0'); // returning black color

New version of TCPDF

... to be finished ...

Custom formatter - asDecimalOrInteger

If I generate a PDF-invoice it contains many numbers and it is nice to print them as integers when decimals are not needed. For example number 24 looks better and saves space compared to 24.00. So I created such a formatter. Original inspiration and how-to was found here:

My formatter looks like this:

<?php

namespace app\myHelpers;

class MyFormatter extends \yii\i18n\Formatter {

  public function asDecimalOrInteger($value) {
    $intStr = (string) (int) $value; // 24.56 => "24" or 24 => "24"
    if ($intStr === (string) $value) {
      // If input was integer, we are comparing strings "24" and "24"
      return $this->asInteger($value);
    }
    if (( $intStr . '.00' === (string) $value)) {
      // If the input was decimal, but decimals were all zeros, it is an integer.
      return $this->asInteger($value);
    }
    // All other situations
    $decimal = $this->asDecimal($value);
    
    // Here I trim also the trailing zero.
    // Disadvantage is that String is returned, but in PDF it is not important
    return rtrim((string)$decimal, "0"); 
  }

}

Usage is simple. Read the link above and give like to karpy47 or see below:

// file config/web.php
'components' => [
    'formatter' => [
        'class' => 'app\myHelpers\MyFormatter',
   ],
],

There is only one formatter in the whole of Yii and you can extend it = you can add more methods and the rest of the formatter will remain so you can use all other methods as mentioned in documentation.

Displaying SUM of child models in a GridView with parent models

... can be easily done by adding a MySQL VIEW into your DB, creating a model for it and using it in the "ParentSearch" model as the base class.

Let's show it on list of invoices and their items. Invoices are in table "invoice" (model Invoice) and their items in "invoice_item" (model InvoiceItem). Now we need to join them and sort and filter them by SUM of prices (amounts). To avoid calculations in PHP, DB can do it for us if we prepare a MySQL VIEW:

CREATE VIEW v_invoice AS
SELECT invoice.*, 
SUM(invoice_item.units * invoice_item.price_per_unit) as amount,
COUNT(invoice_item.id) as items
FROM invoice 
LEFT JOIN invoice_item 
ON (invoice.id = invoice_item.id_invoice)
GROUP BY invoice.id

Note: Here you can read why it is better not to use COUNT(*) in LEFT JOIN:

This will technically clone the original table "invoice" into "v_invoice" and will append 2 calculated columns: "amount" + "items". Now you can easily use this VIEW as a TABLE (for reading only) and display it in a GridView. If you already have a GridView for table "invoice" the change is just tiny. Create this model:

<?php
namespace app\models;
class v_Invoice extends Invoice
{
    public static function primaryKey()
    {
        // here is specified which column(s) create the fictive primary key in the mysql-view
        return ['id']; 
    }
    public static function tableName()
    {
        return 'v_invoice';
    }
}

.. and in model InvoiceSearch replace word Invoice with v_Invoice (on 2 places I guess) plus add rules for those new columns. Example:

public function rules()
{
  return [
    // ...
    [['amount'], 'number'], // decimal
    [['items'], 'integer'],
  ];
}

Into method search() add condition if you want to filter by amount or items:

if (trim($this->amount)!=='') {
  $query->andFilterWhere([
    'amount' => $this->amount
  ]);
}

In the GridView you can now use the columns "amount" and "items" as native columns. Filtering and sorting will work.

Danger: Read below how to search and sort by related columns. This might stop working if you want to join your MySQL with another table.

I believe this approach is the simplest to reach the goal. The advantage is that the MySQL VIEW is only used when search() method is called - it means in the list of invoices. Other parts of the web are not influenced because they use the original Invoice model. But if you need some special method from the Invoice model, you have it also in v_Invoice. If data is saved or changed, you must always modify the original table "invoice".

Sort and search by related column

Lets say you have table of invoices and table of companies. They have relation and you want to display list of Invoices plus on each row the corresponding company name. You want to filter and sort by this column.

Your GridView:

<?= GridView::widget([
// ...
  'columns' => [
    // ...
    [
      'attribute'=>'company_name',
      'value'=>'companyRelation.name',
    ],

Your InvoiceSearch model:

class InvoiceSearch extends Invoice
{
  public $company_name;
  
  // ...
  
  public function rules() {
    return [
      // ...
      [['company_name'], 'safe'],
    ];
  }             

  // ...
  
  public function search($params) {
    // ...

    // You must use joinWith() in order to have both tables in one JOIN - then you can call WHERE and ORDER BY on the 2nd table. 
    // Explanation here:
    // https://stackoverflow.com/questions/25600048/what-is-the-difference-between-with-and-joinwith-in-yii2-and-when-to-use-them
    
    $query = Invoice::find()->joinWith('companyRelation');

    // Appending new sortable column:
    $sort = $dataProvider->getSort(); 
    $sort->attributes['company_name'] = [
      'asc' => ['table.column' => SORT_ASC],
      'desc' => ['table.column' => SORT_DESC],
      'label' => 'Some label',
      'default' => SORT_ASC            
    ];

    // ...
 
    if (trim($this->company_name)!=='') {
      $query->andFilterWhere(['like', 'table.column', $this->company_name]);
    }
  }

Sending binary data as a file to browser - decoded base64

In my tutorial for Yii v1 I presented following way how to send headers manually and then call exit(). But calling exit() or die() is not a good idea so I discovered a better way in Yii v2. See chapter Secured (secret) file download

Motivation: Sometimes you receive a PDF file encoded into a string using base64. For example a label with barcodes from FedEx, DPD or other delivery companies and your task is to show the label to users.

For me workes this algorithm:

$pdfBase64 = 'JVBERi0xLjQ ... Y0CiUlRU9GCg==';

// First I create a fictive stream in a temporary file
// Read more about PHP wrappers: 
// https://www.php.net/manual/en/wrappers.php.php 
$stream = fopen('php://temp','r+');

// Decoded base64 is written into the stream
fwrite($stream, base64_decode($pdfBase64));

// And the stream is rewound back to the start so others can read it
rewind($stream);

// This row sets "Content-Type" header to none. Below I set it manually do application/pdf.
Yii::$app->response->format = Yii::$app->response::FORMAT_RAW;
Yii::$app->response->headers->set('Content-Type', 'application/pdf');
      
// This row will download the file. If you do not use the line, the file will be displayed in the browser.
// Details here:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#Downloads
// Yii::$app->response->headers->set('Content-Disposition','attachment; filename="hello.pdf"'); 
    
// Here is used the temporary stream
Yii::$app->response->stream = $stream;

// You can call following line, but you don't have to. Method send() is called automatically when current action ends:
// Details here:
// https://www.yiiframework.com/doc/api/2.0/yii-web-response#sendContentAsFile()-detail
// return Yii::$app->response->send(); 

Note: You can add more headers if you need. Check my previous article (linked above).

]]>
0
[wiki] Start using Yii2 in Raspberry Pi3 (RPI3) via Pantahub Tue, 22 Dec 2020 14:57:34 +0000 https://www.yiiframework.com/wiki/2557/start-using-yii2-in-raspberry-pi3-rpi3-via-pantahub https://www.yiiframework.com/wiki/2557/start-using-yii2-in-raspberry-pi3-rpi3-via-pantahub sirin_ibin sirin_ibin
  1. Make your RPI3 device ready to deploy Yii2 by following 6 Steps
  2. Deploy your Yii2 app to the device by following 5 Steps

Note:Pantahub is the only place where Linux firmware can be shared and deployed for any device, You can signup @pantahub here:http://www.pantahub.com

Make your RPI3 device ready to deploy Yii2 by following 6 Steps

Step 1: Burn the RPI3 initial stable image into your sd card.
a) Download RPI3 image

Click to download: https://pantavisor-ci.s3.amazonaws.com/pv-initial-devices/tags/012-rc2/162943661/rpi3_initial_stable.img.xz

b) unxz the device image

Run $ unxz rpi3_initial_stable.img.xz

c) Burn image into sd card using Raspberry Pi Imager 1.2

Step 2: Boot your RPI3
a) Insert your sd card and supply the power

Step 3: Singup @pantahub here http://www.pantahub.com
Step 4: Download & Install a CLI tool "pvr"

Note: pvr is a CLI tool which can be used to interact with your device through pantahub platform.

Note: Using pvr you can share your firmware and projects as simple as with a git tree.

Note: Move the pvr binary to your bin folder after download.

Linux(AMD64): Download

Linux(ARM32v6): Download

Darwin(AMD64): Download

pvr clone; pvr commit; pvr post

Install from github source code: $ go get gitlab.com/pantacor/pvr $ go build -o ~/bin/pvr gitlab.com/pantacor/pvr

Note: You need "GOLANG" to be installed in your system for building pvr from github source code.

Step 5: Detect & Claim your device
a) Connect a LAN cable between your RPI3 & computer/Router.

b) Open your terminal & run $ pvr scan

c) Claim your device

$ pvr claim -c merely-regular-gorilla https://api.pantahub.com:443/devices/5f1b9c44e193a Watch now on Amazon Prime Video 5000afa9901

d) Log into Panthub.com and check whether the newly claimed device appeared in the dashboard or not.

Step 6: Clone the device to your computer using the Clone URL of your device

$ pvr clone https://pvr.pantahub.com/sirinibin/presently_learning_pelican/0 presently_learning_pelican

Now your device is ready to deploy your Yii2 app

Deploy your Yii2 app to the device by following 5 Steps

Step 1: Move to device root dir
 `$ cd presently_learning_pelican`
Step 2: Add a new app "yii2" into the device

>sirinibin/yii2-basic-arm32v7:latest is a Docker Watch now on Amazon Prime Video image made for the devices with ARM32 architecture >> You can customise the docker image for your custom Yii2 app.

$ pvr app add yii2 --from=sirinibin/yii2-basic-arm32v7:latest

Step 3: Deploy the changes to the device

$ pvr add . $ pvr commit $ pvr post

Step 4: Check the device status changes in Pantahub.com dashboard & wait for the status to become "DONE"

Status 1:

Status 2:

Status 3:

Status 4:

Step 5: Verify the "yii2" app deployment

Access the device IP: http://10.42.0.231/myapp1/web/ in your web browser.

You are done!

]]>
0
[wiki] Yii2 - Upgrading to Bootstrap 4 Fri, 20 Mar 2020 12:18:55 +0000 https://www.yiiframework.com/wiki/2556/yii2-upgrading-to-bootstrap-4 https://www.yiiframework.com/wiki/2556/yii2-upgrading-to-bootstrap-4 RichardPillay RichardPillay

Yii2 - Converting from Bootstrap3 to Bootstrap4

This article has been written because while conversion is a largely pain-free process, there are some minor issues. These are not difficult to solve, but it is not immediately obvious where the problem lies.

1 - Install Bootstrap4 My preference is to simply use composer. Change composer.json in the root of your project:

  • find the line that includes Bootstrap3.
  • Copy the line, and change the new line:

  • Save the file, then run 'composer update'
  • Use your IDE, text editor or whatever other means you have at your disposal to find all occurrences where bootstrap is used and change it bootstrap4. My suggestion is to search for the string yii\bootstrap\ and change it to yii\bootstrap4\. However, be careful - your IDE may require the backslash to be escaped. For example, Eclipse will find all files with the string easily, but the search string must have double-backslashes, while the replacement string must be left as single ones.
  • Now run all your tests and fix the problems that have arisen. Most of the failures will come from the fact that the Navbar no longer works, most likely. It's still there, just being rendered differently, and in my case it was invisible. This is because Bootstrap4 has changed some elements of the navbar. In my case, 14 tests failed - many of which failed due to the use of navbar content in some manner, such as looking for the Login link to infer whether the user is logged in or not.
    • You're not going to fix these issues without understanding them, so take a look at https://getbootstrap.com/docs/4.0/migration/. In particular, look at what has changed regarding the Navbars. The most meaningful is that Bootstrap4 no longer specifies a background, where Bootstrap3 did.
    • Open up frontend/viewslayouts/main.php in your editor, then look at your site in the browser. In my case no navbar, except for the Brand, and that is because I changed this from what was delivered as part of the Yii2 template, and it included a background. Since the rest of it was standard, there was nothing else - no ribbon strip, and no menu entries, although mousing into the area would show the links were there and could be clicked.
      • Find the line that starts with NavBar::begin and look at it's class options. In my case they were the original: 'class' => 'navbar-inverse navbar-fixed-top'
        • As I said before, no background is included, so add one: bg-dark - and check again. Now there's the ribbon back again, but no menu items.
        • Right-click on the ribbon and select "Inspect Element", or do whatever you have to do in your browser to inspect the page source. Looking at that starts to give you clues over what the navbar is doing. Looking at that, and referring back to the Bootstrap4 migration guide, I had the impression that neither navbar-inverse nor navbar-fixed-top were doing anything. So I removed those, and when refreshing the page, confirmed there were no changes.
        • More reading on the Bootstrap website gave me the bg-dark I mentioned earlier, and for the text, navbar-light or navbar-dark produced results.
        • now I had no menu items, but I did have a button that expanded the menu. Inspecting it's properties told me it was 'navbar-toggler', and the Bootstrap website told me it new to Bootstrap4, while it and was collapsed by default, 'navbar-expand' would expand it by default. That's cool - I reckon I'm going to add a setting for logged-in users that let them choose which they prefer. In the end, I opted for navbar-expand-md, which keeps it expanded unless the screen width is tight.

At the end of all this, I had the class line changed to something which gave me a navbar very similar to the original:

        //Bootstrap3: 'class' => 'navbar-inverse navbar-fixed-top',
        //Changed for Bootstrap4: 
        'class' => 'navbar navbar-expand-md navbar-light bg-dark',


Breadcrumbs

Note - March 2020: This entire section on Breadcrumbs may no longer be an issue. While I've left the section in as a tutorial, before making any changes read what Davide has to say in the user comments.

So, that fixed my navbar. Next, I noticed that the breadcrumbs were not quite right - the slash separating the path elements was no longer there. Preparing for a lot of debugging, I went to the Bootstrap site to look for a little inspiration. I didn't need to look any further - Bootstrap 4 requires each Breadcrumb element to have a class of "breadcrumb-item". After I spent a little time looking at vendors/yiisoft/yii2/widgets/Breadcrumbs.php to get some understanding of the issue, I discovered all that's needed is to change the itemTemplate and activeItemTemplate. Of course, since these are part of the Yii2 framework, you don't want to change that file, otherwise, it will probably get updated at some stage, and all your changes would be lost. Since both of these attributes are public, you can change them from outside the class, and the easiest place to do this is in frontend/views/main.php: `html

<div class="container">
    <?= Breadcrumbs::widget([
        'itemTemplate' => "\n\t<li class=\"breadcrumb-item\"><i>{link}</i></li>\n", // template for all links
        'activeItemTemplate' => "\t<li class=\"breadcrumb-item active\">{link}</li>\n", // template for the active link
        'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
    ]) ?>
    <?= Alert::widget() ?>
    <?= $content ?>
</div>```


Data Grid ActionColumn One of my pages was a data grid generated for me by gii. On each row it has a set of buttons that you can click to view, edit or delete the row. Under Bootstrap 4, the ActionColumn disappeared. Viewing the page source showed me it was there, but I couldn't see it or click on it. Going to the migration guide, it turns out that Bootstrap 3 includes icons, but Bootstrap 4 doesn't. I got a lot of help from a question asked in the Yii2forum.. In the end, my solution was to get a local copy of FontAwesome 5 by including the line "fortawesome/font-awesome": "^5.12.1" in the require section of composer.json, and then choosing the icons that I wanted. I spent a lot of time figuring out how to do this, but when I was done, it seemed almost anti-climactic in it's simplicity. This is what I did in my data form:

            ['class' => 'yii\grid\ActionColumn',
                'buttons' => [
                    'update' =>  function($url,$model) {
                        return Html::a('<i class="fas fa-edit"></i>', $url, [
                            'title' => Yii::t('app', 'update')
                        ]);
                    },
                    'view' =>  function($url,$model) {
                        return Html::a('<i class="fas fa-eye"></i>', $url, [
                            'title' => Yii::t('app', 'view')
                        ]);
                    },
                    'delete' => function($url,$model) {
                        return Html::a('<i class="fas fa-trash"></i>', $url, [
                            'title' => Yii::t('app', 'delete')
                        ]);
                    }
                 ]
            ],


Functional Tests

Not seeing anything more visually, at least nothing that was obvious, I now ran the suite of tests. These were all previously passing, but now several of them failed. One of them was the Contact Form, so I ran that one separately and the tests informed me they were failing because they couldn't see the error message:

1) ContactCest: Check contact submit no data
 Test  ../frontend/tests/functional/ContactCest.php:checkContactSubmitNoData
 
 Step  See "Name cannot be blank",".help-block"
 
 Fail  Element located either by name, CSS or XPath element with '.help-block' was not found.


I, on the other hand, could see the error messages on the form, so I used the browser's page source and discovered that the css class was no longer "help-block", it had changed to "invalid-feedback". Easy enough - in frontend/tests/_support/FunctionalTester.php, I changed the expected css class:

public function seeValidationError($message)
{
    $this->see($message, '.invalid-feedback');
}

Of course, this little excerpt is just an example. I found the same thing had to be done in several locations, but all were easily found and resolved.


After this, running my tests pointed me to no other problems, but I don't expect that to mean there aren't any other problems. While everything seems to be working so far, I expect there are more issues hiding in the woodwork. Somehow, those problems don't seem quite so insurmountable anymore.

]]>
0