Windows + Docker + Magento 2 + Grunt = Wasting my life

I have this very annoying habit of denying new technology. Not because I’m not a tech-savy person, not because I don’t like new stuff … it is just me. I just don’t see the benefits. Until I have to start working with it and then Hallelujah “Why didn’t I start to work with this ages ago?”. This was the issue with me and SCSS (any form of preprocessing languages), with Sublime, with Foundation (Grids and frameworks in general).

And because I acknowledge my problem I decided to stay ahead of it this time and I got me some pretty good understanding of the Docker. In fact I thought I was pretty good at it as when I spoke with some of my colleagues that I knew they were using it for quite some time, they didn’t suspect I was a newbie. So I started using it everywhere!

I’ve been also working with Magento 2 recently. So in my mind everything was clicking “I will spin off a new container, I will mount my Magento 2 installation to and I will have a Mary X-mass”. Boy, was I wrong!

Magento 2 is using symbolic links (symlinks) like a lot! A lot a lot! And Docker for Windows does not like it. It could be because I’m using 64 bit Windows 10 pro, it could be because I’m using a SSD or Hyper-V or I don’t know why .. but the fact is when I run “grunt less:<theme>” the container complains that the files inside pub/static/frontend/<Vendor>/<theme>/<locale> were ENOTEMPTY and it could not continue the process. My God … Didn’t I spend the entire day yesterday of trying to bypass/fix it.

Until I found out this gem of a comment. What I did is the following:

  • Deleted everything inside my Windows host pub/static/ folder content.
  • Attached to my container and created a new folder away from the mounted one. I mounted my code folder into /code volume so I created a new folder in /var/www/pub/static/. I don’t use apache, nor I use /var/www/ in any other way. So it is pretty safe to be there.
  • Then created a symlink for pub/static/ to poitn to /var/www/pub/static/

As far as the container is concerned the “pub/static” folder is a local for the container and not mounted one. At this point when I run grunt less:<theme> it all works as expected.

Can I have my beer now?

A new plugin developed

I’m very pleased to present the newest plugin developed for WordPress by HTML Pet. Well … probably it is very optimistic to announce the small “gist” of a code but still 🙂

WooCommerce VAT to API” is a simple (very) plugin that adds only two additional functionality and they are on top of another two plugins – WooCommerce and Booster for WooCommerce. First – add a new key into order’s billing address when requested by WooCommerce API. This is very handy for retailers who want to send their Woo-data to an ERP system. With all the EU laws VAT is an essential part of a B2B order.

The second functionality is the ability to auto fill customer’s pre-filled VAT number (when VAT module is enabled by Booster for WooCommerce). By default when you try to create a manual order and you select an existing customer their billing and shipping data is pre-populated. But for some reason the VAT number is not. So the plugin makes sure the code is also extracted and put in the right place.

That is it. Few lines of code but could save you a lot of time.

How to deal with a lot of missing images in Wordpress

Recently I had to start working on a new website. Usually I download the original one, setup it on my local machine and start developing. This site however had almost half a million of images. Of course I didn’t download them all because that would have taken forever and will eat up a lot of my local space (which is not that much to start with). But I said to myself “Not a biggie. Few missing images will not do much harm to my work.”. But it did …

See … because of the missing images and the default .htaccess rules WordPress is constantly trying to show its 404 Not Found page. And that page requires a bunch of system resources to fire up. Multiply these resources by the number of missing images and you are getting a high spike in CPU usage. So high that my Mac becomes inoperable. It becomes even worse if the 404 page also has missing images. Inception!

*insert image with Leo and his smushy face

I had this problem before. What I did is go to header.php of the theme and add a bit of code.


