Live News for Yii Framework News, fresh extensions and wiki articles about Yii framework. Mon, 17 Feb 2020 12:24:41 +0000 Zend_Feed_Writer 2 (http://framework.zend.com) https://www.yiiframework.com/ [extension] pingcrm-yii2 Mon, 17 Feb 2020 08:29:35 +0000 https://www.yiiframework.com/extension/pingcrm-yii2 https://www.yiiframework.com/extension/pingcrm-yii2 tebe tebe

Ping CRM on Yii 2

  1. Installation
  2. Running tests
  3. Requirements
  4. Credits

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

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

screenshot.png

Installation

Clone the repo locally:

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

Install PHP dependencies:

composer install

Install NPM dependencies:

npm ci

Build assets:

npm run dev
npm run css-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:

  • Username: johndoe@example.com
  • Password: secret

Running tests

To run the Ping CRM tests, run:

(to be done)

Requirements

  • PHP >= 5.6.0
  • SQLite

Credits

  • Original work by Jonathan Reinink (@reinink) and contributors
  • Port to Yii 2 by Thomas Breuss (@tbreuss)
]]>
0
[wiki] Yii2 - Upgrading to Bootstrap 4 Sun, 16 Feb 2020 03:19:10 +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:

    • change bootstrap to bootstrap4
    • Now head over to https://github.com/yiisoft/ - the Yii2 repository on Github
    • Change the version string to that version number, and also change the ~ to ^
    • After this, you should have something like this below, maybe with higher version numbers:

      "yiisoft/yii2-bootstrap" : "~2.0.6", "yiisoft/yii2-bootstrap4" : "^2.0.8",

  • 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',

So, that fixed my navbar. Running my tests pointed me to other problems, but I won't go into those. Now that you know the process, you can find all your errors and fix them in the same way - I need to fix problems in some of my forms where the action buttons are now being rendered in a space too small to be visible. Somehow, those problems don't seem quite so insurmountable anymore.

]]>
0
[extension] buttflattery/yii2-multimenu Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/buttflattery/yii2-multimenu https://www.yiiframework.com/extension/buttflattery/yii2-multimenu omeraslam omeraslam

Yii2-multimenu

  1. What is this repository for?
  2. External Libraries Used
  3. About Bootstrap Version Usage
  4. Demos
  5. Usage
  6. Available Options for the Widget
  7. BigDrop minimal options with brand text
  8. Leftnav Minimal options with brand image
  9. Dropup menu with custom color theme Asset Class
  10. Menu items from Database
  11. Available Constants

What is this repository for?

A Yii2 widget that creates a navigation menu based on the yii\widgets\Menu, and provides you with multiple layout options that include a Big Drop style top navigation, a Left Menu navigation and a Sticky drop-up footer menu just by using this single widget. it uses multilple 3rd party plugins for animating the menu. It provide an extra helper component to build menu from the model.

preview

External Libraries Used

About Bootstrap Version Usage

The extension detects if you are using the yiisoft/yii2-bootstrap or yiisoft/yii2-bootstrap4 and loads the appropriate assets for the extension. It will check first the "yiisoft/yii2-bootstrap4" if it exists then it will load bootstrap4 resources otherwise it will fall back to use bootstrap3. So make sure you are following the correct guide to use the yiisoft/yii2-bootstrap4" and remove the "yiisoft/yii2-bootstrap": "~2.0.0", from you composer.json and change the minimum-stability:"dev" here is the complete guide.

How do I get set up

use composer to install the extension

php composer.phar require  buttflattery/yii2-multimenu "@dev"

or add into the composer.json file under require section

"buttflattery/yii2-multimenu":"@dev"

Demos

Usage

<?php
    echo MultiMenu::widget(
        [
            'activeCssClass' => 'active',
            'items' => Yii::$app->menuhelper->getMenuItems(),
            'brandLabel' => 'Left Navigation',
            'brandImage' => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAB+0lEQVR4AcyYg5LkUBhG+1X2PdZGaW3btm3btm3bHttWrPomd1r/2Jn/VJ02TpxcH4CQ/dsuazWgzbIdrm9dZVd4pBz4zx2igTaFHrhvjneVXNHCSqIlFEjiwMyyyOBilRgGSqLNF1jnwNQdIvAt48C3IlBmHCiLQHC2zoHDu6zG1iXn6+y62ScxY9AODO6w0pvAqf23oSE4joOfH6OxfMoRnoGUm+de8wykbFt6wZtA07QwtNOqKh3ZbS3Wzz2F+1c/QJY0UCJ/J3kXWJfv7VhxCRRV1jGw7XI+gcO7rEFFRvdYxydwcPsVsC0bQdKScngt4iUTD4Fy/8p7PoHzRu1DclwmgmiqgUXjD3oTKHbAt869qdJ7l98jNTEblPTkXMwetpvnftA0LLHb4X8kiY9Kx6Q+W7wJtG0HR7fdrtYz+x7iya0vkEtUULIzCjC21wY+W/GYXusRH5kGytWTLxgEEhePPwhKYb7EK3BQuxWwTBuUkd3X8goUn6fMHLyTT+DCsQdAEXNzSMeVPAJHdF2DmH8poCREp3uwm7HsGq9J9q69iuunX6EgrwQVObjpBt8z6rdPfvE8kiiyhsvHnomrQx6BxYUyYiNS8f75H1w4/ISepDZLoDhNJ9cdNUquhRsv+6EP9oNH7Iff2A9g8h8CLt1gH0Qf9NMQAFnO60BJFQe0AAAAAElFTkSuQmCC',
            'brandUrl' => 'https://yii2plugins.omaraslam.com',
            'activateParents' => true,
            'layoutTemplate'=>'{multimenu}{brand}',
            'enableIcons'=>true,
            'multimenuOptions' => [
                'theme' => MultiMenu::THEME_LEFTNAV,
                'mobileView' => true,
                MultiMenu::THEME_LEFTNAV => [
                    'position'=>MultiMenu::LEFT_NAV_FIXED,
                    'slimScroll' => [
                        'scrollColor' => 'rgba(0,0,0,0.75)'
                    ]
                ]
            ]
        ]
    );
?>

Available Options for the Widget

You can use the available default options for the yii\widgets\Menu along with the following options

$brandImage (string|bool)

Src of the brand image or false if it's not used. Note that this param will override $this->brandLabel param. Default value is false.

$brandLabel (string|bool)

The text of the brand or false if it's not used. Note that this is not HTML-encoded. Default value is false.

$brandOptions (array)

The HTML attributes of the brand link. \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.

$brandUrl (bool)

The URL for the brand's hyperlink tag. This parameter will be processed by [[\yii\helpers\Url::to()]] and will be used for the "href" attribute of the brand link. Default value is false that means [[\yii\web\Application::homeUrl]] will be used. You may set it to null if you want to have no link at all.

$containerCssClasses (array)

The Container HTML options.

$layoutTemplate (string)

The layout template used to draw the menu and rearrange the brand and menu items placement. Default value is {brand}{multimenu}.

$enableIcons (bool)

If default icon should be enabled along with the labels of the menu. It uses the Multmenu::DEFAULT_ICON which has the value <i class="ion-android-menu"></i>

$multimenuOptions (array)

You can pass the plugin specific settings here for the multimenu and settings depending on the type of the menu you are using.

  • theme (string)

    The theme of the menu you want to use, it can be any of the bigdrop,leftnav and dropup, you can use the available constants THEME_BIGDROP, THEME_LEFTNAV and THEME_DROPUP. See available constants section.

  • themeColorFile (string)

    The name space of the theme color file you want to load with the menu you can customize classes for the menu and load via this option. Default value is empty ''. See wiki for creating the theme file.

  • mobileView (bool)

    If enabled, the menu will be responsive in the mobile view. This option comes handy when you want to use 2 menus on the same page and want one of them to be show for the mobile view, you can turn the other one off. Default value is true.

  • enableWavesPlugin (bool)

    If the Waves. plugin should be enabled when clicking on the menu items. Default value is true.

  • wavesEffect (string)

    The color of the waves effect see https://materializecss.com/waves.html#! . Default value is waves-cyan.

  • wavesType (string) circular|default

    The waves type effect, circular or default. Default value is default. See the constants section to see all available effects you can use.

  • mobileBreakPoint (int)

    The mobile view break point where the javascript plugin should recalculate sizes. Default value is 1200. (Note: dont change this option unless you know what you are doing as changing it will require you to update the media queries for the themes too.)

There are menu/theme specific options that are applicable to specific menu types only. See details below.

  • bigdrop (array)

    It accepts the following settings to be used for bigdrop menu only.

    • enableTransitionEffects (bool) : Enable transition effects on the menu, uses animate.css. default is true.

    • transitionEffect (string) : Transition effect to show the menu. default value flipInX . See animate.css for trasition effect types.

    • transitionDelay (string) : Animate speed for the transition "fast"|"faster"|"slow"|"slower"|"". Default value faster. See available constants.

  • dropup (array)

    It accepts the the same set of settings as above for the bigdrop but uses different default values

    • enableTransitionEffects => true
    • transitionEffect => 'fadeIn`
    • transitionDelay => 'slow'
  • leftnav (array)

    It accepts the the same set of settings as above for the bigdrop with different default values, and some extra options for the leftnav.

    • enableTransitionEffects => true

    • transitionEffect => 'fadeIn`

    • transitionDelay => 'slow'

    • position (string) default|fixed|absolute : the position of the left menu, the default value is default and the leftnav will be rendered relative to it's parent container.

    • slimScroll (array) : The options for the slimscroll plugin used for the left nav.

      • scrollColor (string) : The scroll bar color, default value is rgba(0,0,0,0.8).

      • scrollWidth (string) : Width of the scroll bar, default value is 4px.

      • scrollAlwaysVisible (bool) : If true scroll will always be visible, default value is false.

      • scrollBorderRadius : The scroll bar border radius, default value is 0.

      • scrollRailBorderRadius : Sets border radius of the rail, default value is 0.

      • scrollActiveItemWhenPageLoad : If true, will always scroll to the active menu item link after the page loads, default value is true.

Remember when using any on the following menus if you are providing the items array manually, when specifying label for the submenus that further opens a menu you should not leave the url blank but javascript::void(0) as in the examples below.

BigDrop minimal options with brand text

<?php
    use buttflattery\multimenu\MultiMenu;
    $items = [
            ['label' => 'Home', 'url' => ['/site/index']],
            ['label' => 'About', 'url' => ['/site/about']],
            ['label' => 'Contact', 'url' => ['/site/contact']],
            [
                'label' => 'Dropdown',
                'url' => 'javascript:void(0)',
                'items' => [
                    ['label' => 'Level 1  Dropdown A', 'url' => '#'],
                    ['label' => 'Level 1  Dropdown B', 'url' => '#'],
                ],
            ],
            Yii::$app->user->isGuest ? (
                ['label' => 'Login', 'url' => ['/site/login']]
            ) : (
                '<li>'
                . Html::beginForm(['/site/logout'], 'post')
                . Html::submitButton(
                    'Logout (' . Yii::$app->user->identity->username . ')',
                    ['class' => 'btn btn-link logout']
                )
                . Html::endForm()
                . '</li>'
        ),
    ];
    echo MultiMenu::widget(
        [
            'activeCssClass' => 'active',
            'items' => $items,
            'layoutTemplate' => '{multimenu}{brand}',
            'enableIcons'=>true,
            'brandUrl' => 'https://plugins.omaraslam.com',
            'brandLabel'=>'Yii2 Multimenu',
            'activateParents' => true,
            'multimenuOptions' => [
                'theme' => MultiMenu::THEME_BIGDROP,
            ],
        ]
    );

?>

Leftnav Minimal options with brand image

<?php
    use buttflattery\multimenu\MultiMenu;
    $items = [
            ['label' => 'Home', 'url' => ['/site/index']],
            ['label' => 'About', 'url' => ['/site/about']],
            ['label' => 'Contact', 'url' => ['/site/contact']],
            [
                'label' => 'Dropdown',
                'url' => 'javascript:void(0)',
                'items' => [
                    ['label' => 'Level 1  Dropdown A', 'url' => '#'],
                    ['label' => 'Level 1  Dropdown B', 'url' => '#'],
                ],
            ],
            Yii::$app->user->isGuest ? (
                ['label' => 'Login', 'url' => ['/site/login']]
            ) : (
                '<li>'
                . Html::beginForm(['/site/logout'], 'post')
                . Html::submitButton(
                    'Logout (' . Yii::$app->user->identity->username . ')',
                    ['class' => 'btn btn-link logout']
                )
                . Html::endForm()
                . '</li>'
        ),
    ];

    echo MultiMenu::widget(
        [
            'activeCssClass' => 'active',
            'items' => $items,
            'brandLabel' => 'Left Navigation',
            'brandImage' => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAB+0lEQVR4AcyYg5LkUBhG+1X2PdZGaW3btm3btm3bHttWrPomd1r/2Jn/VJ02TpxcH4CQ/dsuazWgzbIdrm9dZVd4pBz4zx2igTaFHrhvjneVXNHCSqIlFEjiwMyyyOBilRgGSqLNF1jnwNQdIvAt48C3IlBmHCiLQHC2zoHDu6zG1iXn6+y62ScxY9AODO6w0pvAqf23oSE4joOfH6OxfMoRnoGUm+de8wykbFt6wZtA07QwtNOqKh3ZbS3Wzz2F+1c/QJY0UCJ/J3kXWJfv7VhxCRRV1jGw7XI+gcO7rEFFRvdYxydwcPsVsC0bQdKScngt4iUTD4Fy/8p7PoHzRu1DclwmgmiqgUXjD3oTKHbAt869qdJ7l98jNTEblPTkXMwetpvnftA0LLHb4X8kiY9Kx6Q+W7wJtG0HR7fdrtYz+x7iya0vkEtUULIzCjC21wY+W/GYXusRH5kGytWTLxgEEhePPwhKYb7EK3BQuxWwTBuUkd3X8goUn6fMHLyTT+DCsQdAEXNzSMeVPAJHdF2DmH8poCREp3uwm7HsGq9J9q69iuunX6EgrwQVObjpBt8z6rdPfvE8kiiyhsvHnomrQx6BxYUyYiNS8f75H1w4/ISepDZLoDhNJ9cdNUquhRsv+6EP9oNH7Iff2A9g8h8CLt1gH0Qf9NMQAFnO60BJFQe0AAAAAElFTkSuQmCC',
            'brandUrl' => 'http://omaraslam.com',
            'activateParents' => true,
            'layoutTemplate'=>'{multimenu}{brand}',
            'enableIcons'=>true,
            'multimenuOptions' => [
                'theme' => MultiMenu::THEME_LEFTNAV,
                'mobileView' => true,
                MultiMenu::THEME_LEFTNAV => [
                    'position'=>MultiMenu::LEFT_NAV_FIXED,
                    'slimScroll' => [
                        'scrollColor' => 'rgba(0,0,0,0.75)'
                    ]
                ]
            ]
        ]
    );
?>

Dropup menu with custom color theme Asset Class

<?php
    use buttflattery\multimenu\MultiMenu;

    $items = [
            ['label' => 'Home', 'url' => ['/site/index']],
            ['label' => 'About', 'url' => ['/site/about']],
            ['label' => 'Contact', 'url' => ['/site/contact']],
            [
                'label' => 'Dropdown',
                'url' => 'javascript:void(0)',
                'items' => [
                    ['label' => 'Level 1  Dropdown A', 'url' => '#'],
                    ['label' => 'Level 1  Dropdown B', 'url' => '#'],
                ],
            ],
            Yii::$app->user->isGuest ? (
                ['label' => 'Login', 'url' => ['/site/login']]
            ) : (
                '<li>'
                . Html::beginForm(['/site/logout'], 'post')
                . Html::submitButton(
                    'Logout (' . Yii::$app->user->identity->username . ')',
                    ['class' => 'btn btn-link logout']
                )
                . Html::endForm()
                . '</li>'
        ),
    ];

    echo MultiMenu::widget(
        [
            'activeCssClass' => 'active',
            'items' => Yii::$app->menuhelper->getMenuItems('menu_name'),
            'activateParents' => true,
            'brandLabel' => 'Yii2 Multimenu',
            'enableIcons'=>true,
            'brandUrl' => 'https://plugins.omaraslam.com',
            'layoutTemplate'=>'{multimenu}{brand}',
            'multimenuOptions' => [
                'theme' => MultiMenu::THEME_DROPUP,
                'themeColorFile' => \app\assets\DropUpBisqueRed::class,
                'mobileView' => true,
                MultiMenu::THEME_DROPUP => [
                    'transitionEffect' => MultiMenu::ANIMATE_DEFAULT,
                ],
            ],
        ]
    );
?>

Your theme asset class code

<?php
// @codingStandardsIgnoreStart
namespace app\assets;

use buttflattery\multimenu\assetbundles\bs3\ThemeDropUpAsset;

class DropUpBisqueRed extends ThemeDropUpAsset
{
    //@codingStandardsIgnoreEnd

    public function init()
    {
        parent::init();
        array_push($this->css, '/css/theme/dropup/bisque-red.css');
    }
}

Menu items from Database

A helper component buttflattery\multimenu\helpers\MenuHelper is added that come in handy if you want to just add all the menu items in the database table and leave the rest on this component to generate the items array for the menu.

If you dont have a menu table added yet you can run the migration and use the model provided buttflattery\multimenu\models\Menu.

Running migrations

Run the following command on terminal to run the migrations

php ./yii migrate/up --migrationPath=@vendor/buttflattery/yii2-multimenu/src/migrations

+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| label     | varchar(50) | NO   |     | NULL    |                |
| link      | text        | YES  |     | NULL    |                |
| parent_id | int(11)     | YES  |     | NULL    |                |
+-----------+-------------+------+-----+---------+----------------+
Configure the Component

Open config/web.php if you are using yii2-basic or the common/config/main.php if you are using advance-app and add the following under the components array.

'components' => [
    'menuhelper' => [
        'class' => 'buttflattery\multimenu\helpers\MenuHelper',
        'model' => 'buttflattery\multimenu\models\Menu',
    ],

If you wish to use your custom model and have a menu table already you can provide your own model namespace and set the field names appropriately for the component to work correctly. See below for a full set of options you can configure for the component.

'components' => [
    'menuhelper' => [
        'class' => 'buttflattery\multimenu\helpers\MenuHelper',
        'model' => 'app\models\Menus',
        'linkField'=>'menu_link',
        'labelField'=>'menu_name',
        'idField'=>'menu_id',
        'parentIdField'=>'menu_parent_id',
    ],

Now you can call the function getMenuItems(orderByField) to get the $items array like below.

<?php
    use buttflattery\multimenu\MultiMenu;

    echo MultiMenu::widget(
        [
            'activeCssClass' => 'active',
            'items' => Yii::$app->menuhelper->getMenuItems(),
            'layoutTemplate' => '{multimenu}{brand}',
            'enableIcons'=>true,
            'brandUrl' => 'https://plugins.omaraslam.com',
            'brandLabel'=>'Yii2 Multimenu',
            'activateParents' => true,
            'multimenuOptions' => [
                'theme' => MultiMenu::THEME_BIGDROP,
            ],
        ]
    );

?>

Available Constants


  /**
  * For use with leftnav `position` option
  */
  const LEFT_NAV_FIXED = 'fixed';
  const LEFT_NAV_ABSOLUTE = 'absolute';
  const LEFT_NAV_DEFAULT = 'default';

  /**
   * Supported Transition effects by animate.css
   * https://daneden.github.io/animate.css/
  */
  const ANIMATE_BOUNCE_IN = 'bounceIn';
  const ANIMATE_BOUNCE_IN_DOWN = 'bounceInDown';
  const ANIMATE_BOUNCE_IN_UP = 'bounceInUp';
  const ANIMATE_BOUNCE_IN_LEFT = 'bounceInLeft';
  const ANIMATE_BOUNCE_IN_RIGHT = 'bounceInRight';

  const ANIMATE_FADE_IN = 'fadeIn';
  const ANIMATE_FADE_IN_DOWN = 'fadeInDown';
  const ANIMATE_FADE_IN_DOWN_BIG = 'fadeInDownBig';
  const ANIMATE_FADE_IN_LEFT = 'fadeInLeft';
  const ANIMATE_FADE_IN_LEFT_BIG = 'fadeInLeftBig';
  const ANIMATE_FADE_IN_RIGHT = 'fadeInRight';
  const ANIMATE_FADE_IN_RIGHT_BIG = 'fadeInRightBig';

  const ANIMATE_FLIP = 'flip';
  const ANIMATE_FLIP_IN_X = 'flipInX';
  const ANIMATE_FLIP_IN_Y = 'flipInY';

  const ANIMATE_LITE_SPEED_IN = 'lightSpeedIn';

  const ANIMATE_ROTATE_IN = 'rotateIn';
  const ANIMATE_ROTATE_IN_DOWN_LEFT = 'rotateInDownLeft';
  const ANIMATE_ROTATE_IN_DOWN_RIGHT = 'rotateInDownRight';
  const ANIMATE_ROTATE_IN_UP_LEFT = 'rotateInUpLeft';
  const ANIMATE_ROTATE_IN_UP_RIGHT = 'rotateInUpRight';

  const ANIMATE_SLIDE_IN_UP = 'slideInUp';
  const ANIMATE_SLIDE_IN_DOWN = 'slideInDown';
  const ANIMATE_SLIDE_IN_LEFT = 'slideInLeft';
  const ANIMATE_SLIDE_IN_RIGHT = 'slideInRight';

  const ANIMATE_ZOOM_IN = 'zoomIn';
  const ANIMATE_ZOOM_IN_UP = 'zoomInUp';
  const ANIMATE_ZOOM_IN_DOWN = 'zoomInDow';
  const ANIMATE_ZOOM_IN_LEFT = 'zoomInLeft';
  const ANIMATE_ZOOM_IN_RIGHT = 'zoomInRight';

  /**
   * delay types for `transitionDelay` option
   * under the menu specific options
   */
  const ANIMATE_DEFAULT = '';
  const ANIMATE_FAST = 'fast';
  const ANIMATE_FASTER = 'faster';
  const ANIMATE_SLOW = 'slow';
  const ANIMATE_SLOWER = 'slower';

  /**
   * Waves plugin effects http://fian.my.id/Waves/#examples
   * used for the option `wavesEffect`
   */
  const WAVES_TYPE_CIRCLE = 'waves-circle';
  const WAVES_TYPE_DEFAULT = 'default';

  const WAVES_LIGHT = 'waves-light';
  const WAVES_RED = 'waves-red';
  const WAVES_PINK = 'waves-pink';
  const WAVES_PURPLE = 'waves-purple';
  const WAVES_DEEP_PURPLE = 'waves-deep-purple';
  const WAVES_INDIGO = 'waves-indigo';
  const WAVES_BLUE = 'waves-blue';
  const WAVES_LIGHT_BLUE = 'waves-light-blue';
  const WAVES_CYAN = 'waves-cyan';
  const WAVES_TEAL = 'waves-teal';
  const WAVES_GREEN = 'waves-green';
  const WAVES_LIGHT_GREEN = 'waves-light-green';
  const WAVES_LIME = 'waves-lime';
  const WAVES_YELLOW = 'waves-yellow';
  const WAVES_AMBER = 'waves-amber';
  const WAVES_ORANGE = 'waves-orange';
  const WAVES_DEEP_ORANGE = 'waves-deep-orange';
  const WAVES_BROWN = 'waves-brown';
  const WAVES_GREY = 'waves-grey';
  const WAVES_BLUE_GREY = 'waves-blue-grey';
  const WAVES_BLACK = 'waves-black';

  /**
   * Themes available used for the `theme` option
   * under `multimenuOptions` option
   */
  const THEME_BIGDROP = 'bigdrop';
  const THEME_LEFTNAV = 'leftnav';
  const THEME_DROPUP = 'dropup';
Who do I talk to?
  • buttflattery@gmail.com
  • yii2plugins@omaraslam.com
]]>
0
[extension] letsjump/yii2-anchorjs Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/letsjump/yii2-anchorjs https://www.yiiframework.com/extension/letsjump/yii2-anchorjs letsjump letsjump

