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();
}

collectTotals in Magento 2

If we were to have a custom quote object which we were adding items to, we can re-calculate the totals in the cart using the following method.

$shippingAddress = $preorderQuote->getShippingAddress();
$shippingAddress->unsetData('cached_items_all');            
$quote->setTotalsCollectedFlag(false)->collectTotals();
$this->quoteRepository->save($quote);

We unset the cached item on the quote address object to force Magento to re-load the items for the address. This is necessary as Magento will sometimes not update this automatically, leading to an incorrect zero value quote and quote items.

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']
        }
      }
    }
  ]

Webpack – Setting Up

Initialising

Webpack is installed using NPM. Initialise a new project, which will create a package.json file. Optionally use the flag -y to accept all defaults when initialising the project.

npm init

Now we can install Webpack and Webpack CLI via NPM using the following. The flag -D is for –save-dev, which adds both packages to our devDependencies.

npm install webpack webpack-cli -D

By default, Webpack 4 will use a default configuration file, so one isn’t necessary to get going. The default configuration values can be found here.

By default, Webpack will look in the folder “src” for the file “index.js”, and output to the folder “dist”, with the javascript file “main.js”. A handy Webpack config generator can be found here.

We can now create a file, index.html in the project root and link to the file “dist/main.js”.

<!doctype html>
<html>
 <head>
   <title>Getting Started</title>
 </head>
 <body>
    <script src="dist/main.js"></script>
 </body>
</html>

For our example, we’ll install Lodash, which is a utility library

npm install lodash --save

Now let’s create the entry point file in “src/index.js”, we’ll import lodash.

import _ from 'lodash';

console.log(_.join(["This", "is", "using", "Lodash"], " "));

This will import the default export from lodash in our local “_” variable. We could use any name of our choosing for this to use locally, however underscore is a convention when using this package.

To build our javascript into the dist directory, we can run

npx webpack

NPX is a tool for executing node packages. It is typically only used for single use commands, such as react-create-app. We’ll supersede its use later for our builds, but it’s useful to use it for this example. Executables which are available for use with NPX are symlinked to the node_modules/.bin directory. Running “NPX with any of the files in here can perform useful functions, e.g. mkdirp which will make directories recursively:

mkdirp /directory1/directory2

Importing Individual Methods

If we didn’t want to use the entire lodash library, we could import individual methods from it using the following:

import {join as _join} from 'lodash'
console.log(_join(["This", "is", "using", "Lodash"], " "));

However, this will still import the entire Lodash library into our project. Instead, we can use the lodash-es module which defines each method as an ES module:

import {join as _join} from 'lodash-es'

console.log(_join(["This", "is", "using", "Lodash"], " "));

This example cuts our dist/main.js size down from ~70KB to ~1KB.

Building with a separate configuration file

If we need to test something using a separate configuration file, we can use NPX to achieve this. We can create a file called webpack.test.config.js in our project’s root with the following:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'test.js'
    }
};

Note: config files use require to include, which was introduced with Node.js some time ago, and does not use import

And then execute it with NPX:

npx webpack --config webpack.test.config.js

This will bundle our javascript in dist/test.js instead of dist/main.js.

Note that config files are standalone. So if we had a webpack.config.js in our project’s root, any config file passed to Webpack via NPX will not be merged together.

Adding a Script Shortcut

We can assign shortcut’s via NPM’s script facility. At a very basic level, we can setup a build command so that we do not have to rely on NPX. We can do that in our package.json file:

{
...
  "scripts": {
    "build": "webpack"
  },
...
}

We can then add a watch command which will automatically re-build our javascript based on changes to the project:

{
...
  "scripts": {
    "build": "webpack",
    "watch": "webpack  --watch"
  },
...
}

We can also specify other parameters, such as “mode” in our build scripts:

{
...
  "scripts": {
    "build": "webpack --mode production",
    "watch": "webpack --mode development  --watch"
  },
...
}

Note: When watching for changes, the process will have to be broken and re-started when the config file is changed

