Magento 2’s uiRegistry Debugging

On the frontend of Magento 2 components are constructed hierarchically. Their names are derived from a concatenation of their parent names. Looking at the checkout_index_index.xml, we can see that the checkout component contains a horrendous amount of config.

To make debugging of this gargantuan hellspawned rat’s nest easier, we can use the uiRegistry object in our browser’s console.

If we would like to get a particular object and we know the full concatenated name of the item, we can simply use something akin to the following;

requirejs('uiRegistry').get("checkout.steps.shipping-step.shippingAddress");

If however, we would like to get a uiComponent using a property name, we can instead use the get method as a query. In the example above, if we only knew the script location of the uiComponent in question, we could instead perform

requirejs('uiRegistry').get("component = Magento_Checkout/js/view/shipping");

We can also pass in a callback method as the second parameter, where the item(s) returned are passed in as parameters to that function.

Getting all registered uiComponents

The get method also allows us to pass in a callback function as the first parameter, instead of a query. This will pass all items sequentially through our callback, allowing us to see exactly what is registered;

requirejs('uiRegistry').get(function(item){
    console.log(item.name, item);
});

Do this on the checkout and prepare to have an exorcist level of data vomited into your poor, unsuspecting console.

Related Magento DevDoc

Stopping Magento 2 Redirecting from the Checkout

Sometimes, we want to add debug information when submitting Magento’s checkout to see what exactly is going on in the backend. Adding any debug information to the payload of Magento’s payment-information call will cause a redirect back to the cart page. This will render any information we’ve output as unobtainable even when using “Preserve Log” in Chrome Dev Tools. To stop this redirect, we can temporarily comment out the following in module-checkout/view/frontend/web/js/view/payment/default.js in the placeOrder method

if (self.redirectAfterPlaceOrder) {
    redirectOnSuccessAction.execute();
}

Webpack – Assets

By default, Webpack only deals with javascript. For other filetypes, we need to add loaders to our config. We define these in the rules section of our config file, providing a regex for the test key for the files to be dealt with (typically file extension).

{
...
 rules: [
            {
                test: /\.css$/
                ...
            }
        ]
...
}

If we want to exclude files for any reason, we can add an exclude key, which is also a regex

 rules: [
            {
                test: /\.css$/,
                exclude: /file-not-to-be-processed.css/
                ...
            }
        ]  

CSS

Let’s add a CSS file to our project in src/css/style.css

body{
    background: red;
}

Now we need to include this file in our src/index.js file.

import './css/style.css';

In our webpack.config.js file, we can now add a definition for different filetypes which match on a filename regex:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    }
};

And then install the css-loader and style-loader

npm install style-loader css-loader -D

When processing files, the use array is processed from top to bottom.
The CSS Loader loads CSS files, and deals with any assets within the file. Then the result is passed to the Style Loader which injects the CSS onto the page using a style tag.

In the example above, the the loaders are passed in order as strings in an array. If we need to configure the loaders with options, for example to enable source maps, we can use an object instead of a string, and provide an options key:

{
...
 rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true
                        }
                    }
                ]
            }
        ]
...
}

We will now be able to see files and line numbers when inspecting elements in the dev tools.

By default, the style-loader will create a separate style tag for each file being imported. To coalesce them into a single style tag, we can provide a singleton option key:

...
{
    loader: 'style-loader',
    options: {
        sourceMap: true,
        singleton: true
    }
}
...

Outputting CSS To A File

Instead of including all of the CSS in style tags, we can instead output them to a file. We’ll use the MiniCssExtractPlugin to achieve this.

npm install -D mini-css-extract-plugin

And then include it at the top of our config file

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

We can then need to use this as a plugin which will output our file

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

{
...
 plugins: [
        new MiniCssExtractPlugin({
            filename: "style.css"
        })
    ],
...
}

This also requires a loader to be added to the process chain. This plugin, will replace the style-loader which we used earlier.

{
...
    rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader
                    },
                    { 
                        loader: "css-loader"
                    }  
                ]
            }
        ]
...
}

We can now link to dist/styles.css from within our HTML file

<link rel="stylesheet" href="dist/style.css">

Dynamically Including CSS

In the example above, we have to hardcode our output CSS file within our HTML file. This would be cumbersome if we were using a hashed filename which would re-generate dynamically. We can achieve dynamic importing using the HTML Webpack Plugin

npm install -D html-webpack-plugin

Now we can remove any hardcoded script or CSS tags from our index.html, and move it to the ./src folder. Next, we can add the plugin definition

{
...
 plugins: [
        ...
        new MiniCssExtractPlugin({
            filename: "style.css",
        }),
        new HTMLWebpackPlugin({
            filename: '../index.html',
            template: './src/index.html'
        })
    ],
...
}

File Hashing