AnchorJS

  1. Overview
  2. Installation
  3. Usage
  4. Application wide configuration

Yii2 wrapper for AnchorJS that add deep anchor links to your Yii docs.

Overview

AnchorJS lets you drop deep anchor links (like these) onto any webpage, and be on your way.

You don't need to set up IDs or worry about urls. AnchorJS will respect your IDs if you have them, and generate them if you don't.

It uses an attractive link icon by default, but you can customize the display via options and CSS styling. The examples demonstrate a few customization ideas.

Finally, AnchorJS is lightweight, accessible, and dependency-free.

Example: The bootstrap manual:

bootstrap

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist letsjump/yii2-anchorjs "*"

or add

"letsjump/yii2-anchorjs": "*"

to the require section of your composer.json file.

Usage

Once the extension is installed, simply add this in any view that need it.

<?= \letsjump\AnchorJS\AnchorJS::widget([
        'add' => 'h1, h2',
        'options' => [
            'placement'=>'right', 
            'icon'=>'',
            // ... refer to https://www.bryanbraun.com/anchorjs/#options for any option available
        ], 
]); ?>

Application wide configuration

You can set an application wide configuration by adding its options to your /app/config/params.php `php <?php

return [

'adminEmail' => 'admin@example.com',
'anchorjs'   => [
    'placement' => 'left',
    'icon' => ''
    // ... refer to https://www.bryanbraun.com/anchorjs/#options for any option available
],

]; `

]]>
0
[extension] startpl/yii2-nested-sets-menu Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/startpl/yii2-nested-sets-menu https://www.yiiframework.com/extension/startpl/yii2-nested-sets-menu koperdog koperdog

Nested Sets Menu Widget

  1. Installation
  2. Usage

Widget to display nested sets as Menu

PackagistPackagist Version

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist startpl/yii2-nested-sets-menu "*"

or add

"startpl/yii2-nested-sets-menu": "*"

to the require section of your composer.json file.

Usage

A basic usage looks like the following: `php

  • <?= Menu::widget([
  • 'items' => \startpl\yii2NestedSetsMenu\services\MenuArray::getData($data), // $data is your models|rows
  • 'options' => ['id'=>'main-menu', 'class' => 'menu'],
  • 'encodeLabels'=>false,
  • 'activateParents'=>true,
  • 'activeCssClass'=>'active',
  • ]);?>
  • `

Also you can extending of MenuArray, NestedSetsTreeMenu for fine-tune your data
]]>
0
[news] ApiDoc extension version 2.1.3 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/273/apidoc-extension-version-2-1-3-released https://www.yiiframework.com/news/273/apidoc-extension-version-2-1-3-released samdark samdark

We are very pleased to announce the release of the ApiDoc extension version 2.1.3.

This release has two important bugfixes about @inheritdoc and getter/setter methods. Also it contains two enhancements:

  • @since tags are now propagated to inherited methods/properties in the same package.
  • HTTPS is now used for www.php.net links.

See the CHANGELOG for a full list of changes.

Thank you, Brandon Kelly and Somogyi Márton for this release pull requests.

]]>
0
[news] Auth Client extension 2.2.7 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/272/auth-client-extension-2-2-7-released https://www.yiiframework.com/news/272/auth-client-extension-2-2-7-released samdark samdark

We are very pleased to announce the release of Auth Client extension version 2.2.7.

This release updates GitHub token transfer method since GitHub deprecated old way to transfer a token.

Note: Make sure to update your project before July 1st, 2020. That is when GitHub will stop supporting old token trafer method.

]]>
0
[extension] sjaakp/yii2-datepager Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/sjaakp/yii2-datepager https://www.yiiframework.com/extension/sjaakp/yii2-datepager sjaakp sjaakp

yii2-datepager

  1. Installation
  2. Using Datepager
  3. Classes
Paging on date values for Yii2

Latest Stable Version Total Downloads License

These classes enable database paging based on date values in the Yii 2.0 PHP Framework.

A demonstration of yii2-datepager is here.

Installation

The preferred way to install yii2-datepager is through Composer. Either add the following to the require section of your composer.json file:

"sjaakp/yii2-datepager": "*"

Or run:

composer require sjaakp/yii2-datepager "*"

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

Using Datepager

Using Yii2 Datepager is easy. A minimum usage scenario would look like the following. In EventController.php we would have something like:

<?php
use sjaakp\datepager\ActiveDataProvider;

class EventController extends Controller
{
	// ...

	public function actionIndex()    {
        $dataProvider = new ActiveDataProvider([
            'query' => Event::find(),
            'dateAttribute' => 'date'
        ]);

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

	// ... more actions ...
}

The corresponding view file index.php could look something like:

<?php
use sjaakp\datepager\DatePager;
?>

<?= DatePager::widget([
    'dataProvider' => $dataProvider
]) ?>

<?= GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        'date',
        'title',
        // ... more columns ...
    ],
]); ?>

Classes

ActiveDataProvider

This is an extension from its Yii-counterpart in yii\data, and can be used in the same way. It is important to set attribute $dateAttribute.

$dateAttribute

string Set this to the name of the attribute which is used to define the pages. Must be set.

$interval

string Defines the interval of the pages. Must be set in a form PHP's DateInterval can understand. Default: 'P1Y' (one year).

$beginDate

string Date of the first Datepager page. If not set (default), Datepager determines this date itself. It can be set to any date within the range of the first page. Format: any PHP date format.

$endDate

string Date of the last Datepager page. If not set (default), Datepager determines this date itself. It can be set to any date within the range of the last page. Format: any PHP date format.

$dateParam

string The Datepager HTML parameter name. Default value: 'date'. Might be changed if there is a conflict with other functionality.

DatePager

This is the widget that renders the actual datepager. The attribute $dataProvider must be set.

$dataProvider

The Datepager ActiveDataProvider that this pager is associated with. Must be set.

$maxButtonCount

int The maximum number of page buttons rendered. Default: 10.

$labelFormat

null|string|callable Defines the format of the page label.

  • If null: DatePager determines the format based on the dataprovider's interval.
  • If string: a date format according to Yii's Fomrmatter::dateformat.
  • If callable: a function($page, $datePager), returning a string, where $page is a PHP DateTimeInterface.
$options

array HTML options for the datepager container tag. Default: [ 'class' => 'pagination' ], compatible with Bootstrap.

$buttonOptions

array HTML options for the datepager buttons. Default: [ 'class' => 'page-item' ], compatible with Bootstrap 4.

$linkOptions

array HTML options for the datepager links. Default: [ 'class' => 'page-link' ], compatible with Bootstrap 4.

$activePageCssClass

string CSS class of the active page. Default: 'active'.

$disabledPageCssClass

string CSS class of a disabled page. Default: 'disabled'.

$prevPageLabel, $nextPageLabel

'string' Text labels for the previous and next buttons, will not be HTML encoded. If false, the button will not be rendered. Defaults: '&laquo;' and '&raquo;'.

$firstPageLabel, $lastPageLabel

'string' Text labels for the first and last buttons, will not be HTML encoded. If false (default), the button will not be rendered.

All properties, except $dataProvider and $labelFormat are direct equivalents of their counterparts in Yii's LinkPager.

]]>
0
[news] Twig extension 2.2.2 and 2.3.0 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/271/twig-extension-2-2-2-and-2-3-0-released https://www.yiiframework.com/news/271/twig-extension-2-2-2-and-2-3-0-released samdark samdark

We are very pleased to announce two releases of Twig extension.

Version 2.2.2 fixes an issue with non-working HtmlHelperExtension, version 2.3.0 upgrades Twig to 2.7.x.

]]>
0
[extension] mgrechanik/yii2-seo-categories Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/mgrechanik/yii2-seo-categories https://www.yiiframework.com/extension/mgrechanik/yii2-seo-categories Pathfinder Pathfinder

Active Record hierarchical SEO categories and tags for Yii2 framework

  1. Table of contents
  2. Goal
  3. or `slug` fields and they will not appear in the web form of creating/editing SEO category
  4. Installing

Русская версия

Table of contents

Goal

This extension gives you a variation of categories module, in which the opportunity to create any your own `Active Record` category models was given.

We suggest that when creating pages at frontend to display associated content of the category (or tag) we would need to manage SEO information of such category page.

Respectively we add next fields to our SEO category `Active Record` model:

  • 
    
  • 
    
  • 
    
  • 
    
  • 
    
  • 
    

With all this in module's settings you may choose not to use `meta_other`

or `slug` fields and they will not appear in the web form of creating/editing SEO category

Installing

Installing through composer:

The preferred way to install this extension is through composer.:

Either run composer require --prefer-dist mgrechanik/yii2-seo-categories

or add "mgrechanik/yii2-seo-categories" : "~1.0.0" to the require section of your composer.json

Migrations

This extension comes with two migrations:

  • the first creates SEO categories table with all indexes needed
  • the second creates unique index for `slug` field

You can run both of them:

php yii migrate --migrationPath=@vendor/mgrechanik/yii2-seo-categories/src/console/migrations

, or when you do not use `slug` field run only the first migration:

php yii migrate 1 --migrationPath=@vendor/mgrechanik/yii2-seo-categories/src/console/migrations
Setting the module up

As was mentioned in the basic categories module, this module follows the approach of universal module, and since it gives you only backend pages when you set it up into your application specify the next `mode:php

'modules' => [
    'seocategory' => [
        'class' => 'mgrechanik\yii2seocategory\Module',
        'mode' => 'backend',
        // Other module settings
    ],
    // ...
],

Done. When you access ```/seocategory``` page you will see all your SEO categories in a form of tree.

---

## Module settings <span id="settings"></span>

[Setting up](#setup) the module into application, along with all properties of the [base categories module](https://github.com/mgrechanik/yii2-categories-and-tags#settings), we can use it's next properties:

#### ```$useMetaOtherField = false``` 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Whether to use ```other meta tags``` field

#### ```$useSlugField = true``` 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Whether to use ```slug``` field. It is supposed to be unique

#### ```$slugPattern``` 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- When we use previous field in this property we set up regular expression of expected symbols 

#### ```$showTitleColumnAtIndexPage = true```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Whether to display ```title``` field in the categories list grid

#### ```$showSlugColumnAtIndexPage = false``` 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Whether to display ```slug``` field in the categories list grid
]]>
0
[extension] mgrechanik/yii2-categories-and-tags Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/mgrechanik/yii2-categories-and-tags https://www.yiiframework.com/extension/mgrechanik/yii2-categories-and-tags Pathfinder Pathfinder

Active Record hierarchical categories (or tags) for Yii2 framework

  1. Table of contents
  2. Goal
  3. Demo
  4. Installing

Русская версия

Table of contents

Goal

This extension gives you the module with the next functionality:

  1. It connects `Active Recordmodels of one table to a tree according toMaterialized path` algorithm using this extension
  2. You can use your own `ActiveRecord` model with fields you need by inheriting it from base category model of this extension. Details
  3. This module follows approach of universal module
  4. In fact you will have a set of `Active Recordmodels connected into a tree withCRUD` operations with them at backend section

    • This module gives no frontend section since we are not aware of what will be put into category
    • It will also fit to serve for tags system (if they are organized hierarchically)
    • Functionality of `CRUD` pages provides a possibility to set up/change a position of each node in the tree to any valid position
    • The futher work with a category tree is meant by using Materialized path extension ! Example
    • The index page of viewing a categories tree assumes that all category needs to be displayed, without pagination or filtering

Demo

The functionality of backend section will look like: Functionality of category we get

Installing

Installing through composer:

The preferred way to install this extension is through composer.:

Either run composer require --prefer-dist mgrechanik/yii2-categories-and-tags

or add "mgrechanik/yii2-categories-and-tags" : "~1.0.0" to the require section of your composer.json

Migrations

If you do not need additional fields to category `Active Record` model (details) then the table for default category can be created by running:

php yii migrate --migrationPath=@vendor/mgrechanik/yii2-categories-and-tags/src/console/migrations
Setting the module up

As was mentioned before this module follows the approach of universal module, and since it gives you only backend pages when you set it up into your application specify the next `mode:php

'modules' => [
    'category' => [
        'class' => 'mgrechanik\yii2category\Module',
        'mode' => 'backend',
        // Other module settings
    ],
    // ...
],

Done. When you access ```/category``` page you will see all your categories in a form of a tree.

---

## Default AR category model of this extension  <span id="default-ar"></span> 

The **required** fields for category model are ```id, path, level, weight ``` (`id` is the **primary key**), 
they serve to saving tree position. The rest of the fields are ones you need.

If you are satisfied with only one additional text field - ```name``` - then this extension provides
[Category](https://github.com/mgrechanik/yii2-categories-and-tags/blob/master/src/models/Category.php) model which is set as the default category model of the module.

The work precisely with it is shown at [demo](#demo) above.


---

## Using your own AR model  <span id="custom-ar"></span>   

If having one additional ```name``` field [default](#default-ar) category model gives is not enough 
there is a way to use your own model with fields you need which will serve as a category model.

To do this you need to follow the next steps:

#### А) Setting up your AR model <span id="custom-ar-a"></span>

1) Generate the class of your AR model starting from table created by migration similar to [Category model migration](https://github.com/mgrechanik/yii2-categories-and-tags/blob/master/src/console/migrations/m180908_094405_create_category_table.php). The main point here are [required](#default-ar) fields

2) Change the code of your AR model exactly like we did the same with [Category](https://github.com/mgrechanik/yii2-categories-and-tags/blob/master/src/models/Category.php) model: 
* change the table name
* make it to be inherited from ```BaseCategory``` class
* Set up your additional fields in ```rules(), attributeLabels()```

3) Set up your module to use this category model by using it's ```$categoryModelClass``` property

4) If your model does not have ```name``` field you need to set up [```$indentedNameCreatorCallback```](#indented-name) module property

#### B) Setting up your category form model <span id="custom-ar-b"></span>

AR model and form model are separated so the steps similar to **A)** need to be performed to your form model.

1) Create your form model starting from [CategoryForm](https://github.com/mgrechanik/yii2-categories-and-tags/blob/master/src/ui/forms/backend/CategoryForm.php). 
In the default form we added only one field - ```name``` but you need to add your own. Do not forget about inheritance from ```BaseCategoryForm```

2) Set up your module to use this category form model by using it's ```$categoryFormModelClass``` property

#### C) Setting up views <span id="custom-ar-c"></span>