Setting publicPath

publicPath is used when resolving things like images included in CSS files. It’s prepended to these files. For this example, we’ll set it to the dist folder.

{
...
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: 'dist/'
    },
...
}

Source Maps

To allow easier debugging of code, we can set the devtool parameter of our config to one of the sourceMap options. As Webpack itself only deals with Javascript, this will only affect JS SourceMaps, CSS and other loaders will need to be configured in their own right.

By default, setting the config’s mode to ‘development’ will enable SourceMaps, and ‘production’ will disable them, however there are numerous other options which can be useful for both production and development.

Development SourceMaps

These methods generate sourceMaps inline, in the bundled JS files, adding bloat and should not be used in production.

eval

Fast. Files are separated using the original directory structure, but the code generated by Webpack to load modules is present.

cheap-eval-source-map

Pretty Fast. Webpack loading code is removed.

cheap-module-eval-source-map

Medium Speed (but with fast rebuilds). Uses sourcemaps output by defined loaders.

eval-source-map

Slow. Adds column level mappings so inline breakpoints can be set.

Production SourceMaps

none

No SourceMaps are generated. This is the default in production, but leaves code difficult to debug when receiving user reports.

source-map

Source Maps are generated in a separate file. A comment tells dev tools (such as Chrome Dev Tools) where to find them, which are loaded when the dev tools themselves are opened.

hidden-source-map

Source Maps are generated in a separate file, but the comment to tell the browser where to find it is omitted. These can then be Loaded Manually

nosources-source-map

The same as source-map, but only gives the file name and line number. The code itself is not visible to the browser.

Cleaning The Output Folder

When changing Webpack settings, it’s often useful to get rid of old files in the output folder which may be left handing around. To do this, we can use a plugin called Clean Webpack Plugin

Install the plugin

npm install -D clean-webpack-plugin

And then add this to the plugins section of our config file:

module.exports = {
...
    plugins: [
        new CleanWebpackPlugin(['dist'])
    ]
...
}

The option passed into the first parameter of the plugin is an array of paths to be cleaned. This is required, as it does not use Webpack’s output configuration. Supplying a name like above will completely remove that folder, however we can also provide glob strings, such as dist/., which would remove the files within the folder.

This will, by default leave generated files when we’re watching files, so changes will continually generate new files in our dist folder. To re-generate whilst watching, we can use the following option

module.exports = {
...
    plugins: [
        new CleanWebpackPlugin(['dist'], {watch: true})
    ]
...
}

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

Modifying Phoenix_Worldpay For Testing in Magento

Worldpay offers an easy way to switch from live to test mode, however if your server is unreachable by them then after completing the test transaction you won’t be redirected back to your Magento store.

We can make the following temporary adaptions to the module which will allow us to manually pass the required data via a GET string:

Phoenix_Worldpay_ProcessingController

   protected function _checkReturnedPost()
    {
//        // check request type
//        if (!$this->getRequest()->isPost()) {
//            Mage::throwException('Wrong request type.');
//        }
//
//        // validate request ip coming from WorldPay/RBS subnet
//        $helper = Mage::helper('core/http');
//        if (method_exists($helper, 'getRemoteAddr')) {
//            $remoteAddr = $helper->getRemoteAddr();
//        } else {
//            $request = $this->getRequest()->getServer();
//            $remoteAddr = $request['REMOTE_ADDR'];
//        }
//        if (!preg_match('/\.worldpay\.com$/', gethostbyaddr($remoteAddr))) {
//            Mage::throwException('Domain can\'t be validated as WorldPay-Domain.');
//        }

        // get request variables
        $request = $this->getRequest()->getParams();
        if (empty($request)) {
            Mage::throwException('Request doesn\'t contain POST elements.');
        }

        // check order id
        if (empty($request['MC_orderid']) || strlen($request['MC_orderid']) > 50) {
            Mage::throwException('Missing or invalid order ID');
        }

        // load order for further validation
        $this->_order = Mage::getModel('sales/order')->loadByIncrementId($request['MC_orderid']);
        if (!$this->_order->getId()) {
            Mage::throwException('Order not found');
        }

        $this->_paymentInst = $this->_order->getPayment()->getMethodInstance();

        // check transaction password
        if ($this->_paymentInst->getConfigData('transaction_password') != $request['callbackPW']) {
            Mage::throwException('Transaction password wrong');
        }

        return $request;
    }