By default, the index.html file will be built in our dist folder, we’ll place this one level above in this example. We are now able to introduce file hashing to our application, as the CSS files are being injected dynamically. This means that if any of our file contents change, such as adding a new CSS rule, the filename will change and the user will always request the newest version.

There are two ways to do this, we can either use the html-webpack-plugin to append a querystring hash to the end of our static filenames, or we can use the mini-css-extract-plugin to create a hashed filename for our CSS. We can also use a combination of the two.

{
...
 plugins: [
        new CleanWebpackPlugin(['dist']),
        new MiniCssExtractPlugin({
            filename: '[contenthash].css'
        }),
        new HTMLWebpackPlugin({
            filename: '../index.html',
            template: './src/index.html',
            hash: true
        })
    ],
...
}

The above example will do both, using [contenthash] as the filename and hash: true in the HTMLWeboackPlugin.

Taking this further, we can now use contenthash when including our javascript file in Webpack’s config, so instead of using just main.js, we can use

{
...
    output: {
        filename: '[name].[contenthash].js'
        ...
    }
...
}

contenthash should be used when we need to know if a particular piece of content changes. Using chunkhash or hash will regenerate when content outside of our context changes. E.g. If we change CSS, then the JS’s file hash will also change.

It should be noted, that using HTMLWebpackPlugin’s hash option uses webpack’s hash for the build, which will change when the entire build changes. Therefore, when we’re using contenthash to generate filenames, we should turn off this option to avoid unnecessary downloads.

{
...
   plugins: [
        new HTMLWebpackPlugin({
            filename: '../index.html',
            hash: false
        })
   ]
...
}

Minifying CSS

All of the CSS we’ve been generating so far has been un-minified. To minify it for production, we can use the OptimiseCssAssetPlugin.

npm install -D optimize-css-assets-webpack-plugin

We can then use it by adding its plugin to our definition

{
   ...
    plugins: [
        new CleanWebpackPlugin(['dist'], { watch: false }),
        new MiniCssExtractPlugin({
            filename: "[contenthash].css",
        }),
        new HTMLWebpackPlugin({
            filename: '../index.html',
            hash: false,
            cache: true
        }),
        new OptimizeCssAssetsPlugin({})
    ],
   ...
}

Adding a HTML Template

By default, our index.html file will be generated automatically for us. Using the HTMLWebpackPlugin, we can specify a template file for the plugin to use which may contain our initial HTML structure. Create a file in src/index.html and then add a template key to our HTMLWebpackPlugin config

{
...
    plugins: {
    ...
        new HTMLWebpackPlugin({
            filename: '../index.html',
            template: './src/index.html',
            hash: false,
            cache: true
        }),
   ...
   }
...
}
<!doctype html>
<html>
 <head>
   <title>Getting Started</title>
 </head>
 <body>
    <header>HEADER</header>
 </body>
</html>

The CSS will now be added to the head of the template, and JS files will be added at the end of the HTML tag.

Sass

Sass compiled much the same way as standard CSS, but it uses two extra Webpack plugins to achieve it, Sass Loader, and Node Sass

npm install node-sass sass-loader -D

And then create a rule in our Webpack config

{
   ...
 rules: [
       ...
            {
                test: /\.scss$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader
                    },
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: "sass-loader"
                    }
                ]
            }
      ...
     ]
}

We can then create a Sass file in our src directory, and import it in the same way as our previous CSS file. This will then be combined into our dist folder’s output CSS.

Images

Images in our CSS can be handled using the url-loader Webpack plugin.

npm install url-loader url-loader -D

And then add a loader rule to our config

test: /\.(png|svg|jpg|gif)$/,
use: {
    loader: 'url-loader',
    options: {
        name: '[path][name].[ext]',
        context: 'src',
        limit: 9000
    }
}

This will copy images used in our CSS or SASS and place them in the dist folder. The path folder by default will include our src directory, so specifying the context: src key, will tell url-loader not to include that path in our output directory. We can also specify a limit (in bytes), which will include any images under that size inline as a data-url in our application; this is used to limit server requests for small images.

This will work for images in CSS, however images imported using javascript will not have the correct path. To fix this, we can add a publicPath declaration to our Webpack output

module.exports = {
...
    output: {
        filename: '[name].[contenthash].js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/dist/'
    },
...
}

We can now perform imports in JS files with the correct path names, such as the following

import image from './img/test.jpg';

const img = document.createElement('img');
img.src = image;
document.querySelector('body').appendChild(img);

ES 6

To transpile our ES6 Code such as arrow functions so that they work in browsers such as Internet Explorer, we can install Babel Loader.

npm install babel-loader @babel/core @babel/preset-env -D 

and then add a rule in our Webpack config

rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    }
  ]

Overcoming Magento’s Double Grand Total Issue