This module has an opportunity to set up [which views to use](#setup-views).

The ones of them with information which vary needs to be copied, changed as needed and set up to module.

#### Ready to use examples of this module variations <span id="custom-ar-examples"></span>

Nowadays there are the next variations of this module:
* [SEO categories](https://github.com/mgrechanik/yii2-seo-categories)

---

## Module settings <span id="settings"></span>

[Setting up](#setup) the module into application we can use it's next properties:

#### ```$categoryModelClass``` 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Which category AR model class to use

#### ```$categoryFormModelClass``` 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Which category form model class to use

#### ```$indentedNameCreatorCallback``` <span id="indented-name">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Callback which will create the label of the category at the categories page
considering indent needed to show categories as a tree

#### ```$categoryIndexView```, ```$categoryCreateView```, ```$categoryUpdateView```, ```$categoryFormView```, ```$categoryViewView``` <span id="setup-views"></span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- The corresponding **views** for module to use. 
For it's format look into [documentation](https://www.yiiframework.com/doc/api/2.0/yii-base-view#render()-detail)

#### ```$redirectToIndexAfterCreate``` 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Whether to redirect to the categories page after new element has been created.  
```True``` by default. With ```false``` the redirect will be to category view page

#### ```$redirectToIndexAfterUpdate``` 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Similar to the previous property but for updation task

#### ```$validateCategoryModel``` 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- Whether to validate category model before saving.  
Default ```false``` when we consider that the validation form performes is enough

#### ```$creatingSuccessMessage```, ```$updatingSuccessMessage```, ```$deletingSuccessMessage``` 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- The texts of flash messages.  
If you change them do not forget about their translations in the ```yii2category``` source


---

## Example of displaying a categories tree at frontend <span id="frontend-output"></span>

If you need to output your categories tree into any template just run:
```php
use mgrechanik\yiimaterializedpath\ServiceInterface;
// This is our default category model:
use mgrechanik\yii2category\models\Category;
use mgrechanik\yiimaterializedpath\widgets\TreeToListWidget;

// get the trees managing service
$service = \Yii::createObject(ServiceInterface::class);
// Get the element relevant to who we build the tree.
// In our case it is the Root node
$root = $service->getRoot(Category::class);
// Build the tree from descendants of the Root node
$tree = $service->buildDescendantsTree($root);
// Print at the page
print TreeToListWidget::widget(['tree' => $tree]);

You will see the next tree:

  • Laptops & PC
    • Laptops
    • PC
  • Phones & Accessories
    • Smartphones
      • Android
      • iOS
    • Batteries
]]>
0
[extension] sjaakp/yii2-wordcount Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/sjaakp/yii2-wordcount https://www.yiiframework.com/extension/sjaakp/yii2-wordcount sjaakp sjaakp
  1. Yii2-wordcount
  2. Installation
  3. Using WordCount
  4. Totals

Yii2-wordcount

Word count behavior for Yii2

Latest Stable Version Total Downloads License

This is a word counting behavior for ActiveRecords in the Yii 2.0 PHP Framework. It counts the words in one or more designated attributes. The count(s) are exposed through new virtual attributes.

A demonstration of yii2-wordcount is here.

Installation

The preferred way to install yii2-wordcount is through Composer. Either add the following to the require section of your composer.json file:

"sjaakp/yii2-wordcount": "*"

Or run:

composer require sjaakp/yii2-wordcount "*"

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

Using WordCount

WordCount is a Behavior for an ActiveRecord. It has one property:

  • $attribute string|array The name of the attribute of wihich we want to count the words. Can also be an array of attribute names. Moreover, it can be an array with '<attrName>' => '<countAttrName>' elements.

If the count attribute name is not explicitly set in the $attribute array, the virtual count attribute is called '<attrName>_count' automatically.

Here is the simplest way to set up an ActiveRecord with WordCount:

namespace app\models;

use yii\db\ActiveRecord;
use sjaakp\wordcount\WordCount;

class Article extends ActiveRecord
{
    public static function tableName()
    {
        return 'article';
    }
    // ...
        
    public function behaviors()
    {
        return [
            [
                'class' => WordCount::class,
                'attribute' => 'bodytext'
            ],
            // ... other behaviors ...
        ];
    }
    // ...
}

Class Article will now have a new virtual attribute with the name 'bodytext_count'. It's value is an integer and it can be queried just like any other attribute:

$wordsCounted = $model->bodytext_count

A slightly more involved way to set up an Activerecord with WordCount would be:

 // ...     
 class Article extends ActiveRecord
 {
     // ...
         
     public function behaviors()
     {
         return [
             [
                 'class' => WordCount::class,
                 'attribute' => [
                    'bodytext' => 'textcount',
                    'title' => 'titlecount'
                 ]
             ],
             // ... other behaviors ...
         ];
     }
     // ...
 }

It gives two new virtual attributes, named 'textcount' and 'titlecount'.

Notice that WordCount uses the PHP function str_word_count(). This is not the most perfect way to count words, so you should consider the results as no more than good approximations.

Totals

Totals is a helper class with one method:

public static function count($query, $attribute)

This static function returns the total of $attribute values in the ActiveRecords found by ActiveQuery $query. If $attribute is a string, the return value will be an integer. If $attribute is an array of attribute names, count() will return an array with '<attr>' => <total> elements.

Usage example:

use sjaakp\wordcount\Totals;

$totals = Totals::count(Article::find(), [ 'titlecount', 'textcount' ]);

Notice that count() also works with non-virtual attributes. However, it would be much wiser to use ActiveQuery::sum() in that case.

]]>
0
[news] Yii 2.0.32 Wed, 22 Jan 2020 11:31:32 +0000 https://www.yiiframework.com/news/270/yii-2-0-32 https://www.yiiframework.com/news/270/yii-2-0-32 samdark samdark

We are very pleased to announce the release of Yii Framework version 2.0.32. Please refer to the instructions at https://www.yiiframework.com/download/ to install or upgrade to this version.

Version 2.0.32 is a minor release of Yii 2.0 that fixes 14 issues and makes two enhancements:

  • Path alias support was added to \yii\web\UploadFile::saveAs()
  • Added support for aria attributes to \yii\helpers\BaseHtml::renderTagAttributes()

Among bug fixes, there is one that changes HTTP response code when content type negotiation fails. Previously it was "415 Unsupported Media Type", now it is "406 Not Acceptable".

Additionally, there are adjustments in application templates. Advanced application template got updated Codeception configuration to match newer version used. Basic application template raised minimum PHP requirement to version 5.6.

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
[extension] undefinedor/smart-rest Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/undefinedor/smart-rest https://www.yiiframework.com/extension/undefinedor/smart-rest undefinedor undefinedor

SmartRest

  1. Purpose
  2. Installation
  3. Usage

Purpose

The controller of `yii\rest\urlRule` must be set. So you have to set this property when you add a controller. This project sloved it.

Installation

The preferred way to install this extension is through Composer . composer require undefinedor/smart-rest

Usage


 'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'enableStrictParsing' => true,
            'rules' => [
                ['class' => 'SmartRest\UrlRule'],
            ],
        ],

]]>
0
[news] Debug extension 2.1.13 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/269/debug-extension-2-1-13-released https://www.yiiframework.com/news/269/debug-extension-2-1-13-released samdark samdark

Debug extension version 2.1.13 was released. This release fixes an error in Dump panel.

See the CHANGELOG for details.

]]>
0
[news] Gii extension 2.1.4 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/268/gii-extension-2-1-4-released https://www.yiiframework.com/news/268/gii-extension-2-1-4-released samdark samdark

We are very pleased to announce the release of Gii extension version 2.1.4.

This release fixes relational query getter documentation style and adds relation's phpdoc hints with target ActiveQuery class to model generator.

See the CHANGELOG for details.

]]>
0
[news] Yii 1.1.22 is released Fri, 17 Jan 2020 09:02:51 +0000 https://www.yiiframework.com/news/267/yii-1-1-22-is-released https://www.yiiframework.com/news/267/yii-1-1-22-is-released samdark samdark

We are very pleased to announce that Yii Framework version 1.1.22 is released. You can download it at yiiframework.com/download/.

This release is a release of Yii 1.1 that has reached maintenance mode and will, only receive necessary security fixes and fixes to adjust the code for compatibility with PHP 7 if they do not cause breaking changes. This allows you to keep your servers PHP version up to date in the environments where old Yii 1.1 applications are hosted and stay within the version ranges supported by the PHP team.

Yii 1.1.22 provides additional PHP 7 compatibility fixes and is compatible with PHP 7.4.

We recommend to use Yii 2.0 for new projects as well as introducing Yii 2.0 for developing new features in existing Yii 1.1 apps, as described in the Yii 2 guide. Upgrading a whole app to Yii 2.0 will, in most cases, result in a total rewrite so this option provides a way for upgrading step by step and allows you to keep old applications up to date even with low budget.

For the complete list of changes in this release, please see the change log. For upgrading, always make sure to read the upgrade instructions, however in this release there are no changes that require changes.

We would like to express our gratitude to all contributors who have spent their precious time helping improve Yii and made this release possible.

]]>
0
[extension] sjaakp/yii2-locator Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/sjaakp/yii2-locator https://www.yiiframework.com/extension/sjaakp/yii2-locator sjaakp sjaakp
  1. Yii2-locator
  2. Installation
  3. Usage
  4. Methods
  5. Properties
  6. Tile Names
  7. Geocoder Names
  8. Marker Types

Yii2-locator

Leaflet-wrapper for Yii2 PHP framework

Latest Stable Version Total Downloads License

This is a wrapper of the Leaflet JavaScript geomapping library for the Yii 2.0 PHP Framework. It's an Yii2 Widget that can be used to display geographical data stored in an ActiveRecord, as well as to update it. Yii2-locator optionally has a search facility. It can use several providers, for the map tiles as well as for the geocoding service.

A demonstration of yii2-locator is here.

Installation

The preferred way to install yii2-locator is through Composer. Either add the following to the require section of your composer.json file:

"sjaakp/yii2-locator": "*"

Or run:

composer require sjaakp/yii2-locator "*"

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

GeoJSON

Yii2-locator handles data in GeoJSON format. Some databases store these directly. Others, like MySQL and MariaDB, use their own format for spatial data. My Yii2-spatial extension can be used to transform MySQL format to GeoJSON and vice versa. In that case, the model should be extended from sjaakp\spatial\ActiveRecord in stead of the usual yii\db\ActiveRecord.

Usage

A typical usage scenario is like this: suppose we have a database table with some geographical data, let's say the table tower. If we use MySQL or MariaDB, the model Tower is extended like this:

class Tower extends sjaakp\spatial\ActiveRecord    {
    public static function tableName()
    {
        return 'tower';
    }
    // ...
}    

The table tower has, among others, the following fields:

  • location: POINT the location of the tower,
  • mapcenter: POINT the center of the map,
  • mapzoom: int the zoom level of the map.
View

In a yii2 view, displaying a map of a tower is simple as this:

<?php
use sjaakp\locator\Locator;
/**
 * @var app\models\Tower $model
 */
?>
...
<?php
    $map = Locator::begin([
        'height' => 480,
        // ... other options ...
    ]);
    
    $map->modelCenter($model, 'mapcenter'); // set the map's center
    
    $map->modelZoom($model, 'mapzoom'); // set the map's zoom level

    $map->modelFeature($model, 'location'); // place a marker at the tower's location

    Locator::end();
?>
...
Index

Displaying a map with all the towers in, say, the index view, can be accomplished with:

<?php
use sjaakp\locator\Locator;
/**
 * @var yii\data\ActiveDataProvider $dataProvider
 */
?>
...
<?php 
    $map = Locator::begin([
        'leafletOptions' => [
            'center' =>  [48.8, 2.3],   // Paris
            'zoom' => 5,
            // ... more options ...
        ],
    ]);

    $map->modelFeatures($dataProvider, 'location'); // provide the tower locations

    Locator::end();
?>
...
Active Locator

In a create or update view, Locator can be used in a form:

<?php
use yii\widgets\ActiveForm;
use sjaakp\locator\Locator;
/**
 * @var app\models\Tower $model
 */
?>
...
<?php $form = ActiveForm::begin(); ?>
...
<?php
    $map = Locator::begin([
        // ... Locator options ...
    ]);
    
    $map->activeCenter($model, 'mapcenter'); // allow the map's center to be chenged

    $map->activeZoom($model, 'mapzoom'); // allow the map's zoom level to be changed

    $map->activeMarker($model, 'location'); // allow the model's location to be changed

    $map->finder(); // add an interactive Search control to the map

    Locator::end();
?>
...
<?php ActiveForm::end(); ?>
...

Methods

  • tileLayer($data) - Add a tile to the map. $data: string|array: tile provider name, or name with options. See Tile names. Return: $this.
  • center($lat, $lng = null) - Set the center of the map. $lat and $lng are the latitude and longitude, float. $lat can also be an array [<lat>, <lng>]. Return: $this.
  • modelCenter($model, $attribute) - Set the center of the map to the value of $attribute in $model. This should be a GeoJSON Feature. Return: $this.
  • activeCenter($model, $attribute) - Create an ActiveField for the center of the map, coupled to the value (a GeoJSON Feature) of $attribute in $model. Return: $this.
  • zoom($z) - Set the zoom level of the map. $z: integer. Return: $this.
  • modelZoom($model, $attribute) - Set the zoom level of the map to the value of $attribute in $model. Return: $this.
  • activeZoom($model, $attribute) - Create an ActiveField for the zoom level of the map, coupled to the value of $attribute in $model. Return: $this.
  • feature($feature) - Add a GeoJSON Feature to the map. Return: $this.
  • modelFeature($model, $attribute) - Add the value of $attribute in $model as a GeoJSON Feature to the map. Return: $this.
  • modelFeatures($dataProvider, $attribute) - Add multiple GeoJSON features to the map, provided by ActiveDataProvider $dataProvider, using attribute $attribute. Return: $this.
  • marker($lat = null, $lng = null, $options = [ ]) - Add marker to the map. Return: $this.

    • If $lat == null: marker appears at the first click point on the map.
    • If $lat and $lng are floats: these are the latitude and longitude.
    • If $lat is an array: [<latitude>, <longitude>].

    $options Options for the marker:

    • 'type': the type of the marker. If not set: 'Marker'.
    • Other options are passed to the marker's constructor.
  • modelMarker($model, $attribute, $options = [ ]) - Set the location of the marker to the value (a GeoJSON Feature) of $attribute in $model. Return: $this.
  • activeMarker($model, $attribute, $options = [ ]) - Create an ActiveField for the marker location, coupled to the value of $attribute in $model. Return: $this.
  • geocoder($options) - Set the geocoder of the map. Return: $this.

    • $options is string: the [name](#geocoder names) of the geocoder provider.
    • $options is array: first item is name, rest are geocoder options.
  • finder($geocoder = null, $position = 'topright') - Add a Search Control, using $geocoder, to the map, with specified position. Return: $this.
  • getVar() - Get the name of the JavaScript variable assigned to the Leaflet map. For advanced uses.

Locator is an Yii2 Widget, so it inherits all of its methods.

Chainable

Most of Locator's methods return this, so they are chainable. This means that the absolute minimum code to display a map in a view would be something like:

<?php
use sjaakp\locator\Locator;

...
<?php
    Locator::begin([
        // ... options ...
    ])->modelCenter($model, 'mapcenter')
        ->modelZoom($model, 'mapzoom')
        ->modelFeature($model, 'location')
        ->end();
?>
...

Properties

  • $height int|string|false Height of the Locator element. If int in pixels, if string any other valid CSS-value. If false, the height is not set. Notice that in that case the height must be set with some other means, otherwise the map will have a height of zero, and be invisible. Default: 400.
  • $tile string|array Name or configuration of the first tile layer. Default: 'OpenStreetMap'.
  • $marker array Type and options for the default marker. Default: [ 'type' => 'DotMarker' ].
  • $options array HTML options of the map container. Use this to explicitly set the ID. Default: [ ] (empty array).
  • $leafletOptions array JavaScript options of the map. Default: [ ] (empty array).
  • $cluster null|true|array Options for MarkerClusterer. If null: no clustering. If true: clustering with default options. Default: null.
  • $popup null|true|array Options for popups. If null: no popups. If true: popups with default options. Default: null.
  • $scale null|int Display a Scale Control on the map. Can be null (no Scale Control), SCALE_METRIC, SCALE_IMPERIAL or SCALE_BOTH. Default: SCALE_METRIC.
  • $urlTemplate string URL template used when marker is clicked. If not set, nothing happens. If $popup is set, a popup is shown with contents from the resulting URL. Otherwise a jump is performed to the URL. '{xxx}' is replaced by the Marker option with the name 'xxx'. Typical use: $urlTemplate = 'view/{id}'. Default: null.
  • $fly bool Whether to use 'fly-animation' when a Marker is placed after find.
  • $tileNamespace string Namespace of the Tile* classes, defining the tile layers. Use this to add your own tile layer.

Locator is an Yii2 Widget, so it inherits all of its properties.

Tile Names

Locator retrieves its map tiles from a tile provider or map provider. Tiles are identified by the name of the provider, or by an array with the name as the first item and options in the rest of the array. This value is used in the $tile property, and in the tileLayer() method. A map can have more than one tile layers, which make sense if they are partly transparent, like the tiles from OpenSeaMap.

Some providers offer tiles in a few variants. They are indicated with a suffix to the provider name, seperated by a dot. For example: 'OpenStreetMap' and 'OpenStreetMap.BlackAndWhite'.

Commercial tile providers expect some sort of API key. This should be added to the options. Often, an API key can be obtained free of charge for small or non-commercial applications.

Out of the box, Locator supports several tile providers. They each have a PHP class file in the src/tiles directory. Currently, the following tile providers are supported (there may be more in the future):

NameVariantsRequired option
OpenStreetMapBlackAndWhite, HOT
OpenMapSurferRoads, Hybrid, AdminBounds, ContourLines, Hillshade
OpenTopoMap
OpenSeaMap
Wikimedia
CartoLight, Dark, Voyager
StamenToner, TonerBackground, TonerHybrid, TonerLines, TonerLabels, TonerLite, Watercolor, Terrain, TerrainBackground, TerrainLabels
EsriWorld
Herelots (see TileHere.php) [ 'apiKey' => '...' ]
TomTomBasic, Hybrid, Labels[ 'key' => '...' ]
Kadaster (Netherlands only)grijs, pastel
Amsterdamlight, zw

If $tile is not set, Locator uses tiles from OpenStreetMap.

Geocoder Names

Locator's Search functionality uses information from a geocoding service. The service is set by the first parameter of the finder() method. This can be a string which is the name of the geocoding service, or an array with the name as first item, followed by options.

Generally, there will be no options, apart from the API key some providers expect. Other options may be added.

Currently, Locator supports the following providers (there may be more in the future):

NameRequired option
Nominatim, by OpenStreetMap
GeoNames[ 'username' => '...' ]
Here[ 'apiKey' => '...' ]
TomTom[ 'key' => '...' ]
Kadaster (Netherlands only)

Notice that some providers may stipulate that you should use their service only on map tiles of the same provider.

If you don't explicitly set a geocoder, Leaflet-search uses Nominatim.

Marker Types

In the property $marker, and in methods like marker(), modelMarker() etc. the marker type can be set. This is an array with [ 'type' => '<marker type>' ], supplemented with marker options (dependent on the type). For instance:

$map->marker = [ 'type' => 'Marker', 'opacity' => 0.5 ]

Apart from Leaflet's own Marker and CircleMarker, Locator sports two other markers:

DotMarker

A simple extension of CircleMarker. It has fixed radius and always has (at least) the class name 'dot-marker'. The default marker of Locator is a DotMarker.

SpriteMarker

A marker with a DivIcon. Use this to display FontAwesome markers like so:

$map->marker = [ 'type' => 'SpriteMarker', 'html' => '<i class="far fa-2x fa-dog"></i>' ]
]]>
0
[extension] mehulpatel/vspl Thu, 16 Jan 2020 05:10:11 +0000 https://www.yiiframework.com/extension/mehulpatel/vspl https://www.yiiframework.com/extension/mehulpatel/vspl mehulpatel mehulpatel

mehulpatel/vspl

  1. Installation
  2. Usage

Yii2 audit record and database changes details

Installation

The preferred way to install this extension is through composer.

Either run

composer require --prefer-dist mehulpatel/vspl:"dev-master"

or

php composer.phar require --prefer-dist mehulpatel/vspl:"dev-master"

or add

"mehulpatel/vspl": "dev-master"

to the require section of your composer.json file.

Migration or Table
you need to import "audit_entry.sql" directly in your DB.
Module

Add Audit Entry module in your config file

....
'modules' => [
    ......
    'auditlog' => [
                'class' => 'mehulpatel\mod\audit\AuditEntryModule'
    ],
    ......
],
....
Component

Add DateTimeHelper components in your config file

....
'components' => [
    ......
    'dateTimeConversion' => [
                'class' => 'mehulpatel\mod\audit\components\DateTimeHelper'
    ],
    ......
],
....

Usage

Use get audit log activities or records, attached "AuditEntryBehaviors" with your models as belows:

use mehulpatel\mod\audit\behaviors\AuditEntryBehaviors;
use yii\db\ActiveRecord;

class User extends ActiveRecord {

    public function behaviors(){
        return [ 
            ....
            'auditEntryBehaviors' => [
                'class' => AuditEntryBehaviors::class
             ],
             ....
        ];
    }
}
]]>
0
[extension] koperdog/yii2-sitemanager Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/koperdog/yii2-sitemanager https://www.yiiframework.com/extension/koperdog/yii2-sitemanager koperdog koperdog

Settings Module

  1. Installation
  2. Usage

Settings for multilanguage, multidomain site

PackagistPackagist Version

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist koperdog/yii2-sitemanager "*"

or add

"koperdog/yii2-sitemanager": "*"

to the require section of your composer.json file.

Add the component to your common config:

...
'components' => [
    // ...
    'settings' => [
        'class' => 'koperdog\yii2sitemanager\components\Settings',
    ],
    // ...
]
... 

also you should add component to bootstrap config:

...
'bootstrap' => ['settings'],
...

and add the module to backend config:

'modules' => [
    'manager' => [
        'class' => 'koperdog\yii2sitemanager\Module',
    ],
],

Then start the migration (console): `php php yii migrate --migrationPath=@vendor/koperdog/yii2-sitemanager/migrations `

Usage

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

autoloaded settings: `php \Yii::$app->params['setting_name']; `

If you are not sure if the setting is autoload: `php \Yii::$app->settings->get('setting_name'); `

CRUD and URL config #### CRUD settings: go to /manager #### CRUD domains: go to /manager/domains #### CRUD languages: go to /manager/languages also, if you want use standart CRUD, you can add to Url rule config: ```php // ... 'rules' => [ 'manager' => 'manager/default/index', 'manager//' => 'manager//', 'manager/' => 'manager//index', 'manager/' => 'manager/default/', ], // ... ``` ]]>
0
[extension] xandrkat/yii2-simply Sun, 22 Dec 2019 13:35:07 +0000 https://www.yiiframework.com/extension/xandrkat/yii2-simply https://www.yiiframework.com/extension/xandrkat/yii2-simply katrazhenko katrazhenko

yii2-simply

This extension is a simplification of HTML helper Yii Framework 2.0.

Html Class

This class extends the Yii Html Helper . The helper functions available in this class are:

  • Html::bebin{anyTag}([\'whithot class as key\']),
  • Html::end{anyTag}()
  • Html::{anyTag}('content', [\'whithot class as key\'])
As
<?=\xandrkat\simply\Html::beginDiv(['container']).'any text'.\xandrkat\simply\Html::endDiv()?>
// result <div class="container">any text</div>
<?=\xandrkat\simply\Html::beginDiv(['class' => 'container']).'any text'.\xandrkat\simply\Html::endDiv()?>
// result <div class="container">any text</div> 
<?=\xandrkat\simply\Html::beginDiv(['container', 'class' => 'bg-primary']).'any text'.\xandrkat\simply\Html::endDiv()?>
// result <div class="container bg-primary">any text</div> 
// or simple tag
<?=\xandrkat\simply\Html::p('any content', ['text-center'])?>
// result <p class="text-center">any content</p>
<?=\xandrkat\simply\Html::p('any content', ['class' => 'text-center'])?>
// result <p class="text-center">any content</p>
<?=\xandrkat\simply\Html::p('any content', ['text-success', 'class' => 'text-center'])?>
// result <p class="text-success text-center">any content</p>
Or
use xandrkat\simply;

<?=Html::beginDiv(['container']).'any text'.Html::endDiv()?>
// result <div class="container">any text</div>
<?=Html::beginDiv(['class' => 'container']).'any text'.Html::endDiv()?>
// result <div class="container">any text</div> 
<?=Html::beginDiv(['container', 'class' => 'bg-primary']).'any text'.Html::endDiv()?>
// result <div class="container bg-primary">any text</div> 
// or simple tag
<?=Html::p('any content', ['text-center'])?>
// result <p class="text-center">any content</p>
<?=Html::p('any content', ['class' => 'text-center'])?>
// result <p class="text-center">any content</p>
<?=Html::p('any content', ['text-success', 'class' => 'text-center'])?>
// result <p class="text-success text-center">any content</p>
Attention

Html::a(), Html::img() etc. inherits yii\helpers\Html

License

yii2-helpers-simplyHtml is released under the BSD-3-Clause License. See the bundled LICENSE.md for details.

]]>
0
[news] Yii 2.0.31 Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/266/yii-2-0-31 https://www.yiiframework.com/news/266/yii-2-0-31 samdark samdark

We are very pleased to announce the release of Yii Framework version 2.0.31. Please refer to the instructions at https://www.yiiframework.com/download/ to install or upgrade to this version.

Version 2.0.31 is a minor release of Yii 2.0 that fixes PHP 7.4 compatibility, a number of bugs and adds two important enhancements.

  • Yii 3-like DI container configuration was enhanced to allow configuring application core components.
  • RFC 7239 Forwarded header support for trusted proxies was added. It could be enabled through request configuration.

Additionally, basic and advanced application templates are now using Codeception 4.0.

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
[extension] luyadev/luya-mailjet Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/luyadev/luya-mailjet https://www.yiiframework.com/extension/luyadev/luya-mailjet nadario nadario

LUYA Logo

LUYA Mailjet

  1. Installation
  2. Basic Send Mail
  3. MJML to HTML

LUYA Build Status Total Downloads Latest Stable Version Test Coverage Maintainability

LUYA and Yii Framework integration for Mailjet service.

Contains:

  • Yii Framework BaseMailer for Transaction E-Mails trough API.
  • Interface for Subscription Mail Sync including CLI command for Synchronisation.
  • A PHP library to convert MJML content into Mailjet Passport json format.
  • LUYA Admin Module to convert MJML into HTML based on MJML.io API.
  • LUYA Active Window to retrieve informations about a given User E-Mail.
  • A Widget to subscribe to a List with double opt in (can be disabled).
  • SMS Sending helpers
  • Yii 2 Queue Job to send mails with a template

Installation

Install the extension through composer:

composer require luyadev/luya-mailjet

Add to config:

[
    'components' => [
        'mailjet' => [
            'class' => 'luya\mailjet\Client',
            'apiKey' => '...',
            'apiSecret' => '...',
        ],
        'mailer' => [
            'class' => 'luya\mailjet\Mailer',
        ],
    ]
]

Basic Send Mail

Sending transactional E-Mail:

Yii::$app->mailer->compose()
    ->setFrom('from@domain.com')
    ->setTo('to@domain.com')
    ->setSubject('Message subject')
    ->setTextBody('Plain text content')
    ->setHtmlBody('<b>HTML content</b>')
    ->send();

Send a transactional E-Mail based on the Template id stored in Mailjet:

Yii::$app->mailer->compose()
    ->setTemplate(484590)
    ->setVariables(['lastnbame' => 'Lastname Value'])
    ->setTo(['to@domain.com'])
    ->send();

MJML to HTML

With version 1.3 of LUYA Mailjet library there is an admin module you can configured in order to parser MJML into HTML, therefore add the module to your configuration and provide mjml.io API keys:

'modules' => [
    'mailjetadmin' => [
        'class' => 'luya\mailjet\admin\Module',
        'mjmlApiApplicationId' => 'ApplicationIdFromMjml.io',
        'mjmlApiSecretKey' => 'ApplicationSecretFromMjml.io',
    ]
]

Afterwards you can retrieve and render the HTML of MJML template with:

luya\mailjet\models\Template::renderHtml('slug', ['foo' => 'bar']);
]]>
0
[extension] error-tracker/yii2-log-target Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/error-tracker/yii2-log-target https://www.yiiframework.com/extension/error-tracker/yii2-log-target AdeAttwood AdeAttwood

Error Tracker Yii2 Log Target

  1. Who is this for?
  2. Installation
  3. Configuration
  4. Using Yii2's log functions
  5. Disabling
  6. Contributing
  7. Credits

Who is this for?

This is for Yii2 developers that need to integrate their applications with Error Tracker. This extension uses the Yii2 log component to send errors and warnings direct to Error Tracker.

Installation

Install this package with composer.

composer require error-tracker/yii2-log-target

Configuration

To configure the log target in your application, add the below config. Whenever there is a server side error this will be added to the file log as it normally would. Additionally this will be sent to the error tracker dashboard, for easy searches, alerts and other handy tools.

'log' => [
    'targets' => [
        [
            'class' => 'yii\log\FileTarget',
            'levels' => ['error', 'warning'],
        ],
        [
            'class' => 'ErrorTracker\Yii2\ErrorTrackerTarget',
            'levels' => ['error', 'warning'],
            'app_key' => 'YOUR_APP_KEY'
        ],
    ],
],

When an error is reported onto Error Tracker, it will be saved with a Reference. This is your user's session id, and is also the id you can use to trace the error in your file log where you can find more information about that error error.

Using Yii2's log functions

You can use Yii2's error and warning methods to log errors to Error Tracker without throwing exceptions. This will still be logged in the same way, and will additionally and automatically sent if you have the log target configured. The below code will send an error without throwing an exception.

try {
    $this->willBreake();
} catch (\Exception $e) {
    Yii::error($e->getMessage(), $e->getName());
}

Disabling

Disable your log target by using the method documented by Yii2. Optionally disable the target by setting the enabled property in the configuration. The below config only enables the logger if your application is in a production environment.

[
    'class' => 'ErrorTracker\Yii2\ErrorTrackerTarget',
    'levels' => ['error', 'warning'],
    'app_key' => 'YOUR_APP_KEY',
    'enabled' => YII_ENV_PROD,
],

Contributing

Getting set up

Clone the repo and run composer install. Then start hacking!

Testing

All new features of bug fixes must be tested. Testing is with phpunit and can be run with the following command.

composer run-script test
Coding Standards

This library uses psr2 coding standards and squizlabs/php_codesniffer for linting. There is a composer script for this:

composer run-script lint
Pull Requests

Before creating a pull request with your changes, the pre-commit script must pass. That can be run as follows:

composer run-script pre-commit

Credits

This package is created and maintained by Practically.io

]]>
0
[extension] koperdog/yii2-treeview Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/koperdog/yii2-treeview https://www.yiiframework.com/extension/koperdog/yii2-treeview koperdog koperdog

TreeView widget

  1. Installation
  2. Usage

Widget to display the nested sets tree as a grid

Based on GridView

PackagistPackagist VersionPHP from Packagist

preview

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist koperdog/yii2-treeview "*"

or add

"koperdog/yii2-treeview": "*"

to the require section of your composer.json file.

Usage

Add to your model field: `php public $children = null; `

Has the same settings as GridView.
echo koperdog\yii2treeview\TreeView::widget([
	'dataProvider'  => $dataProvider,
	//'depthPrefix' => ' — ', //see Additional options
	//'depthRoot'   => 1, //see Additional options
	//'collaplse'   => true, //see Additional options
	'columns' => [
	  'id',
	  'name',
	  'created_at:datetime',
	  // ...
	],
    ]);
The column classes the same as GridView
['class' => '\koperdog\yii2treeview\base\CheckboxColumn'],
['class' => '\koperdog\yii2treeview\base\ActionColumn'],
['class' => '\koperdog\yii2treeview\base\RadioButtonColumn'],
['class' => '\koperdog\yii2treeview\base\SerialColumn'],
Additional options ##### depthPrefix - Prefix that displays depth (default " — ") ##### depthRoot - Offset from the root (default 0) ##### collaplse - if true, it will add the class "closed" to the node elements, and "tree-collapse" to the root of the tree (default false) ]]>
0
[extension] mgrechanik/yii2-universal-module-sceleton Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/mgrechanik/yii2-universal-module-sceleton https://www.yiiframework.com/extension/mgrechanik/yii2-universal-module-sceleton Pathfinder Pathfinder

Yii2 universal module sceleton

  1. Table of contents
  2. Goal
  3. Installing
  4. What it is about
  5. Using

Русская версия

Table of contents

Goal

This extension gives the structure of the module which:

  1. will be self-sufficient and portable because it holds all his functionality in one place
  2. logically divided at backend and frontend parts
  3. easily connects both to Advanced and Basic application templates
  4. with Basic application template:

    • connects to module section only one time
    • there is functionality of protection to all backend controllers (for admin part of your web site)
  5. it is easy to inherit such modules from one another to override functionality without controllers duplication

Installing

The preferred way to install this extension is through composer.:

Either run composer require --prefer-dist mgrechanik/yii2-universal-module-sceleton

or add "mgrechanik/yii2-universal-module-sceleton" : "~1.0.0" to the require section of your composer.json

What it is about

  • By default module controllers are being searched automatically in it's `$controllerNamespace`
  • We do not use this functionality but define all our controllers in module's `$controllerMap`
  • But we do this not by `$controllerMap` property explicitly but define backend and frontend controllers separately
  • Module has the mode which is set in config; according to this mode `Controller Map` will have only those controllers who fit the mode:
    • With frontend application of Advanced template we connect our module in `'frontend'` mode
    • With backend application of Advanced template we connect our module in `'backend'` mode
    • With Basic template we can connect our module in two modes described above and also in `'backend and frontend'` mode when both controller types are accessible
  • When module get the request it creates the controller from their map, figuring by it's namespace
    whether it is backend or frontend controller to perform additional set up
  • Module expects the next directory structure:
    Module_directory/
     ui/                                  // User Interface of the module
        controllers/
            backend/                      // Backend controllers like the next:
              AdminDefaultController.php  
              ...
            frontend/                     // Frontend controllers like the next: 
              DefaultController.php       
              ...
        views/                            // Views for corresponding controllers 
            backend/
              admin-default/
            frontend/        
              default/
     Module.php                           // module class
    

Using

1) Generate, or create manually, your module class