– We remove the remote address checks at the start of the method
– We change the getPost to a getParams, allowing us to provide GET parameters

Phoenix_Worldpay_Model_Cc

public function capture(Varien_Object $payment, $amount)
	{
        $payment->getOrder()->addStatusToHistory($payment->getOrder()->getStatus(), $this->_getHelper()->__('Worldpay transaction has been captured.'));
        return true;
        if (!$this->canCapture()) {
            return $this;
        }

        if (Mage::app()->getRequest()->getParam('transId')) {
            // Capture is called from response action
            $payment->setStatus(self::STATUS_APPROVED);
            return $this;
        }
        $transactionId = $payment->getLastTransId();
        $params = $this->_prepareAdminRequestParams();
        $params['transId']  = $transactionId;
        $params['authMode'] = '0';
        $params['op']       = 'postAuth-full';

        $responseBody = $this->processAdminRequest($params);
        $response = explode(',', $responseBody);

        if (count($response) <= 0 || $response[0] != 'A' || $response[1] != $transactionId) {
            $message = $this->_getHelper()->__('Error during capture online. Server response: %s', $responseBody);
            $this->_debug($message);
            Mage::throwException($message);
        } else {
            $payment->getOrder()->addStatusToHistory($payment->getOrder()->getStatus(), $this->_getHelper()->__('Worldpay transaction has been captured.'));
        }
    }

– If capturing is required, then this will need further modification, however for our needs we only needed to return true from this method.

It should now be possible to provide a GET string with all of the required parameters, E.g.

http://mydomain.local/worldpay/processing/response?MC_orderid=100012592&transStatus=Y&authAmount=158.00&authCurrency=GBP&authMode=A

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) {
        ...
    }
}

Alphabetic Option Value Ordering in Magento’s Layered Navigation

Magento will by default order layered navigation options by position. To use alphabetic values first and foremost, do the following:

Override the Eav_Entity_Attribute_Source_Table model

<global>
    <models>
        <eav>
            <rewrite>
        	    <entity_attribute_source_table>Namespace_Module_Model_Eav_Entity_Attribute_Source_Table</entity_attribute_source_table>
        	</rewrite>
        </eav>
    </models>
</global>

PHP

Override the getAllOptions method as follows:

<?php
class Namespace_Module_Model_Eav_Entity_Attribute_Source_Table extends Mage_Eav_Model_Entity_Attribute_Source_Table
{
    public function getAllOptions($withEmpty = true, $defaultValues = false)
    {
        $storeId = $this->getAttribute()->getStoreId();
        if (!is_array($this->_options)) {
            $this->_options = array();
        }
        if (!is_array($this->_optionsDefault)) {
            $this->_optionsDefault = array();
        }
        if (!isset($this->_options[$storeId])) {
            $collection = Mage::getResourceModel('eav/entity_attribute_option_collection')
                ->setAttributeFilter($this->getAttribute()->getId())
                ->setStoreFilter($this->getAttribute()->getStoreId());

            $collection->getSelect()->order(['main_table.sort_order asc', 'value asc']);
            $collection->load();

            $this->_options[$storeId]        = $collection->toOptionArray();
            $this->_optionsDefault[$storeId] = $collection->toOptionArray('default_value');
        }



        $options = ($defaultValues ? $this->_optionsDefault[$storeId] : $this->_options[$storeId]);
        if ($withEmpty) {
            array_unshift($options, array('label' => '', 'value' => ''));
        }

        return $options;
    }
}

This uses sort order first, then uses value to order the attribute options.