if(is_404()) {
die('Not found');

// Theme code to follow
// ...


That will stop the page for fully loading and in most of the cases fix the high CPU issue. But that is just in some cases. Before opening header.php WordPress is doing a lot more in the background and still requires a significant amout of resources to get to the theme processing.

Then hot-linking came to help! I thought to myself … OK, I know the address of the missing images why don’t I just get them from the current site without downloading them. Brilliant! A few lines in the .htaccess

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /

# Hot linking starts
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ((.*)\.jpg)$$1 [L,R=301]
# Hot linking ends

RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress

This is the original .htaccess that comes with WordPress and I added the code between the comments. What this code does is:
– Looks for a file with that path (if exists show the local file)
– Looks for a directory with that path (if exists show the local directory)
– (At this point the requested URL is missing). Redirects all requests for images (.jpg) to a local where the image exists ($1).

If hot linking is not an option for you (Because some servers forbid it) you can always redirect all the missing images to local image. Instead of:

RewriteRule ((.*)\.jpg)$$1 [L,R=301]

you can use

RewriteRule ((.*)\.jpg)$ http://local.example/test.jpg [L,R=301]

The end result won’t be 1:1 with the original website but at least your local apache will not break and you will know that the image is missing.

P.S. I think I am writing this articles more for me and nobody reads them. If you do and you know a better or more elegant solution, please leave a comment.

I have new Wordpress .... now what?

WordPress is notorious for two things:
1. Being very easy to use with tons of plugins and themes
2. Every now and then a new exploit is found that affects millions of users

The latter is caused (primarily) because people assume the guys that created WordPress are taking under account every possible scenario. However it does not work that way and the installation needs some taking care of after initial install to make sure you cover all bases. WordPress security is kinda like bicycle locks – it is not gonna stop a good thief, but will be enough to force him move his effort to the next bike instead of yours.

I am not a security expert but I know few things how to harden your WordPress installation.

0. Do not use wp_ as prefix for tables.

That is number 0 because it has to be done during the installation process.  Wordpress is an Open Source platform and everybody knows its database structure. It is very easy for someone to target the exact table name like wp_posts or wp_options to obtain valuable information. That is why is best if you change your table prefix from wp_ to something else like “starwars_” or “drupal_” 🙂

1. Remove admin user

WordPress comes by default with only one user which is “admin” and it has full administrative privileges. Which means that by default everyone will try to attack this user. Create a new Administrative account and delete the “admin” one as soon as you login for the first time.

2. Change “wp-admin”

Pretty much same as the point before. /wp-admin or /wp-login.php has long been the entry point to your WordPress admin area. Everybody knows that. A potential hacker needs three things to penetrate: a) A place to start b) Username c) Password. Usually with WordPress installations a hacker knows a) and b) right from the start and he only has to focus on gaining your password. So why give him all the three options? We already removed the “admin” account with point 2, now we need to remove the entry point. You can use plugin like WPS Hide Login to change your /wp-admin to something only you wound know. Be creative. Login from /i-know-bretney-spears or /taccos-are_great.  Just don’t use “wp-admin” anymore.

3. Limit login attempts

Unless your potential hacker knows your password he has to make several (or hundreds)  attempts before figure out your password. If you set a limit to how many attempts a person can do before his IP address is banned, then you have got yourself yet another tool against hackers. Use plugin like: WP Limit Login Attempt.

4. Install security plugin

Another thing you can do to harden your WordPress installation is to install a broad scale security plugin like Wordfence Security. WS protects your site in several ways: It will scan your files and compare them agains the official ones to make sure they are not altered, it will share information between all sites that have it installed and if some of them is under attack, the plugin will block that IP for everyone.

5. Hide WordPress at all

If the hacker does not know you use WordPress in a first place that will be great. That way they will not have a clue where to start from at all (well .. some hackers are pretty well educated so they will find out eventually). There is create premium plugin called “Hide My WP – No one can know you use WordPress!” that will hide all possible clues that you might be using a WordPress. Worth the investment.

6. Install and force SSL

You never know who is “listening” and even with all the above options installed and configured, someone could “sniff” your network and get your plain password … just like that. If you buy and setup an SSL certificate and then force WordPress (WordPress Force HTTPS) to always use it (even in the admin area), that way all data between your browser and the server will be encrypted. You can buy RapidSSL or Comodo Essentials for as low as 15 EUR. Small price to pay if you are paranoid about your WordPress security.

7. Add/Change security keys

WordPress uses security keys to ensure your cookies/sessions are secure and no one can steal them and pose as you. If you are using a fairly new WordPress installation you already have them but it won’t hurt if you change them from time to time. You can use WordPress official generator and replace them in your wp-config.php file.

8. Install Akismet

Spam is the most annoying thing in the world when it comes to managing a website. Nobody cares about enlargement pills or the Nigerian prince. Usually WordPress doesn’t care who posts on your blog but that leaves a door wide open for spam bots. Fortunately there is a .. hold your breath … plugin for that. Akismet provides probably the best industry level protection against spam. Although it has a free option I’d recommend to pay just a tiny little bit to make this great company keeps working.

9. Install captcha plugin