Magento will sometimes calculate the total of the cart as double what it should actually be. This occurs typically when multiple collectTotals() calls are made to the shipping address. To overcome this issue, we can clear the cached items of the quote object and recalculate from there.


$quote = Mage::getSingleton(&#039;checkout/session&#039;)-&gt;getQuote();
$quote-&gt;setTotalsCollectedFlag(false);
$quote-&gt;getShippingAddress()-&gt;unsetData(&#039;cached_items_all&#039;);
$quote-&gt;getShippingAddress()-&gt;unsetData(&#039;cached_items_nominal&#039;);

$quote-&gt;getShippingAddress()-&gt;unsetData(&#039;cached_items_nonnominal&#039;);
$quote-&gt;collectTotals()-&gt;save();


WordPress is an arse and can’t do html entities properly

Converting a UTF-16 CSV to UTF-8 in PHP

The best solution I’ve found to this is to open the file, and re-write it with the new encoding before opening it for CSV reading;

function get_encoding($filename){
    $encoding='';
    $handle = fopen($filename, 'r');
    $bom = fread($handle, 2);
    //	fclose($handle);
    rewind($handle);

    if($bom === chr(0xff).chr(0xfe)  || $bom === chr(0xfe).chr(0xff)){
        // UTF16 Byte Order Mark present
        $encoding = 'UTF-16';
    } else {
        $file_sample = fread($handle, 1000) + 'e'; //read first 1000 bytes
        // + e is a workaround for mb_string bug
        rewind($handle);

        $encoding = mb_detect_encoding($file_sample , 'UTF-8, UTF-7, ASCII, EUC-JP,SJIS, eucJP-win, SJIS-win, JIS, ISO-2022-JP');
    }
    if ($encoding){
        stream_filter_append($handle, 'convert.iconv.'.$encoding.'/UTF-8');
    }
    return $encoding;
}

/**
* Decode UTF-16 encoded strings.
* 
* Can handle both BOM'ed data and un-BOM'ed data. 
* Assumes Big-Endian byte order if no BOM is available.
* 
* @param   string  $str  UTF-16 encoded data to decode.
* @return  string  UTF-8 / ISO encoded data.
* @access  public
* @version 0.1 / 2005-01-19
* @author  Rasmus Andersson {@link http://rasmusandersson.se/}
* @package Groupies
*/
function utf16_decode($str, &$be=null) {
    if (strlen($str) < 2) {
        return $str;
    }
    $c0 = ord($str{0});
    $c1 = ord($str{1});
    $start = 0;
    if ($c0 == 0xFE && $c1 == 0xFF) {
        $be = true;
        $start = 2;
    } else if ($c0 == 0xFF && $c1 == 0xFE) {
        $start = 2;
        $be = false;
    }
    if ($be === null) {
        $be = true;
    }
    $len = strlen($str);
    $newstr = '';
    for ($i = $start; $i < $len; $i += 2) {
        if ($be) {
            $val = ord($str{$i})   << 4;
            $val += ord($str{$i+1});
        } else {
            $val = ord($str{$i+1}) << 4;
            $val += ord($str{$i});
        }
        $newstr .= ($val == 0x228) ? "\n" : chr($val);
    }
    return $newstr;
}


$newFilename = 'path/to/uploaded/file.scv';
$encoding = get_encoding($newFilename);

/* This is the fix for UTF-16 files which come in from iPad */
if($encoding == 'UTF-16'){
    $utf8Contents = utf16_decode(file_get_contents($newFilename));
    $resource = fopen($newFilename, 'w');
    /* Write the file over the uploaded one */
    fwrite($resource, pack("CCC",0xef,0xbb,0xbf));
    fwrite($resource, $utf8Contents);
}


if (($handle = fopen($newFilename, "r")) !== false) {
    while (($data = fgetcsv($handle)) !== false) {
        ...
    }
}

Curling a URL in PHP

Assume the form object contains an array in the form of params

 $fieldString = http_build_query($form->getParams());

//open connection
$ch = curl_init();

//set the url, number of POST vars, POST data
curl_setopt($ch,CURLOPT_URL, $this->_dotMailerUri);
curl_setopt($ch,CURLOPT_POST, count($form->getParams()));
curl_setopt($ch,CURLOPT_POSTFIELDS, $fieldString);
// Set this so the result isn't added to stdout and gets returned instead
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

//execute post
$result = curl_exec($ch);

//close connection
curl_close($ch);

Calculating UK Bank Holidays in PHP

This is from the following page at PHP.net: http://php.net/manual/en/ref.calendar.php, copied as a reference here to aid my rapidly depleting hippocampus.

<?php

/*
*    Function to calculate which days are British bank holidays (England & Wales) for a given year.
*
*    Created by David Scourfield, 07 August 2006, and released into the public domain.
*    Anybody may use and/or modify this code.
*
*    USAGE:
*
*    array calculateBankHolidays(int $yr)
*
*    ARGUMENTS
*
*    $yr = 4 digit numeric representation of the year (eg 1997).
*
*    RETURN VALUE
*
*    Returns an array of strings where each string is a date of a bank holiday in the format "yyyy-mm-dd".
*
*    See example below
*
*/

function calculateBankHolidays($yr) {

    $bankHols = [];

    // New year's:
    switch ( date("w", strtotime("$yr-01-01 12:00:00")) ) {
        case 6:
            $bankHols[] = "$yr-01-03";
            break;
        case 0:
            $bankHols[] = "$yr-01-02";
            break;
        default:
            $bankHols[] = "$yr-01-01";
    }

    // Good friday:
    $bankHols[] = date("Y-m-d", strtotime( "+".(easter_days($yr) - 2)." days", strtotime("$yr-03-21 12:00:00") ));

    // Easter Monday:
    $bankHols[] = date("Y-m-d", strtotime( "+".(easter_days($yr) + 1)." days", strtotime("$yr-03-21 12:00:00") ));

    // May Day:
    if ($yr == 1995) {
        $bankHols[] = "1995-05-08"; // VE day 50th anniversary year exception
    } else {
        switch (date("w", strtotime("$yr-05-01 12:00:00"))) {
            case 0:
                $bankHols[] = "$yr-05-02";
                break;
            case 1:
                $bankHols[] = "$yr-05-01";
                break;
            case 2:
                $bankHols[] = "$yr-05-07";
                break;
            case 3:
                $bankHols[] = "$yr-05-06";
                break;
            case 4:
                $bankHols[] = "$yr-05-05";
                break;
            case 5:
                $bankHols[] = "$yr-05-04";
                break;
            case 6:
                $bankHols[] = "$yr-05-03";
                break;
        }
    }

    // Whitsun:
    if ($yr == 2002) { // exception year
        $bankHols[] = "2002-06-03";
        $bankHols[] = "2002-06-04";
    } else {
        switch (date("w", strtotime("$yr-05-31 12:00:00"))) {
            case 0:
                $bankHols[] = "$yr-05-25";
                break;
            case 1:
                $bankHols[] = "$yr-05-31";
                break;
            case 2:
                $bankHols[] = "$yr-05-30";
                break;
            case 3:
                $bankHols[] = "$yr-05-29";
                break;
            case 4:
                $bankHols[] = "$yr-05-28";
                break;
            case 5:
                $bankHols[] = "$yr-05-27";
                break;
            case 6:
                $bankHols[] = "$yr-05-26";
                break;
        }
    }

    // Summer Bank Holiday:
    switch (date("w", strtotime("$yr-08-31 12:00:00"))) {
        case 0:
            $bankHols[] = "$yr-08-25";
            break;
        case 1:
            $bankHols[] = "$yr-08-31";
            break;
        case 2:
            $bankHols[] = "$yr-08-30";
            break;
        case 3:
            $bankHols[] = "$yr-08-29";
            break;
        case 4:
            $bankHols[] = "$yr-08-28";
            break;
        case 5:
            $bankHols[] = "$yr-08-27";
            break;
        case 6:
            $bankHols[] = "$yr-08-26";
            break;
    }

    // Christmas:
    switch ( date("w", strtotime("$yr-12-25 12:00:00")) ) {
        case 5:
            $bankHols[] = "$yr-12-25";
            $bankHols[] = "$yr-12-28";
            break;
        case 6:
            $bankHols[] = "$yr-12-27";
            $bankHols[] = "$yr-12-28";
            break;
        case 0:
            $bankHols[] = "$yr-12-26";
            $bankHols[] = "$yr-12-27";
            break;
        default:
            $bankHols[] = "$yr-12-25";
            $bankHols[] = "$yr-12-26";
    }

    return $bankHols;

}

/*
*    EXAMPLE:
*
*/

header("Content-type: text/plain"); 

$bankHolsThisYear = calculateBankHolidays(2007);

print_r($bankHolsThisYear);

?>

Will output this result:

Array
(
    [0] => 2007-01-01
    [1] => 2007-04-06
    [2] => 2007-04-09
    [3] => 2007-05-07
    [4] => 2007-05-28
    [5] => 2007-08-27
    [6] => 2007-12-25
    [7] => 2007-12-26
)

Thanks to David Scourfield!

Magento Cart Items – Getting Child Product Items

This is how it’s done in the Mage_Checkout_Block_Cart_Item_Renderer_Configurable class. It will return the simple product if available otherwise it will return the product of the cart item.

<?php
public function getChildProduct()
{
    if ($option = $this->getItem()->getOptionByCode('simple_product')) {
        return $option->getProduct();
    }
    return $this->getProduct();
}