2) Inherit your module class from universal module class `php use mgrechanik\yiiuniversalmodule\UniversalModule;

class YourModule extends UniversalModule { ` 3) Now create (or generate) frontend controller

  • Take into consideration that it's `namespaceshould beyourModuleNamespace\ui\controllers\frontend`
  • Create all subdirs needed
  • According to controller it's views will reside in `@yourModuleNamespace/ui/views/frontend/YourControllerName/`
  • We need to define this controller in frontend controller map of this module:
    class YourModule extends UniversalModule
    {
      public $frontendControllers = [
          'default',
      ];
    

    , where `'default'matchyourModuleNamespace\ui\controllers\frontend\DefaultController`.
    When the name and class of controller do not match use next definition form: `'default2' => 'SomeDefaultController'`.

Always when you create new controller do not forget to define it in appropriate controller map of your module.

You are not required to inherit your controller classes from any parent type.

4) Now create (or generate) backend controller

  • Logic is the same with 3), but it's `namespaceshould beyourModuleNamespace\ui\controllers\backend`
  • Define it in module at:
    class YourModule extends UniversalModule
    {
      public $backendControllers = [
          'admin-default',
      ];
    
  • It is handy to prefix backend controller names with Admin, so all backend urls could be set up the way all of them will start with admin/

5) Done, your module is ready, you can connect it to application:

config/main.php: `php

// ...
'modules' => [
    'yourModule' => [
        'class' => 'yourModuleNamespace\YourModule',
        'mode' => 'frontend',
    ],
, do not forget to define - [mode](#mode)

> It is comfortable to connect all such modules at first level of application modules, without nested modules 
> but like a simple list of modules we used to see at admin pages of popular **CMS**s, which also gives short urls.

---

## Module inheritance <span id="inheritance"></span>

When you inherit your module class from existing one without overriding ```$frontendControllers```
and ```$backendControllers``` the next happens:
1) These two properties will be taken from parent naturally
2) But the basic controller namespace will be set to your module namespace.
You do not have such controllers and you do not want to create their copies
3) There are next opportunities how to make your module to use controllers from any of his ancestors:
    * If controllers reside with **immediate** parent of your module set it the next property
	```$takeControllersFromParentModule = true```
    * If controllers are in some other module, say ```'Omega'```, then set the property of your module  ```$baseControllerNamespace``` to **namespace** of ```'Omega'``` module
    * ```Views``` will be searched by default according to settings above	

---

## Module settings <span id="settings"></span>

[Connecting](#setup) module to application we can use next it's properties:

#### ```$mode``` - mode in which this module works
You are required to set up it. [Details](#mode)

#### ```$backendLayout``` - layout for backend controllers
Sets up ```layout``` to module when **backend** controller is requested.  
It is useful for *Basic* application template.

#### ```$frontendControllers``` - frontend controller map
[Details](#fcontroller)

#### ```$backendControllers``` - backend controller map
[Details](#bcontroller)

#### ```$controllerMapAdjustCallback``` - callback for final adjustment of controller map

After module's controller map is generated you can adjust it with this function 
which signature is: ```function($map) { ...; return $map; }```

#### ```$backendControllerConfig``` - **backend** controllers settings
When module [creates](#mknows) **backend** controller it could set up controller with these properties.

It is handy, for example, to restrict access to such controllers using yii filters connected like behaviors.

[Example of using](#example-basic). 

#### ```$frontendControllerConfig``` - **frontend** controllers settings
It is the same like ```$backendControllerConfig```

#### ```$baseControllerNamespace``` - the basic namespace for the controllers the module uses
By default it is not used and controllers will be searched relevant to your current module namespace.  
But with this property you can tell to take controllers from any other module

#### ```$takeControllersFromParentModule``` - whether to take controllers from parent
It is ```false``` by default, meaning no taking.  
But with this property you can tell to take controllers from immediate parent of current module

#### ```$baseViewsPath``` - basic path where to find module's ```views```
By default it is not used.  
It is automatically set up whether to current module directory or relevant to the two properties above when they are set.   
But with this property you can finally say where to search for ```views```. Example of value: ```'@mgrechanik/yii2catalog'```

---

## Example of module's set up with *Basic* application template <span id="example-basic"></span>

Lets suppose that we have two modules we are talking about  - ```example``` and ```omega```.  
Here is working configs to set up these modules:

**config/params.php:**
```php
return [
    'backendLayout' => '//lte/main',
    'backendControllerConfig' => [
        'as backendaccess' => [
            'class' => \yii\filters\AccessControl::class,
            'rules' => [
                [
                    'allow' => true,
                    'ips' => ['54.54.22.44'],
                    'matchCallback' => function ($rule, $action){
                        $user = \Yii::$app->user;
                        return !$user->isGuest &&
                            ($user->id == 1);
                },
                ]
            ],
        ],
    ],	
  
];

At this config we gave permission to "admin pages" only to one user `(id==1)`, with additional check for `ip`.