Although we covered the entry point area in the points so that your attacker does not know where there are, it will never hurt if you just add another level of protection to it. Just add a captcha to the standard username and password. Use a plugin like: Captcha by BestWebSoft and you will make your WordPress login area as strong as concrete.

10. Disable file editing from WP

Sometimes the problem comes from within. Imagine you have everything covered, nobody can login to your WordPress installation – only you. Then you leave your computer for a brief moment and at that time somebody puts some nasty code within your files … because WordPress allows you to edit your source files from the editor. That’s not wise. You can disable it! You should disable it!

11. Backup

Security is all great when done right but as I stated in the beginning of this article all these tricks are good to a point. If you are a target of someone skilful master hacker the chance is that he will win. After he finishes with your website, no matter what he did, you probably don’t want it. So it is worth having a backup to restore your data/files to a safe point. UpdraftPlus Backup and Restoration is a very popular and good working plugin that will give you all kinds of options to backup your files including using a Cloud storage (Dropbox, Google Drive, Microsoft Azure).

12. Cache

Gaining access to your blog is not always the target of your attackers. They just might want your site not available. They can launch a DoS/DDoS attack and keep you out of the picture. Although these kind of attacks are hard to stop unless your hosting provider helps you, you can at least have your site cached so that it keeps working as much as possible. A typical WordPress installation can make up to 100 SQL queries per request. Multiply that to your number of visitors and your server has to deal with thousands of requests per second. That is not a problem. Servers are made to handle millions and billions of requests. However if you have your pages cached as static pages your server will do 0 requests. This is hardly a bulletproof solution but it will helps. Also it will make your site run smoother during normal days. Some great cache plugins are: W3 Total Cache and WP Super Cache.

13. Don’t post from the same account as the administrative one.

Even if you have deleted your default “admin” profile it is not a good idea to post from your new one. That is an obvious way for your attackers to understand your account (remember they need 3 things).  Always create one administrative account and another one for your posting purposes. Give the posting account lowest role as possible – editor or author. That way even if someone gains access to that account they will only be able to change that accounts posts and not harm anything else.

14. Install activity log plugin

Speaking of gaining access and changing stuff you can install a tool to monitor that activity. That way will know what was actually changed. If it harmless you can just reverse a post change or delete a simple file. Some good activity log plugins are: Activity Log.

15. Install login notifications – who tried to login and from where

If you really want to be paranoiac about security you should look from within as well. If you have more then one account or if you give your only account’s login details to other people (which you shoudn’t do), you should have a way to see what they were doing – installed new plugin, deleted a post, changed a setting …

How to extend community module in Magento 1.9

This is not gonna be a groundbreaking article. All the things in it are well known and can be found in dozens other places. However I do hope that you will find that last bit you are missing and get your extension/module working.

We are going to extend Vladimir Popov’s WebForms Pro 2 – a Magento extension for creating dynamic forms and storing the results in the store’s database. It is a great extension and there is nothing wrong with it. A client of ours wanted a little bit of additional functionality which does not exists. He wanted the ability to send a notification to different emails based on the country field selected by the customer. Generally the extension allows sending a notification only to one (administrative) account.