config/web.php: `php

'components' => [
//...
    'urlManager' => [
        'enablePrettyUrl' => true,
        'showScriptName' => false,
        'rules' => [
            'admin/<module:(example|omega)>-<controllersuffix>/<action:\w*>' =>
                '<module>/admin-<controllersuffix>/<action>',
            'admin/<module:(example|omega)>-<controllersuffix>' =>
                '<module>/admin-<controllersuffix>',
        ],
    ],	
],
'modules' => [
    'example' => [
        'class' => 'modules\example\Module',
        'mode' => 'backend and frontend',
        'backendLayout' => $params['backendLayout'],
        'backendControllerConfig' => $params['backendControllerConfig'],
    ],
    'omega' => [
        'class' => 'modules\username1\omega\Module',
        'mode' => 'backend and frontend',
        'backendLayout' => $params['backendLayout'],
        'backendControllerConfig' => $params['backendControllerConfig'],
    ],        
], 

---

## How-to <span id="recipe"></span>

#### Make all admin urls start with ```/admin```  <span id="recipe-admin-url"></span>
Lets see *Basic* application template with two "our" modules connected to it:
```php
    'modules' => [
        'example' => [
            ...
        ],
        'omega' => [
            ...
        ],  

If we followed advice above about naming of backend controllers all of them have names like `Admin...Controller`.
So urls to them will be `example/admin-defaultandomega/admin-default`.
And we want all our admin urls to start with `admin/`.

It is easily achived with the next two `Url Rulesfor yoururlManager`: `php

'urlManager' => [
	'enablePrettyUrl' => true,
	'showScriptName' => false,
	'rules' => [
		'admin/<module:(example|omega)>-<controllersuffix>/<action:\w*>' =>
			'<module>/admin-<controllersuffix>/<action>',
		'admin/<module:(example|omega)>-<controllersuffix>' =>
			'<module>/admin-<controllersuffix>',
	],
],

#### Generating **backend** functionality with Gii CRUD generator   <span id="recipe-crud"></span>

You can easily generate CRUD functionality considering that:
* The ```name``` and the ```namespace``` of the **controller** should be choosen according to [documentation](#bcontroller)
* ```View Path``` should match [directory structure](#dir-structure) the module demands

#### How to connect the module to console application?   <span id="recipe-other-console"></span>

If our module has console commands who reside for example here:

Module_directory/ console/

commands/                // Directory for console commands
  HelloController.php

Module.php `
, then in the console application config this module is connected like:

    'modules' => [
        'example' => [
            'class' => 'modules\example\Module',
            'controllerNamespace' => 'yourModuleNamespace\console\commands',
        ],
    ],
Where to put all other module's functionality?

This module regulates only directory structure described above where only from controllers and views their concrete positions are expected.
When writing the rest of functionality you may follow the next advices:

  • If a component is definitely related only to one part of application - backend or frontend then put it in the corresponding subdirectory
  • If there is no such definite separation put it in the root of his directory

For example for models: ` models/

backend/
  SomeBackendModel.php
frontend/
  SomeFrontendModel.php	  
SomeCommonModel.php  
* Since for all user interface of our module we have already created ```ui/``` subdirectory 
then put **forms** and **widgets** there


]]>
0
[extension] slavkovrn/yii2-ion-rangeslider Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/slavkovrn/yii2-ion-rangeslider https://www.yiiframework.com/extension/slavkovrn/yii2-ion-rangeslider Viacheslav Kolesnikov Viacheslav Kolesnikov

IonRangeSlider widget for Yii2 Framework uses Ion.RangeSlider plugin version 2.3.0 for jQuery with pjax support

  1. Installation
  2. Usage

IonRangeSlider widget demo page

The extension uses Ion.RangeSlider plugin version 2.3.0 for jQuery https://github.com/IonDen and makes user interface for changing min,max values of any range with pjax support

IonRangeSlider widget

Installation

The preferred way to install this extension is through composer.

Either run:

composer require slavkovrn/yii2-ion-rangeslider

or add

"slavkovrn/yii2-ion-rangeslider": "*"

to the require section of your composer.json file.

Usage

Set link to extension in your view:

<?php
use slavkovrn\ionrangeslider\IonRangeSliderWidget;
use yii\widgets\ActiveForm;
use yii\helpers\Html;
?>
    <?php $form = ActiveForm::begin(); ?>

    <?= $form->field($model, 'username')->widget(IonRangeSliderWidget::class,[
	'min' => 0,
	'max' => 10,
	'from' => 2,
	'to' => 6,
    ]) ?>

    <?= $form->field($model, 'email')->widget(IonRangeSliderWidget::class,[
	'min' => 0,
	'max' => 10,
	'from' => 4,
	'to' => 8,
    ]) ?>

    <div class="form-group">
        <?= Html::submitButton('Save', ['class' => 'btn btn-success']) ?>
    </div>

    <?php ActiveForm::end(); ?>​

write comments to admin

]]>
0
[extension] coderius/yii2-jqcloud2-widget Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/coderius/yii2-jqcloud2-widget https://www.yiiframework.com/extension/coderius/yii2-jqcloud2-widget coderius coderius

jQCloud widget for Yii2

  1. jQCloud screenshot:
  2. Installation
  3. Basic usage.
  4. Advanced usage.

The jQCloud widget is a customized jQCloud script based on jQCloud. and This widget used to word clouds and tag clouds that are actually shaped like a cloud.

jQCloud screenshot:

alt text

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require coderius/yii2-jqcloud2-widget "@dev"

or add

"coderius/yii2-jqcloud2-widget" : "@dev"

to the require section of your application's composer.json file.

Basic usage.

In view:

use coderius\jqcloud2;

<?= \coderius\jqcloud2\jQCloud::widget([
    'tagOptions' => [
        'style' => 'width:100%; height: 350px; border: 1px solid #ccc;',
    ],
    
    'wordsOptions' => [
        ['text'=>"Lorem",'weight' => 13, 'link' =>"#"],
        ['text'=>"Ipsum",'weight' => 10.5, 'html' => ['title' => "My Title", "class" => "custom-class"], 'link' => ['href' => "http://jquery.com/", 'target' => "_blank"]],
        [
            'text'=>"Dolor",
            'weight' => 9.4, 
            'handlers' => [
                'click' => new \yii\web\JsExpression("
                    function() {
                        alert('You clicked the word !');
                    }
                "),
            ]
        ],
        ['text'=>"Sit",'weight' => 8],
        ['text'=>"Amet",'weight' => 6.2],
        ['text'=>"Consectetur",'weight' => 5],
        ['text'=>"Adipiscing",'weight' => 5],
        ['text'=>"Elit",'weight' => 5],
        ['text'=>"Nam et", 'weight' => 5]
            
    ],
   
]); ?>

Advanced usage.

In view:

use coderius\jqcloud2;

<?= \coderius\jqcloud2\jQCloud::widget([
    'tagOptions' => [
        'style' => 'width:100%; height: 350px; border: 1px solid #ccc;',
//        'id' => 'myid',
        ],
    
    'wordsOptions' => [
        ['text'=>"Lorem",'weight' => 13, 'link' =>"#"],
        ['text'=>"Ipsum",'weight' => 10.5, 'html' => ['title' => "My Title", "class" => "custom-class"], 'link' => ['href' => "http://jquery.com/", 'target' => "_blank"]],
        [
            'text'=>"Dolor",
            'weight' => 9.4, 
            'handlers' => [
                'click' => new \yii\web\JsExpression("
                    function() {
                        alert('You clicked the word !');
                    }
                "),
            ]
        ],
        ['text'=>"Sit",'weight' => 8],
        ['text'=>"Amet",'weight' => 6.2],
        ['text'=>"Consectetur",'weight' => 5],
        ['text'=>"Adipiscing",'weight' => 5],
        ['text'=>"Elit",'weight' => 5],
        ['text'=>"Nam et", 'weight' => 5]
            
    ],
    'cloudOptions' => [
        'delay' => 50,
        'autoResize' => true,
//        'colors' => ["#800026", "#bd0026", "#e31a1c", "#fc4e2a", "#fd8d3c", "#feb24c", "#fed976", "#ffeda0", "#ffffcc"],
        'fontSize' => [
            'from' => 0.1,
            'to' => 0.02
        ]
    ],
    'methods' =>    function($containerId, $words){
                        return new \yii\web\JsExpression("
                            var arr = arr || $words;
                            $('#update-demo').on('click', function(e) {
                                e.preventDefault();
                                arr.splice(0, 1);
                                $('#{$containerId}').jQCloud('update', arr);
                            });
                            
//                            $('#{$containerId}').jQCloud('destroy');

                        ");
                    } 
   
    
]); ?>

More info about options look in: site about jQuery plugin

Reference to plugin github repository that is used in this widget.

]]>
0
[extension] coderius/yii2-lightbox2-widget Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/extension/coderius/yii2-lightbox2-widget https://www.yiiframework.com/extension/coderius/yii2-lightbox2-widget coderius coderius

Lightbox2 widget for Yii2

  1. Installation
  2. Usage

The Lightbox2 widget is a customized lightbox script based on Lightbox. and This widget used to overlay images on top of the current page.

alt text

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require coderius/yii2-lightbox2-widget "@dev"

or add

"coderius/yii2-lightbox2-widget" : "@dev"

to the require section of your application's composer.json file.

Usage

  • In view:
use coderius\lightbox2\Lightbox2;

<?= coderius\lightbox2\Lightbox2::widget([
    'clientOptions' => [
        'resizeDuration' => 200,
        'wrapAround' => true,
        
    ]
]); ?>

<a href="<?= Yii::getAlias("@img-web-blog-posts/1/middle/pic.jpg"); ?>" data-lightbox="roadtrip" data-title="some title" data-alt="some alt">
    <!-- Thumbnail picture -->
    <?= Html::img("@img-web-blog-posts/pic.jpg"); ?>
</a>

<a href="<?= Yii::getAlias("@img-web-blog-posts/10/middle/pic2.jpg"); ?>" data-lightbox="roadtrip">
    <!-- Thumbnail picture -->
    <?= Html::img("@img-web-blog-posts/pic2.jpg"); ?>
</a>

<a href="<?= Yii::getAlias("@img-web-blog-posts/11/middle/pic3.jpg"); ?>" data-lightbox="roadtrip">
    <!-- Thumbnail picture -->
    <?= Html::img("@img-web-blog-posts/pic3.jpg"); ?>
</a>

You need set data-lightbox attribute to link and path to image in href attribute. If you wanna to set group images, then put identic names to data-lightbox attribute for each needed link.

*Thumbnail picture, by clicking on which opens the widget is wrapped with a link

Reference to plugin github repository that is used in this widget.

]]>
0
[wiki] UUID instead of an auto-increment integer for ID with Active Record Tue, 21 Jan 2020 09:46:44 +0000 https://www.yiiframework.com/wiki/2555/uuid-instead-of-an-auto-increment-integer-for-id-with-active-record https://www.yiiframework.com/wiki/2555/uuid-instead-of-an-auto-increment-integer-for-id-with-active-record grigori grigori

I have a dream ... I am happy to join with you today in what will go down in history as the greatest demonstration of

bad design of Active Record.

I have an API. It's built with a RESTful extension over Active Record, and some endpoints provide PUT methods to upload files. By a REST design we create an entity with POST /video first, and then upload a video file with PUT /video/{id}/data.

How do we get the {id}? The essential solutuion is UUID generated by a client. It allows API application to be stateless and scale it, use master-master replication for databases and feel yourself a modern guy. If you have Postgres — lucky you, feel free to use the built-in UUID data type and close this article. With MySQL the essential solution is insert into users values(unhex(replace(uuid(),'-',''))... MySQL team recommends updating our INSERT queries. With Active Record it is not really possible. For fetching UUIDs it recommends adding a virtual column — this can be used.

If you design the application from ground up, you can use defferent fields for a binary and text representation of UUID, and reference them in different parts of an application, but I am bound to the legacy code.

Adding getId()/setId() won't help - data comes from a client in JSON and fills the model object with a setAttributes() call avoiding generic magic methods.

Here's the hack:

Step 1. Add a private $idText property

use yii\db\ActiveRecord;
class Video extends ActiveRecord
{
    private $idText;

Step 2. Add two validators and a filter

//check if value is a valid UUID
['id','match', 'pattern'=>'/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i'],
// convert UUID from text value to binary and store the text value in a private variable,
// this is a workaround for lack of mapping in active record
['id','filter','skipOnError' => true, 'filter' => function($uuid) {
    $this->idText = $uuid;
    return pack("H*", str_replace('-', '', $uuid));
}],
//now let's check if ID is taken
['id','unique','filter' => function(\yii\db\Query $q) {
    $q->where(['id' => $this->getAttribute('id')]);
}],

First rule is a validator for an input. Second rule is a filter preparing UUID to be written in a binary format and keeping the text form for output. Third one is a validator running a query over the binary value generated by a filter.

Note: I wrote $this->getAttribute('id'), $this->id returns a text form.

We can write a query to validate data, not to save it.

Step 3. Add getters

public function __get($name)
{
    return ($name === 'id') ? $this->getId() : parent::__get($name);
}

/**
 * Return UUID in a textual representation
 */
public function getId(): string
{
    if ($this->idText === NULL && $this->getIsNewRecord()){
        //the filter did not convert ID to binary yet, return the data from input
        return strtoupper($this->getAttribute('id'));
    }
    //ID is converted
    return strtoupper($this->idText ?? $this->getAttribute('id_text'));
}

When we call the $model->id property we need the getId() executed. But Active Record base class overrides Yii compoent default behavior and does not call a getter method of an object if a property is a field in a table. So I override the magic getter. From the other hand, a regexp valiator I wrote calls $model->id, triggering the getter before the UUID is saved to the private property. I check if the object is newly created to serve the text value for validator.

Note the strtoupper() call: client may send UUID in both upper and low cases, but after unpacking from binary we will have a value in upper case. I received different string values before storing data to DB and after fetching it. Convert the textual UUID value to an upper or lower case everywhere to avoid problems.

It looks weird to mutate data in a validator, but I found this is the best way. I belive I shouldn't use beforeSave() callback to set the binary value for generating SQL, and return the text value back in afterSave() - supporting this code would be a classic hell like #define true false;.

Step 4. Define the mapping for output

public function fields()
{
    $fields = parent::fields();
    $fields['id'] =function(){return $this->getId();};
    return $fields;
}

This method is used by RESTful serializers to format data when you access your API with GET /video requests.

So, now you can go the generic MySQL way

Step 5. add a virtual column

ALTER TABLE t1 ADD id_text varchar(36) generated always as
 (insert(
    insert(
      insert(
        insert(hex(id_bin),9,0,'-'),
        14,0,'-'),
      19,0,'-'),
    24,0,'-')
 ) virtual;

Step 5. Use Object Relation Mapping in Yii 3 when it's available and write mapping instead of these hacks.

P.S. A couple of helper functions.

declare(strict_types=1);

namespace common\helpers;


class UUIDHelper
{
    const UUID_REGEXP = '/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i';

    public static function string2bin(string $uuid): string
    {
        return pack("H*", str_replace('-', '', $uuid));
    }

    public static function bin2string(string $binary): string
    {
        return strtolower(join("-", unpack("H8time_low/H4time_mid/H4time_hi/H4clock_seq_hi/H12clock_seq_low", $binary)));
    }

    public static function isUUID(string $uuid): bool
    {
        return (bool)preg_match(self::UUID_REGEXP,$uuid);
    }
}
]]>
0
[news] Yii 2.0.30 Wed, 20 Nov 2019 12:27:54 +0000 https://www.yiiframework.com/news/265/yii-2-0-30 https://www.yiiframework.com/news/265/yii-2-0-30 samdark samdark

We are very pleased to announce the release of Yii Framework version 2.0.30. Please refer to the instructions at https://www.yiiframework.com/download/ to install or upgrade to this version.

Version 2.0.30 is a minor release of Yii 2.0 that fixes a number of bugs.

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] MongoDB extension 2.1.9 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/264/mongodb-extension-2-1-9-released https://www.yiiframework.com/news/264/mongodb-extension-2-1-9-released samdark samdark

We are very pleased to announce the release of MongoDB extension version 2.1.9 that fixes Collection::dropAllIndexes() error when no indexes were dropped.

]]>
0
[news] Smarty extension 2.0.9 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/263/smarty-extension-2-0-9-released https://www.yiiframework.com/news/263/smarty-extension-2-0-9-released samdark samdark

We are pleased to announce the release of Smarty extension version 2.0.9. In this release {js} function was added allowing to instantiate yii\web\JsExpression:

{js assign='expr' expression='function(){alert('expression');}}'}
]]>
0
[news] Gii extension 2.1.3 released Fri, 17 Jan 2020 13:36:35 +0000 https://www.yiiframework.com/news/262/gii-extension-2-1-3-released https://www.yiiframework.com/news/262/gii-extension-2-1-3-released samdark samdark

We are very pleased to announce the release of Gii extension version 2.1.3.

This release fixes issue with RTL naming for foreign keys such as id_user that was causing problems in generated code.

Also, it improves some aspects of generated code. float types are now recognized and generated and annotations for nullable types are now there:

/**
 * @property string|null $car_number
 */

See the CHANGELOG for details.

]]>
0
[news] Debug extension 2.1.12 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/261/debug-extension-2-1-12-released https://www.yiiframework.com/news/261/debug-extension-2-1-12-released samdark samdark

Debug extension version 2.1.12 was released. This release fixes missing timeline panel tooltips and adds a warning message in DB panel when traceLevel is too low in order for it to collect data.

See the CHANGELOG for details.

]]>
0
[news] Auth Client extension 2.2.6 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/260/auth-client-extension-2-2-6-released https://www.yiiframework.com/news/260/auth-client-extension-2-2-6-released samdark samdark

We are very pleased to announce the release of Auth Client extension version 2.2.6.

This release adds CacheStateStorage that provides state storage based in cache component.

Additionally, request option for turning off SSL peer verification was removed to make extension more secure by default. It may break your application if server doesn't have root certificates set up correctly so make sure to check upgrade guide.

See the CHANGELOG for details.

]]>
0
[news] Redis extension 2.0.11 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/259/redis-extension-2-0-11-released https://www.yiiframework.com/news/259/redis-extension-2-0-11-released samdark samdark

We are very pleased to announce the release of Redis extension version 2.0.11.

This version adds for Redis cluster contributed by Robert Hofer.

See the CHANGELOG for details.

]]>
0
[news] Auth Client extension 2.2.5 released Tue, 05 Nov 2019 14:26:17 +0000 https://www.yiiframework.com/news/258/auth-client-extension-2-2-5-released https://www.yiiframework.com/news/258/auth-client-extension-2-2-5-released samdark samdark

We are very pleased to announce the release of Auth Client extension version 2.2.5.

In this release deprecated dependency spomky-labs/jose was replaced by JWT Framework.

See the CHANGELOG for details.

]]>
0
[news] Debug extension 2.1.11 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/257/debug-extension-2-1-11-released https://www.yiiframework.com/news/257/debug-extension-2-1-11-released samdark samdark

Debug extension version 2.1.11 was released. It is adding removal of stale debug data, fixes issues and adding buttons for navigating between requests.

See the CHANGELOG for details.

]]>
0
[news] Yii 2.0.29 Tue, 22 Oct 2019 16:49:03 +0000 https://www.yiiframework.com/news/256/yii-2-0-29 https://www.yiiframework.com/news/256/yii-2-0-29 samdark samdark

We are very pleased to announce the release of Yii Framework version 2.0.29. Please refer to the instructions at https://www.yiiframework.com/download/ to install or upgrade to this version.

Version 2.0.29 is a minor release of Yii 2.0. Additionally to fixing minor bugs it adds PostgreSQL 12 support (including partitioned tables) and adjusts dependency injection container syntax to be closer to what is used in Yii 3:

  • Added support for '__construct()' => ['ConstructorArg1', 'ConstructorArg2']
  • Added support for '__class' => SomeClass::class
  • Instance::of() is now allowed in definitions ('SomeInterface' => Instance::of('SomeService')) and $container->get(Instance::of('SomeInterface')
  • Definition could be defined as static call 'SomeInterface' => [SomeFactory::class, 'createMethod']

Current Yii 2 syntax works as it did so it is not necessary to update your configuration.

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] Debug extension 2.1.10 released Tue, 22 Oct 2019 13:00:36 +0000 https://www.yiiframework.com/news/255/debug-extension-2-1-10-released https://www.yiiframework.com/news/255/debug-extension-2-1-10-released samdark samdark

Debug extension version 2.1.10 was released. It is tweaking Logs panel not to wrap important columns text and to display arrows in less distracting manner.

See the CHANGELOG for details.

]]>
0
[news] Redis extension 2.0.10 released Mon, 17 Feb 2020 12:24:40 +0000 https://www.yiiframework.com/news/254/redis-extension-2-0-10-released https://www.yiiframework.com/news/254/redis-extension-2-0-10-released samdark samdark

We are very pleased to announce the release of Redis extension version 2.0.10.

In this version \yii\redis\Connection::$retryInterval was added. It speficifies wait time between retry in microseconds.

See the CHANGELOG for details.

]]>
0
[wiki] Yii v2 snippet guide Thu, 23 Jan 2020 19:48:42 +0000 https://www.yiiframework.com/wiki/2552/yii-v2-snippet-guide https://www.yiiframework.com/wiki/2552/yii-v2-snippet-guide rackycz rackycz
  1. Intro
  2. Prerequisities
  3. Yii demo app + GitLab
  4. User management + DB creation + login via DB
  5. i18n translations
  6. Switching languages + session + lang-dropdown in the top menu
  7. Simple access rights
  8. Nice URLs
  9. How to redirect web to subfolder /web
  10. Auto redirection from login to desired URL
  11. What to change when exporting to the Internet
  12. Saving contact inqueries into DB
  13. Tests - unit + opa
  14. Adding a google-like calendar
  15. Scenarios - UNKNOWN SCENARIO EXCEPTION
  16. Richtext / wysiwyg HTML editor - Summernote
  17. SEO optimization
  18. Other useful links
  19. jQuery + draggable/droppable on mobile devices (Android)

Intro

Hi all!

Please note, that this article will be updated regularly as I have more and more snippets so come back in a few weeks

This snippet guide works with the basic Yii demo application and enhances it. It continues in my series of simple Yii tutorials. Previous two contain basic info about MVC concept, exporting to Excel and other topics so read them as well, but they are meant for Yii v1. I started with them cca in year 2011:

... and today I am beginning with Yii 2 so I will also gather my snippets and publish them here so we all can quickly setup the yii-basic-demo just by copying and pasting. This is my goal - to show how-to without long descriptions.

I was suprised that the Yii 2 demo application does not contain some basic functionalities (like login via DB, translations etc) which must be implemented in the most of web projects so I will focus on them. Plus I will talk about GitLab.

If you find any problems in my snippets, let me know, please.

Prerequisities

Skip this paragraph if you know how to run your Yii demo project...

I work with Win10 + XAMPP Server so I will expect this configuration. Do not forget to start the server and enable Apache + MySQL in the dialog. Then test that following 2 URLs work for you

You should also download the Yii basic demo application and place it into the htdocs folder. In my case it is here:

  • C:\xampp\htdocs

And your index.php should be here:

  • C:\xampp\htdocs\basic\web\index.php

If you set things correctly up, following URL will open your demo application. Now it will probably throw an exception:

The Exception is removed by entering any text into attribute 'cookieValidationKey' in file:

  • C:\xampp\htdocs\basic\config\web.php

Dont forget to connect Yii to the DB. It is done in file:

  • C:\xampp\htdocs\basic\config\db.php

... but it should work out-of-the-box if you use DB name "yii2basic" which is also used in examples below ...

Yii demo app + GitLab

Once you download and run the basic app, I recommend to push it into GitLab. You will probably need a SSH certificate which can be generated like this using PuTTYgen. When I work with Git I use TortoiseGIT which integrates all git functionalities into the context menu in Windows File Explorer.

First go to GitLab web and create a new project. Then you might need to fight a bit, because the process of connecting your PC to GIT seems to be quite complicated. At least for me.

Once things work, just create an empty folder, right click it and select Git Clone. Enter your git path, best is this format:

When cloned, copy the content of the "basic" folder into the new empty git-folder and push everything except for folder "vendor". (It contains 75MB and 7000 files so you dont want to have it in GIT)

Then you can start to modify you project, for example based on this "tutorial".

Automatical copying from GitLab to FTP

I found these two pages where things are explained: link link and I just copied something.

You only need to create 1 file in your GitLab repository. It is named .gitlab-ci.yml and it should contain following code:

variables:
  HOST: "ftp url"
  USERNAME: "user"
  PASSWORD: "password"
  TARGETFOLDER: "relative path if needed, or just ./"

deploy:
  script:
    - apt-get update -qq && apt-get install -y -qq lftp
    - lftp -c "set ftp:ssl-allow no; open -u $USERNAME,$PASSWORD $HOST; mirror -Rnev ./ $TARGETFOLDER --ignore-time --parallel=10 --exclude-glob .git* --exclude .git/ --exclude vendor --exclude web/assets --exclude web/index.php --exclude web/index-test.php" 
  only:
    - master

I just added some exclusions (listed below) and will probably add --delete in the future. Read linked webs.

  • exclude vendor = huge folder with 3rd party SW which is not in GIT
  • exclude web/assets = also some cache
  • exclude web/index.php = in GIT is your devel index with DEBUG mode enabled. You dont wanna have this file in productive environment
  • exclude web/index-test.php = tests are only on your computer and in GIT

User management + DB creation + login via DB

To create DB with users, use following command. I recommend charset utf8_unicode_ci (or utf8mb4_unicode_ci) as it allows you to use more international characters.

CREATE DATABASE IF NOT EXISTS `yii2basic` DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;

CREATE TABLE IF NOT EXISTS `user` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(45) NOT NULL,
  `password` VARCHAR(60) NOT NULL,
  `email`    VARCHAR(60) NOT NULL,
  `authKey`  VARCHAR(60),
  PRIMARY KEY (`id`))
ENGINE = InnoDB;

INSERT INTO `user` (`id`, `username`, `password`, `email`, `authKey`) VALUES (NULL, 'user01', '0497fe4d674fe37194a6fcb08913e596ef6a307f', 'user01@gmail.com', NULL);

If you must use MyISAM instead of InnoDB, just change the word InnoDB into MYISAM.

Then replace existing model User with following snippet

  • The model was generated by Gii and originally had 3 methods: tableName(), rules(), attributeLabels()
  • In order to use the DB for login, we needed to implement IdentityInterface which requires 5 new methods.
  • Plus we add 2 methods because of the default LoginForm and 1 validator.
<?php

namespace app\models;

use Yii;

class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface {

    // When user detail is being edited we will only modify attribute password_new
    // Why? We dont want to load password-hash from DB and display it to the user
    // We only want him to see empty field and if it is filled in, password is changed on background
    public $password_new;
    public $password_new_repeat;

    // Use this scenario in UserController->actionCreate() right after: $model = new User() like this:
    // $model->scenario = User::SCENARIO_CREATE;
    // This will force the user to enter the password when new user is created
    // When user is edited, new password is not needed
    const SCENARIO_CREATE = "user-create";

    // ----- Default 3 model-methods by GII:

    public static function tableName() {
        return 'user';
    }

    public function rules() {
        return [
            [['username', 'email'], 'required'],
            [['password_new_repeat', 'password_new'], 'required', "on" => self::SCENARIO_CREATE],
            [['username', 'email'], 'string', 'max' => 45],
            ['email', 'email'],
            [['password', 'authKey'], 'string', 'max' => 60],
            [['password', 'password_new_repeat', 'password_new'], 'safe'],
            ['password_new_repeat', 'compare', 'operator' => '==', 'compareAttribute' => 'password_new'],
            ['password_new', 'compare', 'operator' => '==', 'compareAttribute' => 'password_new_repeat'],
            
            ['password_new_repeat', 'setPasswordWhenChanged'],
        ];
    }

    public function attributeLabels() {
        return [
            'id' => Yii::t('app', 'ID'),
            'username' => Yii::t('app', 'Username'),
            'password' => Yii::t('app', 'Password'),
            'password_new' => Yii::t('app', 'New password'),
            'password_new_repeat' => Yii::t('app', 'Repeat new password'),
            'authKey' => Yii::t('app', 'Auth Key'),
            'email' => Yii::t('app', 'Email'),
        ];
    }

    // ----- Password validator

    public function setPasswordWhenChanged($attribute_name, $params) {

        if (trim($this->password_new_repeat) === "") {
            return true;
        }

        if ($this->password_new_repeat === $this->password_new) {
            $this->password = sha1($this->password_new_repeat);
        }

        return true;
    }

    // ----- IdentityInterface methods:

    public static function findIdentity($id) {
        return static::findOne($id);
    }

    public static function findIdentityByAccessToken($token, $type = null) {
        return static::findOne(['access_token' => $token]);
    }

    public function getId() {
        return $this->id;
    }

    public function getAuthKey() {
        return $this->authKey;
    }

    public function validateAuthKey($authKey) {
        return $this->authKey === $authKey;
    }

    // ----- Because of default LoginForm:

    public static function findByUsername($username) {
        return static::findOne(['username' => $username]);
    }

    public function validatePassword($password) {
        return $this->password === sha1($password);
    }

}

Validators vs JavaScript:

  • There are 2 types of validators. All of them are used in method rules, but as you can see, the validator setPasswordWhenChanged is my custom validator and needs a special method. (I just abused a validator to set the password value, no real validation happens inside)
  • If a validator does not need this special method, it is automatically converted into JavaScript and is used on the web page when you are typing.
  • If a validator needs the method, it cannot be converted into JavaScript so the rule is checked only in the moment when user sends the form to the server - after successful JavaScript validation.

Now you can also create CRUD for the User model using GII:

CRUD = Create Read Update Delete = views and controller. On the GII page enter following values:

  • Model Class = app\models\User
  • Search Model Class = app\models\UserSearch
  • Controller Class = app\controllers\UserController
  • View Path can be empty or you can set: views\user
  • Again enable i18n

And then you can edit users on this URL: http://localhost/basic/web/index.php?r=user ... but it is not all. You have to modify the view-files so that correct input fields are displayed!

Open folder views\user and do following:

  • _form.php - rename input password to password_new then duplicate it and rename to password_new_repeat. Remove authKey.
  • _search.php - remove password and authKey.
  • index.php - remove password and authKey.
  • view.php - remove password and authKey.

Plus do not forget to use the new scenario in UserController->actionCreate() like this:

public function actionCreate()
{
  $model = new User();
  $model->scenario = User::SCENARIO_CREATE; // the new scenario!
  // ...

i18n translations

Translations are fairly simple, but I probably didnt read manuals carefully so it took me some time. Note that now I am only describing translations which are saved in files. I do not use DB translations yet. Maybe later.

1 - Translating short texts and captions

First create following folders and file.

  • "C:\xampp\htdocs\basic\messages\cs-CZ\app.php"

(Note that cs-CZ is for Czech Lanuage. For German you should use de-DE etc. Use any other language if you want.)

The idea behind is that in the code there are used only English texts and if you want to change from English to some other language this file will be used.

Now go to file config/web.php, find section "components" and paste the i18n section:

    'components' => [
        'i18n' => [
          'translations' => [
            '*' => [
              'class' => 'yii\i18n\PhpMessageSource',
              'basePath' => '@app/messages',
              'sourceLanguage' => 'en-US',
              'fileMap' => [
                'app' => 'app.php'
              ],
            ],
          ],
        ], // end of 'i18n'

        // ... other configurations

    ], // end of 'components'
    

Explanation of the asterisk * can be found in article https://www.yiiframework.com/doc/guide/2.0/en/tutorial-i18n

You surely saw that in views and models there are translated-texts saved like this:

Yii::t('app', 'New password'),

It means that this text belongs to category "app" and its English version (and also its ID) is "New password". So this ID will be searched in the file you just created. In my case it was the Czech file:

  • "C:\xampp\htdocs\basic\messages\cs-CZ\app.php"

Therefore open the file and paste there following code:

<?php
return [
    'New password' => 'Nové heslo',
];
?>

Now you can open the page for adding a new user and you will see than so far nothing changed :-)

We must change the language ... For now let's do it in a primitive and permanent way again in file config/web.php

$config = [
    // use your language
    // also accessible via Yii::$app->language
    'language' => 'cs-CZ',
    
    // This attribute is not necessary.
    // en-US is default value
    'sourceLanguage' => 'en-US',
    
    // ... other configs

2 - Translating long texts and whole views

If you have a view with long texts and you want to translate it into a 2nd language, it is not good idea to use the previous approach, because it uses the English text as the ID.

It is better to translate the whole view. How? ... Just create a sub-folder next to the view and give it name which will be identical to the target-lang-ID. In my case the 2nd language is Czech so I created following folder and copied my view in it. So now I have 2 identical views with identical names:

  • "C:\xampp\htdocs\basic\views\site\about.php" ... English
  • "C:\xampp\htdocs\basic\views\site\cs-CZ\about.php" ... Czech

Yii will automatically use the Czech version if needed.

Switching languages + session + lang-dropdown in the top menu

First lets add to file config/params.php attributes with list of supported languages:

<?php
return [
    // ...
    'allowedLanguages' => [
        'en-US' => "English",
        'cs-CZ' => "Česky",
    ],
    'langSwitchUrl' => '/site/set-lang',
];

This list can be displayed in the main menu. Edit file:

  • C:\xampp\htdocs\basic\views\layouts\main.php

And above the Nav::widget add few rows:

    $listOfLanguages = [];
    $langSwitchUrl = Yii::$app->params["langSwitchUrl"];
    foreach (Yii::$app->params["allowedLanguages"] as $langId => $langName) {
        $listOfLanguages[] = ['label' => Yii::t('app', $langName), 'url' => [$langSwitchUrl, 'langID' => $langId]];
    }

and then add one item into Nav::widge

    echo Nav::widget([
        // ...
        'items' => [
            // ...
            ['label' => Yii::t('app', 'Language'),'items' => $listOfLanguages],
            // ...

Now in the top-right corner you can see a new drop-down-list with list of 2 languages. If one is selected, action "site/setLang" is called so we have to create it in SiteController.

Note that this approach will always redirect user to the new action and his work will be lost. Nevertheless this approach is very simple so I am using it in small projects. More complex projects may require an ajax call when language is changed and then updating texts using javascript so reload is not needed and user's work is preserved. But I expect that when someone opens the web, he/she sets the language immediately and then there is no need for further changes.

The setLang action looks like this:

    public function actionSetLang($langID = "") {
        $allowedLanguages = Yii::$app->params["allowedLanguages"];
        $langID = trim($langID);
        if ($langID !== "" && array_key_exists($langID, $allowedLanguages)) {
            Yii::$app->session->set('langID', $langID);
        }
        return $this->redirect(['site/index']);
    }

As you can see when the language is changed, redirection to site/index happens. Also mind that we are not modifying the attribute from config/web.php using Yii::$app->language, but we are saving the value into the session. The reason is that PHP deletes memory after every click, only session is kept.

We then can use the langID-value in other controllers using new method beforeAction:

    public function beforeAction($action) {

        if (!parent::beforeAction($action)) {
            return false;
        }

        Yii::$app->language = Yii::$app->session->get('langID');

        return true;
    }

.. or you can create one parent-controller named for example BaseController. All other controllers will extend it.

<?php

namespace app\controllers;

use Yii;
use yii\web\Controller;

class BaseController extends Controller {

    public function beforeAction($action) {

        if (!parent::beforeAction($action)) {
            return false;
        }

        Yii::$app->language = Yii::$app->session->get('langID');

        return true;
    }

}

As you can see in the snippet above, other controllers must contain row "use app\controllers\BaseController" + "extends BaseController"

Simple access rights

Every controller can allow different users/guests to use different actions. Method behaviors() can be used to do this. If you generate the controller using GII the method will be present and you will just add the "access-part" like this:


// don't forget to add this import:
use yii\filters\AccessControl;

public function behaviors() {
  return [
    // ...
    'access' => [
      'class' => AccessControl::className(),
      'rules' => [
        [
          'allow' => true,
          'roles' => ['@'], // logged in users
          // 'roles' => ['?'], // guests
          // 'matchCallback' => function ($rule, $action) {
            // all logged in users are redirected to some other page
            // just for demonstration of matchCallback
            // return $this->redirect('index.php?r=user/create');
          // }
        ],
      ],
      // All guests are redirected to site/index in current controller:
      'denyCallback' => function($rule, $action) {
        Yii::$app->response->redirect(['site/index']);
      },
    ],
  ];
}

.. This is all I needed so far. I will add more complex snippet as soon as I need it ...

Details can be found here https://www.yiiframework.com/doc/guide/2.0/en/security-authorization.

Nice URLs

Just uncomment section "urlManager" in config/web.php .. htaccess file is already included in the basic demo. In case of problems see this link.

My problem was that images were not displayed when I enabled nice URLs. Smilar discussion here.

// Originally I used these img-paths:
<img src="..\web\imgs\myimg01.jpg"/>

/// Then I had to chage them to this:
Html::img(Yii::$app->request->baseUrl . '/imgs/myimg01.jpg')

// The important change is using the "baseUrl"

Note that Yii::$app->request->baseUrl returns "/myProject/web". No trailing slash.

How to redirect web to subfolder /web

Note: If you are using the advanced demo app, this link can be interesting for you.

Yii 2 has the speciality that index.php is hidden in the web folder. I didnt find in the official documentation the important info - how to hide the folder, because user is not interested in it ...

Our demo application is placed in folder:

  • C:\xampp\htdocs\basic\web\index.php

Now you will need 2 files named .htaccess

  • C:\xampp\htdocs\basic\web\.htaccess
  • C:\xampp\htdocs\basic\.htaccess

The first one is mentioned in chapter Nice URLs and looks like this:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]

The second is simpler:

RewriteEngine on
RewriteRule ^(.*)$ web/$1 [L]

... it only adds the word "web" into all URLs. But first we have to remove the word from URLs. Open file config/web.php and find section request. Add attribute baseUrl:

'request' => [
  // 'cookieValidationKey' => ...
  'baseUrl' => '/basic', // add this line
],

Now things will work for you. But it might be needed to use different value for devel and productive environment. Productive web is usually in the root-folder so baseUrl should be en empty string. I did it like this:

$baseUrlWithoutWebFolder = "";
if (YII_ENV_DEV) {
  $baseUrlWithoutWebFolder = '/basic';
}

// ...

'request' => [
  // 'cookieValidationKey' => ...
  'baseUrl' => $baseUrlWithoutWebFolder,
],

I will test this and if I find problems and solutions I will add them.

Auto redirection from login to desired URL

... to be added ...

What to change when exporting to the Internet

  • Delete file web/index-test.php
  • In file web/index.php comment you 2 first lines containing YII_DEBUG + YII_ENV
  • Delete the text from view site/login which says "You may login with admin/admin or demo/demo."

Saving contact inqueries into DB

DROP TABLE IF EXISTS `contact` ;

CREATE TABLE IF NOT EXISTS `contact` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  `email` VARCHAR(45) NOT NULL,
  `subject` VARCHAR(100) NOT NULL,
  `body` TEXT NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;
  • Create the DB table
  • Generate Model + CRUD using GII
  • In Site controller replace ContactForm with Contact (in section "use" and in actionContact) and in the action change the IF condition:
    use app\models\Contact;
    // ... 
    public function actionContact() {
      $model = new Contact();
      if ($model->load(Yii::$app->request->post()) && $model->save()) {
      // ...
    
  • Open the new contact model and add one attribute and 2 rules:
public $verifyCode;
// ...
  ['verifyCode', 'captcha'],
  ['email', 'email'],

// and translation for Captcha
'verifyCode' => Yii::t('app', 'Verification'),
  • You can also delete one paragraph from view/site/contact
    <p>
    Note that if you turn on the Yii debugger ...
    

Then some security - filtering users in the new ContactController:

public function beforeAction($action) {

  if (!parent::beforeAction($action)) {
    return false;
  }

  $guestAllowedActions = [];

  if (Yii::$app->user->isGuest) {
    if (!in_array($action->actionMethod, $guestAllowedActions)) {
      return $this->redirect(['site/index']);
    }
  }
  
  return true;
}

Tests - unit + opa

... text ...

Adding a google-like calendar

I needed to show user a list of his events in a large calendar so I used library fullcalendar.

Great demo which you can just copy and paste:

/*I added this style to hide vertical scroll-bars*/
.fc-scroller.fc-day-grid-container{
  overflow: hidden !important;
}
  • Don't forget to use these files for example in your view like this:
$this->registerCssFile('@web/css/fullcalendar/fullcalendar.css');
$this->registerCssFile('@web/css/fullcalendar/fullcalendar.print.css', ['media' => 'print']); 

$this->registerJsFile('@web/js/fullcalendar/moment.min.js', ['depends' => ['yii\web\JqueryAsset']]);
$this->registerJsFile('@web/js/fullcalendar/fullcalendar.min.js', ['depends' => ['yii\web\JqueryAsset']]);

// details here:
// https://www.yiiframework.com/doc/api/2.0/yii-web-view

... if you want to go pro, use NPM. The NPM way is described here.

API is here: https://fullcalendar.io/docs ... you can then enhace the calendar config from the example above

In order to make things work I had to force jQuery to be loaded before calendar scripts using file config/web.php like this

   'components' => [
        
		// ...
		
       'assetManager' => [
            'bundles' => [
                'yii\web\JqueryAsset' => [
                    'jsOptions' => [ 'position' => \yii\web\View::POS_HEAD ],
                ],
            ],
        ],

You can customize the calendar in many ways. For example different event-color is shown here. Check the source code.

Scenarios - UNKNOWN SCENARIO EXCEPTION

I have been using scenarios a lot but today I spent 1 hour on a problem - I had 2 scenarios and one of them was just assigned to the model ...

$model->scenario = "abc";

... but had no rule defined yet. I wanted to implement the rule later, but I didnt know that when you set a scenario to your model it must be used in method rules() or defined in method scenarios(). So take this into consideration. I expected that when the scenario has no rules it will just be skipped or deleted.

Richtext / wysiwyg HTML editor - Summernote

If you want to allow user to enter html-formatted text, you need to use some HTML wysiwyg editor, because ordinary TextArea can only work with plain text. It seems to me that Summernote is the simplest addon available:

// Add following code to file layouts/main.php .. 
// But make sure jquery is already loaded !! 
// - Read about this topic in chapter "Adding a google-like calendar"

<!-- include summernote css/js -->
<link href="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote.css" rel="stylesheet">
<script src="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote.js"></script>

// And then in any view you can use this code:

<script>
$(document).ready(function() {
  $('#summernote1').summernote();
  $('#summernote2').summernote();
});
</script>
<div id="summernote1">Hello Summernote</div>

<form method="post">
  <textarea id="summernote2" name="editordata"></textarea>
</form>

On this page I showed how to save Contacts inqueries into database. If you want to use the richtext editor in this section, open view contact/_form.php and just add this JS code:

<script>
$(document).ready(function() {
  $('#contact-body').summernote();
});
</script>

It will be saved to DB as HTML code. But this might be also a source of problems, because user can inject some dangerous HTML code. So keep this in mind.

Now you will also have to modify view contact/view.php like this in order to see nice formatted text:

DetailView::widget([
  'model' => $model,
  'attributes' => [
    // ...
    'body:html',
  ],
])

... to discover all possible formatters, check all asXXX() functions on this page:

SEO optimization

This is not really a YII topic but as my article is some kind of a code-library I will paste it here as well. To test your SEO score you can use special webs. For example seotesteronline, but only once per day. It will show some statistics and recommend enhancements so that your web is nicely shown on FB and Twitter or found by Google.

Important are for example OG meta tags or TWITTER meta tags. They are basicly the same. Read more here. You can test them at iframely.com.

Basic tags are following and you should place them to head:

  • Note that Twitter is using attribute "name" instead of "property" which is defined in OG
  • btw OG was introduced by Facebook. Twitter can process it as well, but SEO optimizers will report an error when Twitter's tags are missing.

<!DOCTYPE html>
<html lang="<?= Yii::$app->language ?>">
<head>

  <meta property="og:site_name" content="European Travel, Inc.">
  <meta property="og:title" content="European Travel Destinations">
  <meta property="og:description" content="Offering tour packages for individuals or groups.">
  <meta property="og:image" content="http://euro-travel-example.com/thumbnail.jpg">
  <meta property="og:url" content="http://euro-travel-example.com/index.htm">
  <meta name="twitter:card" content="summary_large_image">

  <!--  Non-Essential, But Recommended -->
  <meta property="og:site_name" content="European Travel, Inc.">
  <meta name="twitter:image:alt" content="Alt text for image">

  <!--  Non-Essential, But Required for Analytics -->
  <meta property="fb:app_id" content="your_app_id" />
  <meta name="twitter:site" content="@website-username">
  
  <!-- seotesteronline.com will also want you to add these: -->
  <meta name="description" content="blah blah">
  <meta property="og:type" content="website">
  <meta name="twitter:title" content="blah blah">
  <meta name="twitter:description" content="blah blah">
  <meta name="twitter:image" content="http://something.jpg">

Do not forget about file robots.txt and sitemap.xml:

// robots.txt can contain this:
User-agent: *
Allow: /

Sitemap: http://www.example.com/sitemap.xml
// And file sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
  <url>
    <loc>http://example.com/someFile.html</loc>
    <image:image>
      <image:loc>http://example.com/someImg.jpg</image:loc>
    </image:image>
  </url> 
</urlset> 

You can also minify here or here all your files. Adding "microdata" can help as well, but I have never used it. On the other hand what I do is that I compress images using these two sites tinyjpg.com and tinypng.com.

Other useful links

jQuery + draggable/droppable on mobile devices (Android)

JQuery and its UI extension provide drag&drop functionalities, but these do not work on Android or generally on mobile devices. You can use one more dependency called touch-punch to fix the problem. It should be loaded after jQuery and UI.

<!-- jQuery + UI -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>

<!-- http://touchpunch.furf.com/ -->
<!-- Use this file locally -->
<script src="./jquery.ui.touch-punch.min.js"></script>

And then standard code should work:

<!doctype html>

<html lang="en">
  <head>
    <meta charset="utf-8">

    <title>Title</title>

    <!-- jQuery + UI -->
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>

    <!-- http://touchpunch.furf.com/ -->
    <script src="./jquery.ui.touch-punch.min.js"></script>

    <style>
      .draggable {
        width: 100px;
        height: 100px;
        border: 1px solid red;
      }

      .droppable {
        width: 300px;
        height: 300px;
        border: 1px solid blue;
      }

      .over {
        background-color: gold;
      }
    </style>
  </head>

  <body>
    <div class="draggable my1">draggable my1</div>
    <div class="draggable my2">draggable my2</div>
    <div class="droppable myA">droppable myA</div>
    <div class="droppable myB">droppable myB</div>
  </body>


  <script>
    $( function() {

      // All draggables will return to their original position if not dropped to correct droppable
      // ... and will always stay in the area of BODY
      $(".draggable").draggable({ revert: "invalid", containment: "body" });

      // Demonstration of how particular droppables can accept only particular draggables
      $( ".droppable.myA" ).droppable({
        accept: ".draggable.my1",
        drop: function( event, ui ) {

          // positioning the dropped box into the target area
          var dropped = ui.draggable;
          var droppedOn = $(this);
          $(dropped).detach().css({top: 0,left: 0}).appendTo(droppedOn);    
          $(this).removeClass("over");
        },
        over: function(event, elem) {
          $(this).addClass("over");
          console.log("over");
        },
        out: function(event, elem) {
          $(this).removeClass("over");
        }
      });

      // Demonstration of how particular droppables can accept only particular draggables
      $( ".droppable.myB" ).droppable({
        accept: ".draggable.my2",
        drop: function( event, ui ) {

          // positioning the dropped box into the target area
          var dropped = ui.draggable;
          var droppedOn = $(this);
          $(dropped).detach().css({top: 0,left: 0}).appendTo(droppedOn);    
          $(this).removeClass("over");
        },
        over: function(event, elem) {
          $(this).addClass("over");
          console.log("over");
        },
        out: function(event, elem) {
          $(this).removeClass("over");
        }
      });

    });
  </script>

</html>
]]>
0
[wiki] Change default date format in Oracle Fri, 20 Sep 2019 06:15:43 +0000 https://www.yiiframework.com/wiki/2551/change-default-date-format-in-oracle https://www.yiiframework.com/wiki/2551/change-default-date-format-in-oracle lenovo7 lenovo7

Default date format in Oracle is DD-MON-RR (25-JAN-18). With that output, we can't using date formatting.

Too solve this issue, we must change date format oracle like date commonly using

ALTER SESSION SET NLS_DATE_FORMAT = ...

Add this script inside your database connection file

<?php

return [
    'class' => 'yii\db\Connection',
    'dsn' => 'oci:host=127.0.0.1:1521/XE',
    'username' => 'your_username',
    'password' => 'your_password',
    'charset' => 'utf8',

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

    'on afterOpen' => function($event) {
        // $event->sender refers to the DB connection
        $event->sender->createCommand("ALTER SESSION SET NLS_DATE_FORMAT='DD-MM-YYYY hh24:mi:ss'")->execute();
    }    
];
]]>
0
[wiki] Move sources to src Tue, 27 Aug 2019 21:43:50 +0000 https://www.yiiframework.com/wiki/2550/move-sources-to-src https://www.yiiframework.com/wiki/2550/move-sources-to-src samdark samdark

Yii 3 and many Yii 2 package sources are contained within src directory which is convenient since you have less directories to check.

/config
/runtime
/src
  /assets
  /commands
  /controllers
  /mail
  /models
  /views
  /widgets
/tests
/vendor
/web
yii

Let's start with the basic applicaiton template.

  1. Create src directory.
  2. Move source directories there.
  3. Adjust config/web.php:
$config = [
    // ...
    'basePath' => dirname(__DIR__) . '/src',
    'runtimePath' => dirname(__DIR__) . '/runtime',
    'vendorPath' => dirname(__DIR__) . '/vendor',    
    // ...
];

And config/console.php:

$config = [
    // ...
    'basePath' => dirname(__DIR__) . '/src',
    'runtimePath' => dirname(__DIR__) . '/runtime',
    'vendorPath' => dirname(__DIR__) . '/vendor',
    // ...
];

That's it now you have both console and web application source code in src.

]]>
0
[wiki] Nested Set with Yii2 Mon, 01 Apr 2019 07:50:53 +0000 https://www.yiiframework.com/wiki/2549/nested-set-with-yii2 https://www.yiiframework.com/wiki/2549/nested-set-with-yii2 sangprabo sangprabo

The nested set behaviour is an approach to store hierarchical data in relational databases. For example, if we have many categories for our product or items. One category can be a "parent" for other categories, means that one category consists of more than one category. The model can be drawn using a "tree" model. There are other approaches available but what we will learn in this article is specifically the NestedSetsBehavior made by Alexander Kochetov, which utilizing the Modified Preorder Tree Traversal algorithm.

Requirements :

  • Yii2 framework advanced template
  • Yii2 nested sets package

Install the package using composer

It is always recommended to use Composer to install any kind of package or extension for our Yii2-powered project.

$ composer require creocoder/yii2-nested-sets

Create the table

In this article, we will use Category for our model/table name. So we would like to generate the table using our beloved migration tool.

$ ./yii migrate/create create_category_table

We need to modify the table so it contains our desired fields. We also generate three additional fields named position, created_at, and updated_at.

<?php

use yii\db\Migration;

/**
 * Handles the creation for table `category`.
 */
class m160611_114633_create_category extends Migration
{
    /**
     * @inheritdoc
     */
    public function up()
    {
        $this->createTable('category', [
            'id'         => $this->primaryKey(),
            'name'       => $this->string()->notNull(),
            'tree'       => $this->integer()->notNull(),
            'lft'        => $this->integer()->notNull(),
            'rgt'        => $this->integer()->notNull(),
            'depth'      => $this->integer()->notNull(),
            'position'   => $this->integer()->notNull()->defaultValue(0),
            'created_at' => $this->integer()->notNull(),
            'updated_at' => $this->integer()->notNull(),
        ]);
    }

    /**
     * @inheritdoc
     */
    public function down()
    {
        $this->dropTable('category');
    }
}

Then, generate the table using the migration tool.

$ ./yii migrate

If everything is okay, then you could see that a new table named category already exists.

Generate the default CRUD using Gii

To initiate a model, we need to use Gii tool from Yii2. Call the tool from your localhost:8080/gii/model, and fill in the Table Name field with our existing table: category. Fill other fields with appropriate values, and don't forget to give a check to "Generate ActiveQuery" checklist item. This will generate another file that needs to be modified later.

Continue to generate the CRUD for our model with CRUD Generator Tool. Fill in each field with our existing model. After all files are generated, you can see that we already have models, controllers, and views but our work is far from done because we need to modify each file.

Modify models, controllers, and views

The first file we should modify is the model file: Category.

<?php

namespace common\models;

use Yii;
use creocoder\nestedsets\NestedSetsBehavior;

/**
 * This is the model class for table "category".
 *
 * @property integer $id
 * @property string $name
 * @property integer $tree
 * @property integer $lft
 * @property integer $rgt
 * @property integer $depth
 * @property integer $position
 * @property integer $created_at
 * @property integer $updated_at
 */
class Category extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'category';
    }

    public function behaviors() {
        return [
            \yii\behaviors\TimeStampBehavior::className(),
            'tree' => [
                'class' => NestedSetsBehavior::className(),
                'treeAttribute' => 'tree',
                // 'leftAttribute' => 'lft',
                // 'rightAttribute' => 'rgt',
                // 'depthAttribute' => 'depth',
            ],
        ];
    }

    public function transactions()
    {
        return [
            self::SCENARIO_DEFAULT => self::OP_ALL,
        ];
    }

    public static function find()
    {
        return new CategoryQuery(get_called_class());
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['name'], 'required'],
            [['position'], 'default', 'value' => 0],
            [['tree', 'lft', 'rgt', 'depth', 'position', 'created_at', 'updated_at'], 'integer'],
            [['name'], 'string', 'max' => 255],
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id'         => Yii::t('app', 'ID'),
            'name'       => Yii::t('app', 'Name'),
            'tree'       => Yii::t('app', 'Tree'),
            'lft'        => Yii::t('app', 'Lft'),
            'rgt'        => Yii::t('app', 'Rgt'),
            'depth'      => Yii::t('app', 'Depth'),
            'position'   => Yii::t('app', 'Position'),
            'created_at' => Yii::t('app', 'Created At'),
            'updated_at' => Yii::t('app', 'Updated At'),
        ];
    }

    /**
     * Get parent's ID
     * @return \yii\db\ActiveQuery 
     */
    public function getParentId()
    {
        $parent = $this->parent;
        return $parent ? $parent->id : null;
    }

    /**
     * Get parent's node
     * @return \yii\db\ActiveQuery 
     */
    public function getParent()
    {
        return $this->parents(1)->one();
    }

    /**
     * Get a full tree as a list, except the node and its children
     * @param  integer $node_id node's ID
     * @return array array of node
     */
    public static function getTree($node_id = 0)
    {
        // don't include children and the node
        $children = [];

        if ( ! empty($node_id))
            $children = array_merge(
                self::findOne($node_id)->children()->column(),
                [$node_id]
                );

        $rows = self::find()->
            select('id, name, depth')->
            where(['NOT IN', 'id', $children])->
            orderBy('tree, lft, position')->
            all();

        $return = [];
        foreach ($rows as $row)
            $return[$row->id] = str_repeat('-', $row->depth) . ' ' . $row->name;

        return $return;
    }
}

As you can see, we import the extension using the keyword use:

use creocoder\nestedsets\NestedSetsBehavior;

and I also add the TimeStampBehavior for our additional fields, created_at and updated_at

\yii\behaviors\TimeStampBehavior::className(),

Next : Our modification to CategoryController file is at update, create, and delete function.

<?php

namespace backend\controllers;

use Yii;
use common\models\Category;
use common\models\CategorySearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;

/**
 * CategoryController implements the CRUD actions for Category model.
 */
class CategoryController extends Controller
{
    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['POST'],
                ],
            ],
        ];
    }

    /**
     * Lists all Category models.
     * @return mixed
     */
    public function actionIndex()
    {
        $searchModel = new CategorySearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

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

    /**
     * Displays a single Category model.
     * @param integer $id
     * @return mixed
     */
    public function actionView($id)
    {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }

    /**
     * Creates a new Category model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new Category();

        if ( ! empty(Yii::$app->request->post('Category'))) 
        {
            $post            = Yii::$app->request->post('Category');
            $model->name     = $post['name'];
            $model->position = $post['position'];
            $parent_id       = $post['parentId'];

            if (empty($parent_id))
                $model->makeRoot();
            else
            {
                $parent = Category::findOne($parent_id);
                $model->appendTo($parent);
            }

            return $this->redirect(['view', 'id' => $model->id]);
        }

        return $this->render('create', [
                'model' => $model,
            ]);
    }

    /**
     * Updates an existing Category model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ( ! empty(Yii::$app->request->post('Category'))) 
        {
            $post            = Yii::$app->request->post('Category');

            $model->name     = $post['name'];
            $model->position = $post['position'];
            $parent_id       = $post['parentId'];

            if ($model->save())            
            {
                if (empty($parent_id))
                {
                    if ( ! $model->isRoot())
                        $model->makeRoot();
                }
                else // move node to other root 
                {
                    if ($model->id != $parent_id)
                    {
                        $parent = Category::findOne($parent_id);
                        $model->appendTo($parent);
                    }
                }

                return $this->redirect(['view', 'id' => $model->id]);
            }
        }

        return $this->render('update', [
            'model' => $model,
        ]);
    }

    /**
     * Deletes an existing Category model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function actionDelete($id)
    {
        $model = $this->findModel($id);

        if ($model->isRoot())
            $model->deleteWithChildren();
        else 
            $model->delete();

        return $this->redirect(['index']);
    }

    /**
     * Finds the Category model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Category the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Category::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }
}

As for our views files, we need to remove unnecessary fields from our form, such as the lft, rgt, etc, and add the sophisticated parent field to be a dropdown list. This requires a lot of effort, as you can see on the getTree function on our model.

<?php

use yii\helpers\Html;
use yii\widgets\ActiveForm;

use common\models\Category;

/* @var $this yii\web\View */
/* @var $model common\models\Category */
/* @var $form yii\widgets\ActiveForm */
?>

<div class="category-form">

    <?php $form = ActiveForm::begin(); ?>

    <?= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>

    <div class='form-group field-attribute-parentId'>
    <?= Html::label('Parent', 'parent', ['class' => 'control-label']);?>
    <?= Html::dropdownList(
        'Category[parentId]',
        $model->parentId,
        Category::getTree($model->id),
        ['prompt' => 'No Parent (saved as root)', 'class' => 'form-control']
    );?>

    </div>

    <?= $form->field($model, 'position')->textInput(['type' => 'number']) ?>

    <div class="form-group">
        <?= Html::submitButton($model->isNewRecord ? Yii::t('app', 'Create') : Yii::t('app', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
    </div>

    <?php ActiveForm::end(); ?>

</div>

Complete files can be found at my GitHub page: https://github.com/prabowomurti/learn-nested-set

The screencast (subtitled English) here : https://www.youtube.com/watch?v=MjJEjF1arHs

]]>
0
[wiki] Events registration examples Mon, 11 Mar 2019 14:04:14 +0000 https://www.yiiframework.com/wiki/2548/events-registration-examples https://www.yiiframework.com/wiki/2548/events-registration-examples minitia82 minitia82

Register an event handler at Object-Level

e.g inside the init method of a Model

// this should be inside your model class. For example User.php
public function init(){
  $this->on(self::EVENT_NEW_USER, [$this, 'sendMail']);
  $this->on(self::EVENT_NEW_USER, [$this, 'notification']);
  // first parameter is the name of the event and second is the handler. 
  // For handlers I use methods sendMail and notification
  // from $this class.
  parent::init(); // DON'T Forget to call the parent method.
}

from https://stackoverflow.com/questions/28575636/how-to-use-events-in-yii2

Register an event handler at Class-Level

To register event handlers at Class-Level a good place can be inside the bootstrap process.

So, you can put the registration code as

Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
    Yii::debug(get_class($event->sender) . ' is inserted');
});

in a php file like the bootstrap.php used in Advanced-Template or through configuration, with your custom component for example

see docs

(https://www.yiiframework.com/doc/guide/2.0/en/structure-applications#bootstrap or https://www.yiiframework.com/doc/guide/2.0/en/structure-extensions#bootstrapping-classes)

]]>
0
[wiki] (draft) Understanding Yii 3 Sun, 16 Feb 2020 13:25:06 +0000 https://www.yiiframework.com/wiki/2547/draft-understanding-yii-3 https://www.yiiframework.com/wiki/2547/draft-understanding-yii-3 machour machour

Understanding Yii 3

  1. Introduction
  2. Changes overview
  3. Yii 3 composer packages
  4. Running your first Yii 3 powered application

Note: The information here is outdated. You can check yii-demo for current Yii 3 features.

Since this Wiki page is getting bigger and bigger, I decided to document things in a Yii 3 project instead.

Project source can be found at https://github.com/machour/yii3-kitchen-sink Live web site: https://yii3.idk.tn/

Follow the repo/website to get fresher informations, or better yet, pull the project and run it by yourself to get acquainted with Yii3

Introduction

This document is intended for an audience already familiar with Yii2. It's meant to bring together all information related to Yii 3 in one place to make it easier to get on track.

Yii 3 is the second major rewrite of the Yii framework.

Originally started in the 2.1 branch, it was later decided to switch to the 3.X series because of all the backward compatibility breakage. Starting with 3.0, Yii will follow the Sementic Versionning.

This rewrite addresses a lot of issues Yii 2 suffered from, like the framework being too coupled with jQuery, bower, bootstrap. [TODO: add more grieffs about Yii2]

Changes overview

Here are the main changes in Yii 3. You can check the complete CHANGELOG for an exhaustive list.

Source code splitting

The framework source code have been split into several packages, and at its core level, Yii no longer makes assumptions about your development stack, or the features you will be using.

This enable you to cherry pick the packages you need to compose your application.

This re-organisation is also a great news for maintainance, as these packages will be released separately, thus allowing more frequent updates.

Autoloading

The custom PHP class autoloader have been removed in favor of Composer's PSR-4 implementation. This means that in order for Yii to see your classes, you will have to explicitly register your namespace in composer.json. We will see an example later.

PSR compatibility

Yii 3 takes some positive steps following the PHP-FIG recommendations, by implementing the following PSRs:

  • Logging is now compliant with PSR-3
  • Caching is now compliant with PSR-16
  • Dependency Injection is now compliant with PSR-11
Application configuration

If you've ever installed an extension using Yii 2, you may/certainly have found yourself on the extension README file, looking for the chunk of configuration to copy/paste in your own config/main.php file.

This can often lead to:

  • a huge configuration file (which you may have decided to split into smaller files)
  • non-trivials configurations update when a new version of the extension is realeased with new/changed configurations options.

Yii 3 takes another approach. Every package bundle its own configuration, and will probably work out of the box. And you may override them, if you need to, from your configuration file.

This is all done by leveraging the hiqdev/composer-config-plugin composer plugin, which takes care of scanning & merging all the configurations when you run composer dump-autoload (also know as composer du).

You can read Yii2 projects alternative organization for an in-depth explanation of the motivation behind hiqdev/composer-config-plugin.

Packages authors will have the responsibility to avoid introducing BC breaks, by adopting a strict sementical versionning.

Dependencies injection

[TODO]

Yii 3 composer packages

Here are the new packages introduced in Yii 3, which can be found in this official list.

Let's introduce them briefly:

The Framework

This is the new kernel of Yii. It defines the base framework and its core features like behaviors, i18n, mail, validation..

You will rarely want to directly install yiisoft/yii-core. Instead, you will install one or more of the following:

This three packages, considered as Extensions, are responsible for implementing the basic functionnalities of each "channel" they refer to:

  • yii-console implements all that you need to build a console application (the base Controller for commands, the Command helper, ..)
  • yii-web implements all that you need to build a web application (Assets management, Sessions, Request handling ..)
  • yii-rest implements all that you need to build a REST interface (ActiveController, ..)
Librairies

In Yii 3, libraries do not depend on Yii and are meant to be usable outside the framework. Their package name is yiisoft/something without yii-prefix.

Drivers for yiisoft/db

The various drivers for DB have also been separated into packages:

Extensions

Extensions depends (at least) on yii-core. Aside from the 3 extensions already encountered above (yii-console, yii-web, yii-api), these packages are available

Development
View rendering engines
Data rendering
JS & CSS Frameworks integration
Widgets
Misc
Yii project template and application bases

This is a very basic Yii project template, that you can use to start your development.

You will probably want to pick one or more of these three starters to install in your project next:

Let's try running the web base template in the next section.

Running your first Yii 3 powered application

Let's try running a web application using Yii 3, and the provided project template.

Installing the project template
composer create-project --prefer-dist --stability=dev yiisoft/yii-project-template myapp
cd myapp

Here's the created structure:

.
├── LICENSE
├── README.md
├── composer.json
├── composer.lock
├── config
│   ├── common.php
│   └── params.php
├── docker-compose.yml
├── hidev.yml
├── public
│   ├── assets
│   ├── favicon.ico
│   ├── index.php
│   └── robots.txt
├── runtime
└── vendor

You won't be able to start the web server right away using ./vendor/bin/yii serve, as it will complain about not knowing the "app" class.

In fact, this project template only introduce the bare minimum in your application: Caching, Dependencies injection, and Logging. The template doesn't make an assumption about the kind of application you're building (web, cli, api).

You could start from scratch using this bare template, select the extensions & packages you want to use and start developing, or you can pick one of the three starters provided.

Installing the web starter

Since we're doing a web application, we will need an asset manager. We can pick either one of those:

  • Asset-packagist & composer-merge-plugin (requires only PHP)
  • Foxy (requires npm or yarn)

Let's go with foxy (personal taste since composer is so slow from Tunisia):

composer require "foxy/foxy:^1.0.0"

We can now install the yii-base-web starter and run our application:

composer require yiisoft/yii-base-web
vendor/bin/yii serve

By visiting http://localhost:8080/, you should now see something like this:

50153967-44a6af00-02c8-11e9-9914-ceb463065cdf.png

Checking back our project structure, nothing really changed, aside from the creation of these three entries:

  • node_modules/
  • package-lock.json
  • package.json

So where do what we see in the browser comes from ?

Exploring yiisoft/yii-base-web structure:

If you explore the folder in vendor/yiisoft/yii-base-web, you will see that the template is in fact a project itself, with this structure:

.
├── LICENSE.md
├── README.md
├── composer.json
├── config
│   ├── common.php
│   ├── console.php
│   ├── env.php
│   ├── messages.php
│   ├── params.php
│   └── web.php
├── phpunit.xml.dist
├── public
│   └── css
│       └── site.css
├── requirements.php
├── runtime
└── src
    ├── assets
    │   └── AppAsset.php
    ├── commands
    │   └── HelloController.php
    ├── controllers
    │   └── SiteController.php
    ├── forms
    │   ├── ContactForm.php
    │   └── LoginForm.php
    ├── mail
    │   └── layouts
    ├── messages
    │   ├── et
    │   ├── pt-BR
    │   ├── ru
    │   └── uk
    ├── models
    │   └── User.php
    ├── views
    │   ├── layouts
    │   └── site
    └── widgets
        └── Alert.php

The folders and files should make sense to you if you already developed applications using Yii2 and the basic template.

]]>
0
[wiki] Batch Gridview data ajax send splitted in chunks displaying bootstrap Progress bar Sat, 24 Nov 2018 10:48:47 +0000 https://www.yiiframework.com/wiki/2546/batch-gridview-data-ajax-send-splitted-in-chunks-displaying-bootstrap-progress-bar https://www.yiiframework.com/wiki/2546/batch-gridview-data-ajax-send-splitted-in-chunks-displaying-bootstrap-progress-bar toaster toaster

The scenario in which this wiki can be useful is when you have to send an (huge) array of model ids and perform a time consuming computation with it like linking every model to other models. The idea is to split the array into smaller arrays and perform sequential ajax requests, showing the calculation progress using a Bootstrap Progress bar.

I have created a Country model and generated the CRUD operations using Gii. The model class look like this:

namespace app\models;
use Yii;
use yii\helpers\ArrayHelper;

/**
 * This is the model class for table "country".
 *
 * @property string $code
 * @property string $name
 * @property int $population
 *
 */
class Country extends \yii\db\ActiveRecord {

    /**
     * {@inheritdoc}
     */
    public static function tableName() {
        return 'country';
    }

    /**
     * {@inheritdoc}
     */
    public function rules() {
        return [
            [['code', 'name'], 'required'],
            [['population'], 'integer'],
            [['code'], 'string', 'max' => 3],
            [['name'], 'string', 'max' => 52],
            [['code'], 'unique'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels() {
        return [
            'code' => 'Code',
            'name' => 'Name',
            'population' => 'Population',
        ];
    }
    /**
     * 
     * @return array
     */
    public static function getCodes(){
        return array_keys(ArrayHelper::map(Country::find()->select('code')->asArray()->all(), 'code', 'code'));
    }
}

I have also created another model that will take care to validate the ids and perform the time consuming calculation through the process() function. (in this example I just use a sleep(1) that will wait for one second for each element of the $codes array). I also want to keep track of the processed models, incrementing the $processed class attribute.

<?php

namespace app\models;

use app\models\Country;

/**
 * Description of BatchProcessForm
 *
 * @author toaster
 */
class BatchProcessForm extends \yii\base\Model {

    /**
     * The codes to process
     * @var string
     */
    public $codes;

    /**
     * The number of codes processed
     * @var integer
     */
    public $processed = 0;

    /**
     * @return array the validation rules.
     */
    public function rules() {
        return [
            ['codes', 'required'],
            ['codes', 'validateCodes'],
        ];
    }

    /**
     * Check whether codes exists in the database
     * @param type $attribute
     * @param type $params
     * @param type $validator
     */
    public function validateCodes($attribute, $params, $validator) {
        $input_codes = json_decode($this->$attribute);
        if (null == $input_codes || !is_array($input_codes)) {
            $this->addError($attribute, 'Wrong data format');
            return;
        }
        $codes = Country::getCodes();
        if (!array_intersect($input_codes, $codes) == $input_codes) {
            $this->addError($attribute, 'Some codes selected are not recognized');
        }
    }

    /**
     * Process the codes
     * @return boolean true if everything goes well
     */
    public function process() {
        if ($this->validate()) {
            $input_codes = json_decode($this->codes);
            foreach ($input_codes as $code) {
                sleep(1);
                $this->processed++;
            }
            return true;
        }
        return false;
    }

}

The code for the controller action is the following:

/**
 * Process an array of codes
 * @return mixed
 * @throws BadRequestHttpException if the request is not ajax
 */
public function actionProcess(){
    if(Yii::$app->request->isAjax){
        Yii::$app->response->format = Response::FORMAT_JSON;
        $model = new BatchProcessForm();
        if($model->load(Yii::$app->request->post()) && $model->process()){
            return ['result' => true, 'processed' => $model->processed];
        }
        return ['result' => false, 'error' => $model->getFirstError('codes')];
    }
    throw new \yii\web\BadRequestHttpException;
}

In my index.php view I have added a CheckboxColumn as first column of the Gridview that allows, out of the box, to collect the ids of the models selected via Javascript (in this case the values of the code attribute). I have also added a button-like hyperlink tag to submit the collected data (with id batch_process) and a Bootstrap Progress bar inside a Modal Window. The code of my view is the following:

<?php

use yii\helpers\Html;
use yii\grid\GridView;
use yii\bootstrap\Modal;

/* @var $this yii\web\View */
/* @var $searchModel app\models\CountrySearch */
/* @var $dataProvider yii\data\ActiveDataProvider */

$this->title = 'Countries';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="country-index">

    <h1><?= Html::encode($this->title) ?></h1>
    <?php // echo $this->render('_search', ['model' => $searchModel]); ?>

    <p>
        <?= Html::a('Create Country', ['create'], ['class' => 'btn btn-success']) ?>
        <?= Html::a('Batch Process', ['process'], ['class' => 'btn btn-info', 'id' => 'batch_process']) ?>
    </p>

    <?=
    GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'options' => [
            'id' => 'country-index'
        ],
        'columns' => [
            ['class' => 'yii\grid\CheckboxColumn'],
            'code',
            'name',
            'population',
            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]);
    ?>
</div>

<?php Modal::begin(['header' => '<h5 id="progress">0% Complete</h5>', 'id' => 'progress-modal', 'closeButton' => false]); ?>

<?=
yii\bootstrap\Progress::widget([
    'percent' => 0,
     'options' => ['class' => 'progress-success active progress-striped']
]);
?>

<?php Modal::end(); ?>

<?php $this->registerJsFile('@web/js/main.js', ['depends' => [\app\assets\AppAsset::class]]); ?>

The Javascript file that split in chunks the array and send each chunk sequentially is main.js. Although I have registered the Javascript file using registerJsFile() method I highly recommend to better handle and organize js files using Asset Bundles. Here is the code:


function updateProgressBar(percentage) {
    var perc_string = percentage + '% Complete';
    $('.progress-bar').attr('aria-valuenow', percentage);
    $('.progress-bar').css('width', percentage + '%');
    $('#pb-small').text(perc_string);
    $('#progress').text(perc_string);
}

function disposeProgressBar(message) {
    alert(message);
    $('#progress-modal').modal('hide');
}

function showProgressBar() {
    var perc = 0;
    var perc_string = perc + '% Complete';
    $('.progress-bar').attr('aria-valuenow', perc);
    $('.progress-bar').css('width', perc + '%');
    $('#pb-small').text(perc_string);
    $('#progress').text(perc_string);
    $('#progress-modal').modal('show');
}

function batchSend(set, iter, take, processed) {
    var group = iter + take < set.length ? iter + take : set.length;
    var progress = Math.round((group / set.length) * 100);
    var dataObj = [];
    for (var i = iter; i < group; i++) {
        dataObj.push(set[i]);
    }
    iter += take;
    $.ajax({
        url: '/country/process', ///?r=country/process
        type: 'post',
        data: {'BatchProcessForm[codes]': JSON.stringify(dataObj)},
        success: function (data) {
            if (data.result) {
                updateProgressBar(progress);
                processed += data.processed;
                if (progress < 100) {
                    batchSend(set, iter, take, processed);
                } else {
                    var plural = processed == 1 ? 'country' : 'countries';
                    disposeProgressBar(processed + ' ' + plural + ' correctly processed');
                }
                return true;
            }
            disposeProgressBar(data.error);
            return false;
        },
        error: function () {
            disposeProgressBar('Server error, please try again later');
            return false;
        }
    });
}

$('#batch_process').on('click', function (e) {
    e.preventDefault();
    var keys = $('#country-index').yiiGridView('getSelectedRows');
    if (keys.length <= 0) {
        alert('Select at least one country');
        return false;
    }
    var plural = keys.length == 1 ? 'country' : 'countries';
    if (confirm(keys.length + ' ' + plural + ' selected.. continue?')) {
        showProgressBar();
        batchSend(keys, 0, 5, 0);
    }
});

The first three functions take care to show, update and hide the progress bar inside the modal window, while the batchSend function perform the array split and, using recursion, send the data through post ajax request, encoding the array as JSON string and calling itself until there is no more chunks to send. The last lines of code bind the click event of the #batch_process button checking if at least one row of Gridview has been selected. The number of elements on each array chunk can be set as the third argument of the batchSend function (in my case 5), you can adjust it accordingly. The first parameter is the whole array obtained by the yiiGridView('getSelectedRows') function. The final result is something like this:

batch_send1.gif

...looks cool, isn't it?

]]>
0
[wiki] Using multiple models in an identity Tue, 29 Jan 2019 23:11:01 +0000 https://www.yiiframework.com/wiki/2545/using-multiple-models-in-an-identity https://www.yiiframework.com/wiki/2545/using-multiple-models-in-an-identity samdark samdark

Let's assume we have two models: Customer and Supplier and we want both to log in. Yii is quite flexible when it comes to authentication and authorization so it's possible.

First of all, Yii assumes that there's a single type of user component used at once. Still, we're able to create a universal Identity that works with both customers and suppliers.

So we create models/Identity.php:

final class Identity implements IdentityInterface
{
    const TYPE_CUSTOMER = 'customer';
    const TYPE_SUPPLIER = 'supplier';

    const ALLOWED_TYPES = [self::TYPE_CUSTOMER, self::TYPE_SUPPLIER];

    private $_id;
    private $_authkey;
    private $_passwordHash;

    public static function findIdentity($id)
    {
        $parts = explode('-', $id);
        if (\count($parts) !== 2) {
            throw new InvalidCallException('id should be in form of Type-number');
        }
        [$type, $number] = $parts;

        if (!\in_array($type, self::ALLOWED_TYPES, true)) {
            throw new InvalidCallException('Unsupported identity type');
        }

        $model = null;
        switch ($type) {
            case self::TYPE_CUSTOMER:
                $model = Customer::find()->where(['id' => $number])->one();
                break;
            case self::TYPE_SUPPLIER:
                $model = Supplier::find()->where(['id' => $number])->one();
                break;
        }

        if ($model === null) {
            return false;
        }


        $identity = new Identity();
        $identity->_id = $id;
        $identity->_authkey = $model->authkey;
        $identity->_passwordHash = $model->password_hash;
        return $identity;
    }

    public static function findIdentityByAccessToken($token, $type = null)
    {
        $model = Customer::find()->where(['token' => $token])->one();
        if (!$model) {
            $model = Supplier::find()->where(['token' => $token])->one();
        }

        if (!$model) {
            return false;
        }

        if ($model instanceof Customer) {
            $type = self::TYPE_CUSTOMER;
        } else {
            $type = self::TYPE_SUPPLIER;
        }

        $identity = new Identity();
        $identity->_id = $type . '-' . $model->id;
        $identity->_authkey = $model->authkey;
        $identity->_passwordHash = $model->password_hash;
        return $identity;
    }

    public function validatePassword($password)
    {
        return password_verify($password, $this->_passwordHash);
    }

    public static function findIdentityByEmail($email)
    {
        $model = Customer::find()->where(['email' => $email])->one();
        if (!$model) {
            $model = Supplier::find()->where(['email' => $email])->one();
        }

        if (!$model) {
            return false;
        }

        if ($model instanceof Customer) {
            $type = self::TYPE_CUSTOMER;
        } else {
            $type = self::TYPE_SUPPLIER;
        }

        $identity = new Identity();
        $identity->_id = $type . '-' . $model->id;
        $identity->_authkey = $model->authkey;
        $identity->_passwordHash = $model->password_hash;
        return $identity;
    }

    public function getId()
    {
        return $this->_id;
    }

    public function getAuthKey()
    {
        return $this->_authkey;
    }

    public function validateAuthKey($authKey)
    {
        return $this->getAuthKey() === $authKey;
    }
}

In the above we assume that our ids are like customer-23 or supplier-34. When we need to get identity instance we split the id by - and getting both type and integer id for the model corresponding to that type.

Having identity we can tell Yii to use it via config/main.php:

[
    // ...
    'components' => [
        // ...
        'user' => [
            'identityClass' => 'app\models\Identity',
            'enableAutoLogin' => true,
        ],
    ],
],

The only thing left is to adjust models\LoginForm.php:

class LoginForm extends Model
{
    // ...
    
    public function getUser()
    {
        if ($this->_user === false) {
            $this->_user = Identity::findIdentityByEmail($this->username);
        }

        return $this->_user;
    }
}

That's it. Now you can log in using both models.

]]>
0
[wiki] Update and Delete buttons on Breadcrumb Mon, 17 Feb 2020 12:24:41 +0000 https://www.yiiframework.com/wiki/2544/update-and-delete-buttons-on-breadcrumb https://www.yiiframework.com/wiki/2544/update-and-delete-buttons-on-breadcrumb adinugro adinugro

The definition of breadcrumbs according to its documentation is as follow: Breadcrumbs displays a list of links indicating the position of the current page in the whole site hierarchy.

We can define the breadcrumbs easily by adding these lines.

$this->title = $model->formNo;
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Supplier receiving'), 'url' => ['index', 'type' => $model->type]];
$this->params['breadcrumbs'][] = ['label' => $this->title, 'url' => ['view', 'id' => $model->id]];

Reading the documentation, I encountered the template keyword. I was excited about the possibility to add buttons into breadcrumbs. Add these lines and you would have buttons on the right side of the breadcrumb.

$this->title = $model->formNo;
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Supplier receiving'), 'url' => ['index', 'type' => $model->type]];
$this->params['breadcrumbs'][] = ['label' => $this->title, 'url' => ['view', 'id' => $model->id]];
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Delete'), 'template' =>
    Html::tag('span', Html::a(Html::icon('glyphicon glyphicon-trash white') . ' ' . Yii::t('app', 'Delete'), Url::to(['delete', 'id' => $model->id]), [
                'class' => 'btn btn-xs btn-danger',
                'title' => Yii::t('app', 'Delete'),
                'data-pjax' => '0',
                'data-method' => 'POST',
                'data-confirm' => Yii::t('app', 'Are you sure you want to delete this supplier receiving?'),
                    ]
            ), ['class' => 'pull-right'])];
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Update'), 'template' => Html::tag('span', Html::a(
                    Html::icon('glyphicon glyphicon-pencil') . ' ' . Yii::t('app', 'Update'), Url::to(['update', 'id' => $model->id, 'inview' => 1]), [
                'class' => 'btn btn-xs btn-warning',
                'title' => Yii::t('app', 'Update'),
                'role' => 'modal-remote',
                'data-toggle' => 'tooltip',
                    ]
            ), ['class' => 'pull-right', 'style' => 'margin-right: 5px;'])];

Actually you can use this line to define the template, but $links means the full link. A suggestion to the developer is to make the $url and $label variables to be available like $links, so that we can make the template more flexible and meaningful than above codes.

FYI,

  1. I use ajaxcrud extension, so that the update link has 'role' => 'modal-remote' to allow the update process taken place on the modal window. I added a inview parameter to distinguish the update from index or view. Both actions will be called using ajax request and will show the form on the modal, but the index update will redirect to index and refresh gridview while in the view, it should redirect to the view.
  2. On the template, I used span over li to make ' / ' character not appear on the links buttons.

A simple tip to make the view layout efficient.

]]>
0
[wiki] When to use Active Record Mon, 17 Feb 2020 12:24:41 +0000 https://www.yiiframework.com/wiki/2541/when-to-use-active-record https://www.yiiframework.com/wiki/2541/when-to-use-active-record samdark samdark

When to use Active Record is a common question among developers, Yii and overall.

I have about 500K records in my DB tables and each query there are 2 or 3 joins. I was getting data via AR, about a hundred records a time and noticed that using createCommand consumes less memory and CPU. I think with DAO instead of AR it will be faster.

It is true that Active Record consumes memory for storing objects and CPU cycles for instantiate these objects.

Is AR bad? It it for small projects only? We have lots of inserts, about 5000 records an hour and we're using AR for that. Should we re-write?

AR is a pleasure to use when you're managing not that many records at the same time. CRUD is very easy with it. Yii 2 dirty attribute support (only what was modified is saved) off-loads database and mitigates many parallel editing conflicts. If you don't have complex logic in your app and you don't need entity abstractions, AR is an ideal choice.

AR is OK for queries not too complicated when they return no more than a hundred objects. Yes, it is faster and less memory consuming to use query builder or asArray() but working with arrays is significantly less convenient.

For complex queries, such as aggregates or reports, AR isn't recommended. These are easier to express with query builder or SQL. Same goes for import and export routines.

]]>
0
[wiki] Getting information from the current locale Thu, 09 Aug 2018 18:56:33 +0000 https://www.yiiframework.com/wiki/2540/getting-information-from-the-current-locale https://www.yiiframework.com/wiki/2540/getting-information-from-the-current-locale CeBe CeBe

Yii 2.0 comes with a formatter component to format dates, numbers, and other values for international users according to their locale. This is very useful for displaying data. When working with incoming data or when using enhanced input methods like the MaskedInput widget you sometimes need to access the symbols used by the formatter to generate a pattern or parse the input.

The Yii formatter does not have methods to access this data, it relies on the PHP intl extension for formatting. If you want to access the formatting symbols you need to work with intl directly.

Getting decimal and thousand separator

$locale = 'de_DE';

$formatter = new \NumberFormatter($locale,\NumberFormatter::DECIMAL);

$decimalSeparator = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
$thousandSeparator = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL);

There are more symbols than shown above, for the full list of symbols see the list of NumberFormatter constants in the php manual.

Getting a currency symbol

Getting the currency symbol is a bit more complicated if you do not want the symbol of the default currency of a locale, but another currency:

$locale = 'de_DE'; // get the currency symbol of Germanys default currency EUR = "€"
$locale = 'de_DE@currency=USD'; // get the currency symbol of USD in German locale = "$"

$formatter = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);

$currencySymbol = $formatter->getSymbol(\NumberFormatter::CURRENCY_SYMBOL);

Note, that the NumberFormatter is instantiated with the CURRENCY constant instead of DECIMAL here.

Since Yii 2.0.14 Yii has this method built-in in the Locale class.

]]>
0
[wiki] What SQL-Statement creates yii? Fri, 27 Jul 2018 11:28:13 +0000 https://www.yiiframework.com/wiki/2539/what-sql-statement-creates-yii https://www.yiiframework.com/wiki/2539/what-sql-statement-creates-yii Necip Necip

The usual way to find out what Yii has created for an SQL query is to mutilate the SQL in the sourcecode and call the program again so that the SQL statement with errors is displayed. Or you can use the SQL logger, which must be switched on and off each time and logs all SQL statements, which leads to an enormous slowdown in program execution and decelerates your workflow.

These all is not necessary if you use XDebug and place a breakpoint after the function "getCommandBuilder" in the CActiveRecord.php and take a closer look at the return value in $command.

This database access is created in yii:

yii-sql11.png?w=474

This code is called by the yii framework

yii-sql21.png?w=474

getCommandBuilder returns this data structure with the generated SQL statement:

yii-sql32.png?w=474

Path to CActiveRecord.php

C:\xampp\htdocs\your_project\yii\framework\db\ar\CActiveRecord.php

]]>
0
[wiki] How to organize Design "things" in Yii 2 (themes and layout) Thu, 23 Aug 2018 17:32:30 +0000 https://www.yiiframework.com/wiki/2538/how-to-organize-design-things-in-yii-2-themes-and-layout https://www.yiiframework.com/wiki/2538/how-to-organize-design-things-in-yii-2-themes-and-layout olissongs olissongs
  1. Via "theming"
  2. Using layouts
  3. Both
  4. UseCase one
  5. UseCase two

Sometimes the App needs a nicer look & feel, so its necessary to organize the assets for this and Yii can help a lot to make it easy. In this article I provide tips for handling multiple "Designs". I use these three features:

  • AssetBundle
  • Layouts
  • Themes

What do you need for desingning your website:

  • CSS files
  • JS files
  • some Images or other media.

Lets call it "DesignAssets"

What Yii needs:

  • one or more Layout files
  • the view files
  • AssetBundle set in the layout-file

Lets call it "DesignTemplates".

So how to bundle these and where is the best place to put them in your application directory?

Via "theming"

  • myYiiApp
    • ...
    • designs
      • myDesign1
        • put here all your "DesignTemplates" (AssetBundle,layout, and view folder/files, when using themes)

Using layouts

  • myYiiApp
    • ...
    • views
      • layouts
        • myDesign1.php (layout file)
      • ...

Both

  • myYiiApp

    • ...
    • web

      • designs
        • myDesign1
          • put here all your "DesignAssets" (css, js, img, etc.)
    • ...

So you can work with the default security file-rights for the correct purpose.

UseCase one

Write an App and distribute it to some Customer. Here can it be necessary that every customer wants a personal design.

Solution: Using themes

you config in your web.php or main.php (advanced template) whats documented here

if you need special "DesignAssets"

put your "DesignAssets" und the web directory myYiiApp/web/designs/myDesign1/css and js and img folders

customize your myYiiApp/designs/myDesign1/assets/MyDesignAsset.php

and your layout file myYiiApp/designs/myDesign1/layout/main.php

thats it.

UseCase two

you need several designs for e.g. controllers. so my remmmendation is using layouts, because easy switching in controller action.

Solution with layouts

there is no need to configure anything in the config files

if you need special "DesignAssets"

put your "DesignAssets" und the web directory myYiiApp/web/designs/myDesign1/css and js and img folders

create and customize your myYiiApp/assets/MyDesignAsset.php

and your layout file myYiiApp/views/layout/mydesign1.php

in your controller action set your layout.

public function actionIndex() {
    $this->layout = 'mydesign1';
}

may it helps your start with designing Yii.

]]>
0
[wiki] An alternative way to ElasticSearch Sun, 12 Aug 2018 07:49:50 +0000 https://www.yiiframework.com/wiki/2255/an-alternative-way-to-elasticsearch https://www.yiiframework.com/wiki/2255/an-alternative-way-to-elasticsearch Necip Necip

This article is for those who have dealt with the complexity of Elasticsearch or any other indexing machines and are looking for an easier way to index the existing database without additional effort.

Why this post?

The amount of data increases every day. As a result, the search in the databases becomes longer and longer. Conventional data structures must be realigned in order to be able to access information more quickly. There are already database systems like Elasticsearch that can do this. However, such systems also have disadvantages.

The most noticeable major drawbacks are:

  • Learning a new query language. SQL-Plugin won’t get you far or is not flexible enough.
  • The existing programs must be rewritten in order to process the new result sets appropriately.
  • The safety regulations must be defined again.
  • A second database must be set up, which in principle contains the same data.

Who will benefit from this post?

This information is useful for any programmer who wants to integrate an index database into his existing system in a simple way without additional effort.

The basic idea behind Indexing-machines

We will use this simple Table to demonstrate what an Index-machine does

Tablename: object

UID	TITLE	DESCRIPTION
4711	Rudyard Kipling	If you can keep your head when all about you …
4712	John Magee	I have slipped the surly bonds of Earth and danced the skies on laugher-silvered wings …
4713	Wiliam Wordsworth	Ten thousand saw I at a glance, Tossing their heads in sprightly dance…

With this request we can find very specific texts in this single table:

SELECT ID, Title, Description
FROM object
WHERE Description like '%head%'

But what if we want to find ‚%head%‘ in all tables of our database? We have to write a code to do this job for us. This is inefficent and will work very slowy. The idea behind Elasticsearch and other indexing tables is – so far I understood – to break the strings in single tokens. That means in a very easy way that we have to transform the horicontal order of the table into a vertical order.

Tablename: ncp_index

UID	TABLENAME	FIELDNAME	ID	TOKEN
1001	object	Description	4711	if
1002	object	Description	4711	you
1003	object	Description	4711	can
…				
1010	object	Description	4712	I
1011	object	Description	4712	have
1012	object	Description	4712	slipped
…				

We can tokenize any field of any table of our database into the table ncp_index. Now we can find with a single query very fast any (tokenized) word in our hole database.

SELECT Tablenname, Fieldname, Token
FROM ncp_index
WHERE Token like '%head%'

That is the secret of any Index-Searchengine. Yes, the ncp_index table has a lot of redundant data that we can normalize as follows:

Every field is stored in a system table and has a unique id. let us call it field_id Every content of a field has a lot of same words. These words should be stored only once in a separat words-table.

Our ncp_index table looks now so:

UID	FIELD_ID	ID	TOKEN_ID
1001	123	4711	1
1002	123	4711	2
1003	123	4711	31010	123	4712	4
1011	123	4712	5
1012	123	4712	6
…	
		
Systemtable: fields

UID	TABLENAME	NAME
122	object	Name
123	object	Description
…		

Tablename: word

UID	TOKEN
1	if
2	you
3	can
…	

Some basic examples

/**
 * @author ncp <necips@live.de>
 */
class NCPSearch
{
    /**
     * @param $model
     * @param $tablename
     * @param $fieldnames
     */
    public static function delete_ncp_search_item($model, $tablename) {
        $criteria = new CDbCriteria;
        $criteria->condition = "tablename = :tablename " .
                    "AND id = :id ";
        $criteria->params[":tablename"] = $tablename;
        $criteria->params[":id"] = $model->uid;
        NCPIndexModel::model()->deleteAll($criteria);
    }
    /**
     * @param $model
     * @param $tablename
     * @param $fieldnames
     */
    public static function update_ncp_search_item($model, $tablename, $fieldnames) {
        NCPSearch::delete_ncp_search_item($model, $tablename);
        NCPSearch::insert_ncp_search_item($model, $tablename, $fieldnames);
    }
    /**
     * @param $model
     * @param $tablename
     * @param $fieldnames
     */
    public static function insert_ncp_search_item($model, $tablename, $fieldnames) {
        foreach ($fieldnames as $fieldname) {
            $NCP_index_model = new NCPIndexModel("create");
            $NCP_index_model->tablename = $tablename;
            $NCP_index_model->fieldname = $fieldname;
            $NCP_index_model->id = $model->uid;
            $NCP_index_model->save();
            // a very simple way to tokenize the strings!
            $raw = strip_tags($model->{$fieldname});
            $tokens = explode( ' ', $raw);
            foreach ($tokens as $token) {
                $NCP_token_model = new NCPTokenModel("create");
                $NCP_token_model->NCP_index_uid = $NCP_index_model->uid;
                $NCP_token_model->token = $token;
                $NCP_token_model->save();
            }
        }
    }
    /**
     * @param $models
     * @param $tablename
     * @param $fieldnames
     */
    public static function insert_ncp_search_items($models, $tablename, $fieldnames) {
        foreach ($models as $model) {
            NCPSearch::insert_ncp_search_item($model, $tablename, $fieldnames);
        }
    }
}


// main.php:

// initialize ncp_search table once with all tables which has to be indexed in the main function
NCPSearch::insert_ncp_search_items(UserModel::model()->findAll(), "user", ["login", "mail", "name_last", "name_first"]);
NCPSearch::insert_ncp_search_items(DepartmentModel::model()->findAll(), "department", ["title", "description"]);
NCPSearch::insert_ncp_search_items(ObjectModel::model()->findAll(), "object", ["title", "description"]);
  

// model.php:

class Object : Model
{
  function afterSave()  {
    
    
     // insert this code to synchronize the informations on ncp_index
     if ($this->status === ObjectStatus::DELETED)
            NCPSearch::delete_ncp_search_item($this, "object");
        else
            NCPSearch::update_ncp_search_item($this, "object", ["title", "description"]);       
            
    ...
  }
  
  ...
  
}

Conclusion

These are my basic observations on this subject. These are the first steps to a search-engine that can index existing tables so that informations can be found quickly.

Thanks to

Github

https://github.com/Necip8/ncp_search

Homepage https://ncpup.wordpress.com

I hope this information helps you to build your own super search engine!

]]>
0
[wiki] Pjax GridView: refresh page after delete Tue, 24 Jul 2018 14:11:51 +0000 https://www.yiiframework.com/wiki/867/pjax-gridview-refresh-page-after-delete https://www.yiiframework.com/wiki/867/pjax-gridview-refresh-page-after-delete hehbhehb hehbhehb

Normally, after clicking the delete button in gridview, the record will be deleted and the page will refresh, but the page number in query string is lost. This is not always the case we expect.

How to refresh current page with pjax after deleting the record? It seems there is no very simple solution.

  1. Controller file

     public function actionDelete($id)
     {
         $this->findModel($id)->delete();
         if (Yii::$app->request->isAjax) {
             Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
             return ['success' => true];
         }
         return $this->redirect(['index']);
     }
    
  2. index.php (view file)

    <?php
     $this->registerJs("
         $(document).on('ready pjax:success', function() {
             $('.pjax-delete-link').on('click', function(e) {
                 e.preventDefault();
                 var deleteUrl = $(this).attr('delete-url');
                 var pjaxContainer = $(this).attr('pjax-container');
                 var result = confirm('Delete this item, are you sure?');                                
                 if(result) {
                     $.ajax({
                         url: deleteUrl,
                         type: 'post',
                         error: function(xhr, status, error) {
                             alert('There was an error with your request.' + xhr.responseText);
                         }
                     }).done(function(data) {
                         $.pjax.reload('#' + $.trim(pjaxContainer), {timeout: 3000});
                     });
                 }
             });
    
         });
     ");
    ?>
    
    <?php Pjax::begin(['id' => 'my_pjax']); ?>
     <div class="shop-index">
         <?= GridView::widget([
             'dataProvider' => $dataProvider,
             'filterModel' => $searchModel,
             'columns' => [
                 'id',
                 [
                     'class' => 'yii\grid\ActionColumn',
                     'buttons' => [
                         'update' => function ($url, $model) {
                             return Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, [
                                 'class' => 'pjax-update-link',
                                 'title' => Yii::t('yii', 'Update'),
                             ]);
                         },
                         'delete' => function ($url, $model) {
                             return Html::a('<span class="glyphicon glyphicon-trash"></span>', false, [
                                 'class' => 'pjax-delete-link',
                                 'delete-url' => $url,
                                 'pjax-container' => 'my_pjax',
                                 'title' => Yii::t('yii', 'Delete')
                             ]);
                         }
                     ],
                 ],
             ],
         ]); ?>
     </div>
    <?php Pjax::end(); ?>
    
]]>
0
[wiki] Configuring PhpStorm IDE for Yii 2 Fri, 27 Apr 2018 13:08:08 +0000 https://www.yiiframework.com/wiki/865/configuring-phpstorm-ide-for-yii-2 https://www.yiiframework.com/wiki/865/configuring-phpstorm-ide-for-yii-2 CeBe CeBe

There are a few settings and plugins that can enhance the development experience with Yii in PHPStorm or IntelliJ IDEA. This article explains how to get the most out of your IDE.

This arcticle is valid for Yii 2 and above, there is an older article for Yii 1.1.

Plugins

  • Yii2 Support adds many useful tools that improve working with Yii. It makes working with class configuration, DI and views easier.

  • Yii2 Inspections is a very useful plugin that adds Yii specific code inspections to PHPStorm. It helps for example to manage @property annotations for getters and setters as well as translation messages.

  • Php Inspections (EA Extended) is not directly Yii related but has a lot of additional code inspections that help you write better code. There is also a paid version that has even more features, especially security related inspections.

Project Setup

  • Exclude runtime directories from code search. Debug toolbar stores a lot of stuff that clutters search results.

    runtime - right click - Mark Directory As - Excluded

  • Enable composer integration to tell PHPStorm to separate vendor files from project files.

    composer.json - right click - Composer - Init Composer ...

PHPUnit and Codeception

PHPStorm has integrations for PHPUnit as well as Codeception, so you can run your tests directly from the IDE.

Settings for that can be found at Run - Edit Configurations....

To add your Codeception tests, click the + button, select Codeception. Then enter the following details:

  • Name: "Codeception tests" - or whatever you want to name it
  • Test Scope: Defined in the Configuration file
  • Use alternative configuration file: "codeception.yml"
  • In case PHPStorm asks you to do it, configure a PHP Interpreter in PHPStorm settings
  • Configure Codeception binary as vendor/bin/codecept

You can now run your tests directly from PHPStorm.

For PHPUnit the steps are similar but Yii application templates do not provide PHPUnit tests by default so the options depend on your setup.

]]>
0
[wiki] How to login from different tables in Yii2 Tue, 08 Oct 2019 13:54:13 +0000 https://www.yiiframework.com/wiki/864/how-to-login-from-different-tables-in-yii2 https://www.yiiframework.com/wiki/864/how-to-login-from-different-tables-in-yii2 androidelp androidelp

The Problem: Yii2 utilizes by default UserIdentity configured in config/web.php for connection, this object appy one table to authentication ('identityClass' => 'app\painel\models\User'). How to authentication from diferent tables? Solution: Create instances in web.php to uses UserIdentify. eg:

$user = \Yii::$app->user;  
$school = \Yii::$app->school; 
$teacher = \Yii::$app->teacher;

My config/web.php

'user' => [
	'class'=>'yii\web\User',
	'identityClass' => 'app\models\User',
	'enableAutoLogin' => false,
	'authTimeout' => 60*30,
	'loginUrl' => ['dashboard/login'],
	'identityCookie' => [
		'name' => '_panelUser',
	]
],
'school'=>[
	'class'=>'yii\web\User',
	'identityClass' => 'app\models\SchoolUser',
	'enableAutoLogin' => false,
	'authTimeout' => 60*30,
	'loginUrl' => ['dashboard-school/login'],
	'identityCookie' => [
		'name' => '_panelSchool',
	]	
],
'teacher'=> [
	'class'=>'yii\web\User',
	'identityClass' => 'app\models\TeacherUser',
	'enableAutoLogin' => false,
	'authTimeout' => 60*30,
	'loginUrl' => ['dashboard-teacher/login'],
    'identityCookie' => [
		'name' => '_painelTeacher',
	]
],

Note that for each there is a identifyClass and one view login. Now, we need to create the models:

namespace app\models;
use Yii;
// My user
class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface
{
    public static function tableName()
    {
        return '{{%user}}';
    }
    // to continues....

Model scholl:


namespace app\models;
use Yii;
// My School
class SchoolUser' extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface
{
    public static function tableName()
    {
        return '{{%schoolUser}}';
    }
    // to continues
    

Model Teacher:


namespace app\models;
use Yii;
// My School
class TeacherUser'' extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface
{
    public static function tableName()
    {
        return '{{%teacher}}';
    }
    // to continues....

Now In my example I want to have controllers for each type of access, without generating conflicts between them:

In Behavior of the controller i have defined for dashboard-school, teacher and user, the user object representing the authentication status or the ID of the user application component.


	//behaviors of the school
public function behaviors()
{
	return [
        'access' => [
            'class' => AccessControl::className(),
            'user'=>'school', // this user object defined in web.php
            'rules' => [
                [
                    'allow' => true,
                    'roles' => ['@'],
                ],
                [
                    'allow' => true,
                    'actions' => ['login'],                    
 'roles' => ['?'],

                ],
            ],
        ]
    ];
}

To use login:

//school
\Yii::$app->scholl->login($model, $this->rememberMe ? 3600*24*30 : 0);

//teacher
\Yii::$app->teacher->login($model, $this->rememberMe ? 3600*24*30 : 0);

For restrict access in views:

<?php if (!\Yii::$app->scholl->isGuest):?>
<h1>My scholl Name: <?=\Yii::$app->scholl->identity->name?>
<?php endif;?>

<?php if (!\Yii::$app->teacher->isGuest):?>
<h1>Teacher Name: <?=\Yii::$app->teacher->identity->name?>
<?php endif;?>

Always use the specific instance of the user you want to work with.

Criticism, suggestions and improvements, use the comments

Agência Next4 Comunicação Digital Ltda.

]]>
0
[wiki] Use non Gmail/Gsuite on Gcloud projects Sun, 15 Jul 2018 14:00:50 +0000 https://www.yiiframework.com/wiki/863/use-non-gmailgsuite-on-gcloud-projects https://www.yiiframework.com/wiki/863/use-non-gmailgsuite-on-gcloud-projects gogl92 gogl92

Small companies and startups use cheap email services or even Cpanel's mail services which are less secure and compete directly with bigger email providers like Microsoft with Outlook and Google with Gmail. This creates a problem when you try to use their services to send/receive emails from this cheap services. google-cloud-platform.png

Google says this: Google Compute Engine does not allow outbound connections on ports 25, 465, and 587. By default, these outbound SMTP ports are blocked because of the large amount of abuse these ports are susceptible to. In addition, having a trusted third-party provider such as SendGrid, Mailgun, or Mailjet relieves Compute Engine and you from maintaining IP reputation with your receivers.

Some time ago I had a project that requieres this, send emails using a cpanel mail services but we have Google Cloud services so when the client send me the credentials to put it in production the app crashes in a critical time, client will not pay to use Gsuite and I have all the stuff ready for production and configured in Google Cloud, so I found a solution.

  • Make sure you have swiftmail extension installed (it also comes with the basic and advance template)
  1. First get a Gmail or Gsuite account to use it as a bridge between emails. The name doesn't matter it will never be seen by final users. let's say mail_service54@gmail.com or use a Gsuite custom email. It is also important that you have full priviledges to this email as you will need it in the next step.
  2. Enable less secure apps to your email. [https://support.google.com/accounts/answer/6010255?hl=en](Official Docs here)
  3. Now configure the swiftmailer extension to send emails with your account. I use this config for gmail/gsuite accounts:
    'mailer' => [
             'class' => 'yii\swiftmailer\Mailer',
             'transport' => [
                 'class' => 'Swift_SmtpTransport',
                 'host' => 'smtp.gmail.com',
                 'username' => 'yourmail@gmail.com',
                 'password' => 'your password',
                 'port' => '587',
                 'encryption' => 'tls',
             ],
         ],
    
  4. Test that you can send an email so we can get sure the past configuration works well.
  5. After you can send an email, login into your account on the web and go to settings > Accounts and Import > Send mail as
  6. Do the import process of the other service/cpanel email, usually they will send you a link/code to the other email and you will have to confirm it.
  7. Now you can from your gmail account send mails as some other mail.
  8. Do this in your Yii code:
    Yii::$app->mailer->compose()
     ->setFrom('from@cpanel-mail.com')
     ->setTo('to@domain.com')
     ->setSubject('Message subject')
     ->setTextBody('Plain text content')
     ->setHtmlBody('<b>HTML content</b>')
     ->send();
    

    And that's it!

This is the first post of many about using Yii/Open Source in business and enterprise level becuase Open source != not cost.

]]>
0