The following steps presume you are already somehow familiar with Magento’s code structure i.e. core/local/community folders.

  1. Create a new module in app/etc/modules/
    The module name we used is “HtmlPet_WebForms.xml” – HtmlPet is the name of our company and WebForms because we are going to extend the WebForms.

    <?xml version="1.0"?>
    	    <VladimirPopov_WebForms />

    The <depends> tag tells Magento that this mobule needs another one (VladimirPopov_WebForms) in order to work. That way we’ll ensure our extension will override the original.

  2. Create the minimal file structure required.There isn’t much to do with only the previous step so we need to create few folders.
    – app/code/local/HtmlPet/WebForms/etc
    – app/code/local/HtmlPet/WebForms/Block
    – app/code/local/HtmlPet/WebForms/Model
    – app/code/local/HtmlPet/WebForms/sqlWe are going to use the local code pool as we defined that in the module xml from step one. You can create a lot more folders than these but for the sake of our little extension these ones are enough.
  3. Creating a config.xml file.
    The first .xml file we created only tells Magento that there is a module. The config.xml is the one that “pulls the strings”. The basic one we need for now is:

    <?xml version="1.0"?>

    It will change in the next few steps.

  4. All so far are standard Magento stuff and they don’t do much. At this point it is good to know what we want to do.  Initially the WebForms interface looks like that.

    For the purpose of the task in question we need to add a new field to configure the different emails for the different countries. So we need to find which file adds all the original fields and then replace it in our extension. That file is app/code/community/VladimirPopov/WebForms/Block/Adminhtml/Edit/Tab/Email.php. Now when we know the file name will need to modify the config.xml file to tell Magento that we want to use different file. Since it is a block we need to add a <blocks> section in the <global> tag.


    The <adminhtml_webforms_edit_tab_email> tag tells magento we want to <rewrite> the file that matches it with the content of the tag.

    Then we need to copy the file app/code/community/VladimirPopov/WebForms/Block/Adminhtml/Edit/Tab/Email.php to app/code/local/HtmlPet/WebForms/Block/Adminhtml/Edit/Tab/Email.php

    Change the class name to reflect the new location i.e. “HtmlPet_WebForms_Block_Adminhtml_Webforms_Edit_Tab_Email”. In the newly created “local” file we can do pretty much everything without worrying that we will damage the webpage if the original extension updates their code. That’s the beauty of Magento.

    In this particular case we want to add a new textarea field and luckily the author has a pretty sweet way of adding fields and fieldsets so we only need to follow its lead.

    $fieldset = $form->addFieldset('countrybased_notifications', array(
                'legend' => Mage::helper('webforms')->__('Countrybased notifications')
            $country_1 = $fieldset->addField('countrybased_pairs', 'textarea', array(
                'label' => Mage::helper('webforms')->__('Pairs'),
                'note'  => Mage::helper('webforms')->__('Input pair of COUNTRYCODE:email@address on a new line. If there is no match the default admin notification email will be used'),
                'name'  => 'countrybased_pairs'

    That code will transform the original form to this one.


    So far, so good. Now we need to make that data field persistent.

  5. Add an SQL upgrade scriptIn order to save data to store’s database it has to be extended. That can happen with PhpMyAdmin and modifying the database but that solution is not flexible. If this module needs to be populated on another environment you have to modify the database in there too. So Magento give us pretty sweet way to extend the database (Read more about it Magento keeps track on your module’s version and if it detects that there is a change in the version it will search for an upgrade script. Or if there isn’t any track of your module being able to write to the database it will search for the install script. That is what we are going to create – an install script.First we need to tell Mageneto that our extension is going to write to the database.

    And after that create a file to execute. The file needs to be created at app/code/local/HtmlPet/WebForms/sql/webforms_setup_local/ and it has to have the name “install-0.1.0.php” where “webforms_setup_local” is the folder we entered in the <resource> file and 0.1.0 is the version of the module.

    $installer = $this;
    		'TEXT NOT NULL AFTER `email`'

    Next time you refresh the admin area or the frontend Magento will create that field in the database.

  6. Use the configuration dataAt this point we have extended the original module (without touching it) so that it can save new field of data. For the purpose of the task we used the following format to enter the different emails for the different

    First part is the country code and the second is the email that is going to handle that request. The last part is to redirect emails to the correct place. Original module sends emails via file called app/code/community/VladimirPopov/WebForms/Model/Results.php so that is the file we need to modify. Copy the file to your folder (app/code/local/HtmlPet/WebForms/Model) and change the name to reflect your structure i.e. HtmlPet_WebForms_Model_Results.

    In your new Results.php file find the function “sendEmail” and in it find this line:

    $email = $emailSettings['email'];

    After it add

    // Send an email to an appropriate recipient based on the customer selected country
            if($country_based_email = $this->getCountryBasedEmail($webform)) {
                $email = $country_based_email;

    Add this function to your Results.php

    private function getCountryBasedEmail($webform)
            $pairs = $webform->getData('countrybased_pairs');
            $country_support_email = array();
            $pairs_split = preg_split("/\n|\r\n/", $pairs);
            foreach($pairs_split as $row) {
                $pair = explode(':', $row);
                $country_support_email[trim($pair[0])] = trim($pair[1]);
            $country_code = $this->getValue('country');
            if(isset($country_support_email[$country_code])) {
                return $country_support_email[$country_code];
            return null;

    This function parses the text we did input in the form configuration and returns the appropriate email. N.B. This extension assumes you are using a form with country field named “country”. If you are using a different name you need to adjust that in here:

    $country_code = $this->getValue('country');

    That is pretty much it. Looks like a lot but it is all logical (the Magento’s logic is solid, not my article ;)). If you have any questions or disagree with the way we did it, please let us know in the comments.

    You can find all the files in our Github repo – - It starts again is a web service that helps people find the best daily exchange rate between the most popular currencies in Bulgaria (USD, GBP and CHF) and bulgarian lev. It is dead simple to use. Just take a look at the massive table and you will see in green the best offer at the time if you want to buy or sell. Then head to the nearest branch of the bank or exchange office and do your thing. - Възкресение

През октомври 2014 трябваше да замина за Лондон за няколко седмици и трябваше да закупя малко британски паундове, за да имам когато кацна, а след това да използвам банкоматите там. Започнах да преглеждам курсовете на паунда в различни банки, за да видя къде ми е по-изгодно да го направя, но се оказа че е доста тежка задача – много банки, разликите в курса са малки, пипкава работа да намеря разликата.

Веднага реших да автоматизирам решението си. Направих няколко скрипта, които ми преровиха банките и техните курсове и правилния резултат веднага светна. Тогава се роди – автоматизирана система за сравняване на най-изгодните курсове на основните валути в България. Взимат се предвид всичките 22 търговски банки у нас, 2 големи бюра за обмяна на валута и БНБ като референция. Малко след това се появи и приложението за Android.

За съжаление няколко месеца по-късно проекта беше спрян, защото въпреки че голямата част от процесите бяха автоматизирани, имаше няколко, които трябваше да правя ръчно, които постоянно забравях и това доведе до неглежирането на проекта.

Но се възроди и вече отново е активен, като този път коригирах пропуските и вече всичко е автомат. Имам няколко добри идеи как да продължа проекта и съм оптимистично настроен, че ще помогне на хората да спестят. Ако на дата 20 юли 2015 някой иска да закупи 100 долара, то най-добрия курс за деня би му спестил 7.60лв. спрямо най-неизгодния. При 1000 долара това вече е чувствителната сума от 76лв. Не е малко.

Списък с real-life room escape games в България


Игрите офлайн не са в профила на HTML Pet, но аз се забавлявам като ги посещавам и в търсене на други стаи, освен тези, които съм решил (с помощта на екип, разбира се), видях че няма едно място където да са листнати всичките. За удобство на всички фенове на тези стаи, реших да съставя този списък.


Dextrophobia Rooms
3 Key Rooms
House Of Puzzle
Mind Rooms
Escape Rooms
Chamber of Secrets
Escape Plan
Puzzle Escape
Room 66
Dark Rooms
Hour Escape
Килия 49


Domus Rebus
Let Me Out


Escape Room Varna
Logic Room


Escape Game Challenge


Exit Game
Burgas Escape

Велико Търново

Mind Rooms (Не е отворена още)

Знаете ли някоя друга стая, която е пропусната тук? Моля да оставите коментар, за да я попълня.


Първи plugin в

Преди няколко седмици мой клиент се оплака, че неговите WordPress сайтове не показват коректно картинките си, когато се споделят във Facebook. И наистина Facebook избираше снимките малко на случаен принцип, защото нямаше сложен Open Graph таг за снимка към статията. Веднага потърсих plugin-и за Open Graph и тествах до колко са правилното решение на проблема. Оказа се, че или не работят или са прекалено сложни (с много настройки изискващи регистриран Facebook Application). Реших, че най-добре ще е да напиша собствен plugin, който да реши проблема по начин, който аз намирам за правилен.

И така се роди “Simple Facebook OG Image“. Наистина опростен plugin, който прави само едно нещо – генерира Open Graph таг за снимка към статията. Има три правила за определяне на снимката:

  1. Ако има featured снимка, тя се прилага.
  2. Ако има снимки в съдържанието, първата се прилага.
  3. Ако има избрана снимка по подразбиране от настройките, тя се прилага.

Като допълнение, ако сайта има инсталиран plugin като W3 Total Cache т.е. с постоянен кеш, веднъж избрана снимката, тя се кешира, така че plugin-а няма да рови постоянно и да натоварва излишно сайта.

Github хранилището може да се намери тук:  (трябва да е simple-facebook-ogimage, но съм сбъркал, когато го правил и по-късно се усетих).

LESS helpers

One of the best things about CSS preprocessors is that you can include files during build, which will never be output to the clients. This gives space for some pretty useful helper files to be involved. This is one I use to maintain general margins and paddings.