Inchoo http://inchoo.net Magento Design and Magento Development Professionals - Inchoo Fri, 24 Mar 2017 09:48:20 +0000 en-US hourly 1 https://wordpress.org/?v=4.7.3 Meet Magento Croatia 2017 official review (with presentations and photos) http://inchoo.net/life-at-inchoo/meet-magento-croatia-2017-review-presentations-photos/ http://inchoo.net/life-at-inchoo/meet-magento-croatia-2017-review-presentations-photos/#respond Fri, 24 Mar 2017 09:40:38 +0000 http://inchoo.net/?p=29078 After all the feedback we’ve received from #MM17HR delegates, we can proudly say that we are more than satisfied with first Meet Magento Croatia event. On March 17th and 18th, for the first time in Croatia, in Inchoo’s hometown Osijek, we were honored to welcome over 200 delegates from 18 countries and 5 continents! Why...

The post Meet Magento Croatia 2017 official review (with presentations and photos) appeared first on Inchoo.

]]>
After all the feedback we’ve received from #MM17HR delegates, we can proudly say that we are more than satisfied with first Meet Magento Croatia event. On March 17th and 18th, for the first time in Croatia, in Inchoo’s hometown Osijek, we were honored to welcome over 200 delegates from 18 countries and 5 continents!

Why Meet Magento Croatia?

Croatian eCommerce market has been growing for the last several years. Because of that, we find that the timing was right to introduce this kind of conference, where eCommerce and Magento topics can be presented side by side to bring valuable information and networking to all Meet Magento Croatia participants on business and development tracks. To be honest, we were preparing the event for 120-130 people (150 was a number we would’ve been happy with), but when we reached 200 on our delegates list, we knew this was going to be huge!

First of all, a big shout out to 35 speakers that brought enthusiasm, knowledge and information that made delegates take notes and feel at ease asking questions! And you know, we organisers love to see when the audience is active! It shows us that we picked the right speakers and topics for you.

We are happy that we could start a conference a day early, on March 16th, where we had a special add on the agenda – Magento 2 workshop with Magento architects. Magento Contribution Day, as Magento officially calls it, was a perfect chance to work side by side with Magento core team and contribute to Magento 2 improvement. Thank you Max, Igor and Ievgen!

Day 1

The official opening of Meet Magento Croatia was on March 17th, where we had mayor and vice-mayor of the City of Osijek welcoming all of #MM17HR participants alongside with Lydia Schaffranek from Meet Magento Association and Ben Marks (you know, that guy who doesn’t let you edit the core).

The day passed with delegates being immersed in great development topics, such as Magento 2 Architecture, best practices and tools to improve security of Magento shops, state of Magento 2 frontend, PHPSpec, Shell Scripts and Cron jobs, Messing up Dependency Injections and properly overriding Magento 2 core functionalities. Business track was on fire with best practices of using psychology, newsletters and analytics to increase sales, some of the global and regional eCommerce trends (virtual marketplaces) and several great Magento Case Study examples.

The day ended with a great party where everyone loosened up (not that they were very formal before) and danced the night away!

Day 2

We were lucky that the party didn’t take too much of a toll on the crowd showing up our Saturday (March 18th) for Day 2 of #MM17HR, because it started very early with another #MagentoRun. Inchoo’s CEO, Tomislav Bilic also hit the ground running, but with his talk about the bumpy road of growing a team, followed by topics as revamping Magento 1 RWD theme to improve performance, dynamic scanning with OWASP ZAP, Versioning and Backwards Compatibility and Magento Continuous Integration & Continuous Delivery. Business track had merchants listen about the latest in card payments and fraud, hosting for eCommerce and Magento shops, all nicely seasoned with a pinch of legal advice for Croatian merchants.

The agenda was sputtering with great panel discussions about eCommerce Managers, win-win environment for clients and development agencies and a sort of AMA type of interaction, where Ben Marks, Max Yekaterynenko and Jisse Reitsma were questioned about Magento 2 and its future. And just to be clear: no, we didn’t pay the audience to ask the tough questions! 🙂

Presentations

We know you are eager to continue the learning process started at the conference and if you didn’t have the time to write down just that special tip that is all so important – we have you covered.
Here you can find most of the presentations. If you’d like to expand on this knowledge and advice from our speakers, feel free to get in touch with them directly!

Check out the photos HERE!

Now, after you know all the details, check out the photos from the conference! Feel free to spread the Magento Community word, like, share and tag yourself or anybody you know!

Hope to see you at Meet Magento Croatia 2018!

The post Meet Magento Croatia 2017 official review (with presentations and photos) appeared first on Inchoo.

]]>
http://inchoo.net/life-at-inchoo/meet-magento-croatia-2017-review-presentations-photos/feed/ 0
Google Dynamic Remarketing Campaign: a short set-up guide http://inchoo.net/online-marketing/google-dynamic-remarketing-campaign-a-short-set-up-guide/ http://inchoo.net/online-marketing/google-dynamic-remarketing-campaign-a-short-set-up-guide/#respond Thu, 23 Mar 2017 12:48:35 +0000 http://inchoo.net/?p=29066 We wrote about Google Shopping Campaign as an unavoidable type of online advertising and its significance in web shop sales. But, what can we do about the buyers who are not ready to buy a certain product right away? The answer is remarketing! Remarketing enables you to reach the client who visited your web shop,...

The post Google Dynamic Remarketing Campaign: a short set-up guide appeared first on Inchoo.

]]>
We wrote about Google Shopping Campaign as an unavoidable type of online advertising and its significance in web shop sales. But, what can we do about the buyers who are not ready to buy a certain product right away? The answer is remarketing!

Remarketing enables you to reach the client who visited your web shop, but did not buy anything or did not complete any goal you defined. Because of that, it is necessary to remind those clients that they did not fulfil what we think that is their “task/homework” via Google Display Network or Google Search Network. In order to reach your goal for them to purchase as efficiently as possible, it is necessary to show the clients what they have been searching for and encourage them to re-engage. This is exactly what Dynamic Remarketing does.

What is Dynamic Remarketing?

Dynamic Remarketing is a type of campaign for Google Adwords, where Google itself creates personalised ads for every individual visitor. The ads don’t hold the customer’s name, but the products he or she has previously watched while surfing the web site. Ads show the price, as well as the description and the image of the product, and the text which you can add. Before Google does all of this for you, you have to generate the product feed from which Google will take data, create an audience and ad formats you want and need.

Prerequisites for Dynamic Remarketing Campaign:

Product feed

The first thing you have to do is make a product feed (.csv, .tsv, .xls, or .xlsx format) with certain attributes connected to your product (you can also use the feed you have created for Google Shopping Campaign). After doing this, upload it to Business Data section in your Google Adwords. But, if your are a retailer, you will have to upload the product feed to Google Merchant Center. There is one important thing to remember – when you have created the product feed, your job is not done. If you have a lot of products, it is advisable to update the product feed once a day because of the possible changes in price, sale terminations,  products being out of stock and so on. You do not want to lead the potential buyers to web sites with incorrect prices, out of stock products, 404 landing pages, and similar. The problem is not only with taking buyers to the wrong landing page, but you also risk a penalty from Google. Managing product feed can be difficult, especially if you have a lot of products and changes with your products. It is thus necessary to have a person who understands it all and monitors what you do in order to prevent possible problems, since this is the key part which makes Dynamic Remarketing achieve good results.

Remarketing tag with custom parameters

Then, after creating the product feed, start with setting up a remarketing tag with custom parameters to all the web pages from which you want to gather information about your visitors. You can find your remarketing tag in the Audience section of your Shared Library, and when you get to that part, just click on “set up remarketing”.

The next step will lead you to this.

The most important thing is to tick the box in order to use the Dynamic Ads. When you are done with that and when you have chosen “retail” from the dropdown menu under “business type”, you should click on “set up marketing”, which will lead you to a detailed instruction on how to connect Google Adwords with Google Merchant Center (in case you have not already done this). It will also show you how to connect remarketing tag with instruction on how to implement it on the site. When you are finished with the implementation of the remarketing tag, Google Adwords will create 6 default remarketing lists.  

My advice is to think about which people you want to target, maybe according to some shared features, their behaviour on the web site, the way they found your site, and so on. Based on these, you can create your own remarketing list with specific rules. Also, you can create the audience via Google Analytics, if your accounts are linked. This is very important because you can bid differently for every ad group which has been added a different remarketing list.

Dynamic ads

After creating remarketing lists, you also can create ad groups for every individual remarketing list, as well as create ads so that the campaign can start. Under the tab “Ads”, click on the button “+AD” and choose Ad Gallery, and then Dynamic Ads. The next step is to choose between different ad formats: dynamic responsive ads, dynamic product ads – image ads, and dynamic products ad – text ads.

The picture above shows display responsive ad format, which consists of 8 different ad formats with pictures, texts or the combination of both. Your job is to add a logo of your company, write a headline and a short description, while Google Adwords automatically transfers pictures, prices and the description of the product from product feed.

 

The difference between standard and dynamic remarketing

Main advantage of dynamic remarketing in comparison to standard remarketing, is that you can target visitor’s product preferences because dynamic remarketing does associate product data and browsing history with the visitor.

Due to this feature, dynamic remarketing ads can be precisely targeted to a specific product or a group of products visitor has browsed. In addition to that, ad creatives can be automatically built using ad templates. With standard remarketing this precise targeting and automatic ad building is not possible.

For example, a visitor on the site can browse garden furniture, and with standard remarketing you might have an ad that shows a drilling machine, which is definitely not an optimal way to attract the visitor to come back to the site.

Because of all these reasons, dynamic remarketing is a much better option because every visitor gets an ad with the browsed products. For example, visitor has browsed a specific black and white sofa, and an ad with that exact sofa will be shown to that visitor, without the need to create an ad with that particular sofa. This makes the ads more relevant, campaign management much easier and there is a better chance that the user would return and make a purchase.

Why you should use dynamic remarketing?

  • Ad managing – the whole process is simplified because Google creates the ads by taking data from the product feed, you only need to choose between offered ad formats and edit the text headline and description.
  • Returning visitor – the number of visitors coming back to the site will increase and there is a big possibility that they will buy with much less hesitation than the new customers.
  • Personalised ad – help with attracting customers to come back to the site because these ads are more noticeable, they include the products visitor has already shown the interest for.
  • Brand awareness – like other types of online advertising, dynamic remarketing increases brand awareness, therefore, number of further purchases and loyal customers.

Dynamic remarketing will help you increase the sale on web shop and because of that, it has to be a great part of your online advertising. Setting up is a bit more complex because you need developer skills for implementing the remarketing tag with custom parameters, but it won’t be in vain because if you set it up the right way, you will be able to see the results. Try it out!

If all this is too much for you to handle because of all the other work you have to do, you can check our eCommerce PPC Management service and hire us. We’ll be happy to help you out.

Get in touch!

The post Google Dynamic Remarketing Campaign: a short set-up guide appeared first on Inchoo.

]]>
http://inchoo.net/online-marketing/google-dynamic-remarketing-campaign-a-short-set-up-guide/feed/ 0
How I learned to stop worrying and love 3rd party modules for Magento http://inchoo.net/magento/learned-stop-worrying-love-3rd-party-modules-magento/ http://inchoo.net/magento/learned-stop-worrying-love-3rd-party-modules-magento/#respond Tue, 21 Mar 2017 11:34:50 +0000 http://inchoo.net/?p=29041 It’s just another day at the office. “Coffee tastes best on a Friday morning”, you think to yourself while reading emails from the previous day. Couple of guys are shouting in the back, and you’re shushing them angrily. With noise slowly diminishing, you find yourself checking an email with a request for custom change on...

The post How I learned to stop worrying and love 3rd party modules for Magento appeared first on Inchoo.

]]>
It’s just another day at the office. “Coffee tastes best on a Friday morning”, you think to yourself while reading emails from the previous day. Couple of guys are shouting in the back, and you’re shushing them angrily. With noise slowly diminishing, you find yourself checking an email with a request for custom change on one of your Magento stores. “Guys, we’ve got work to do. WeHeart3rdPartyModules Ltd. just requested a custom change on one of their stores. Who is willing to take on this one?”. Silence takes over the room. The only sound you hear is a fan from your colleagues PC (no need to ask why, Magento 2 is deploying static content). It seems like you’re in this alone. “Damn, I was hoping for a slow Friday”.

While PHPStorm is loading up, you’re reading the requirements:

Hi guys,

we would like to add a new column to our sales orders grid. We understand we have 20 of them already, but we really need this one – coupon description. Can you do this for us?

Thanks


“You mean rule description, not coupon, noob!”
, a smirk appears on your face. “I’ve done this a couple of times already. Also, I think we have an article written about this exact problem”.

Using your google-fu, an article pops up on the screen: How to extend Magento Order Grid? says the title. “Just what I need. This will be a piece of cake”.

IDE is finally loaded, and you’re rushing to get this done as fast as possible. “app, code, local, Inchoo” – your mind shouts – “you’ve done this a million times before” – but then you stop, and dark cloud just hangs over you. “Now I remember why there were no volunteers. This project has over a hundred 3rd party modules in local pool alone”. Not only your Friday is ruined, but the entire weekend does not look promising either.

“Well, let’s dig in, then.” Your train of though is something along the lines: “they have over 100 modules.. email mentions some earlier changes on sales order grid (having 20 columns already).. there must be a 3rd party module enabling them, or.. have we implemented this change already?”. You jump to Inchoo_WeHeart module’s config.xml and see no rewrites on 'sales_order_grid' block. It must be 3rd party then. Lovely.

Fortunately, your IDE is good in identifying class hierarchy, and in just a couple of clicks you find the class rewrite, and 3rd party module responsible (actually several of them, but two are disabled):  Amasty extended order grid. This should be fun. Module allows us to select columns to add to “Sales > Orders” grid. Adrenaline spikes up – “what if the column we need can be added via admin? That would be so great”. You check system.xml and adminhtml.xml, run to the admin and start looking for columns. “Of course it’s not possible, it was too good to be true”.

You’ve been in this situation before, and you do the usual routine: start randomly opening 3rd party module’s classes hoping to find something so obvious that would allow you to finish this as fast as possible. Of course, you fail. Looks like a more systematic approach is needed after all. You’re checking config.xml, opening up observers, helpers, and many other classes, tracing the code. Finally you find it. It’s so obvious now.

Now, off to coupon description field. You go through Magento database, and find that coupon description is located in 'salesrule_coupon' table, but there is no description. Description is in another table – 'salesrule'. Looks like we’ll need 2 SQL joins for this. “It’s just a few lines”, a voice in your head says, “no one will notice it. And it’s not a core code, right?”. After a short struggle, you resist the temptation and decide not to change the module’s code directly (sell your soul for a quick fix).

Copying the code to local pool (if the module is in community) is also an option, but we can, and should, do better than that. Is class rewrite good enough? In most situations, yes, and this is one of those situations too. You quickly whip up a rewrite: “I can do this in my sleep”, you think to yourself, but somehow manage to spend 45 minutes on getting it to work (damn XML typo).

<?php
 
class Inchoo_WeHeart_Helper_Ogrid_Columns extends Amasty_Ogrid_Helper_Columns
{
 
    function prepareOrderCollectionJoins(&$collection, $orderItemsColumns = array()){
 
        parent::prepareOrderCollectionJoins($collection, $orderItemsColumns);
 
        $collection->getSelect()->joinLeft(
            array(
                'salesrule_coupon' => $collection->getTable('salesrule/coupon')
            ),
            'order.coupon_code = salesrule_coupon.code',
            array('salesrule_coupon.rule_id as coupon_rule_id')
        )->joinLeft(
            array(
                'salesrule_rule' => $collection->getTable('salesrule/rule')
            ),
            'salesrule_rule.rule_id = salesrule_coupon.rule_id',
            array('salesrule_rule.description as coupon_description')
        );
 
    }
 
    function getDefaultFields(){
 
        if (!$this->_defaultField){
            $this->_defaultField = array_merge(parent::getDefaultFields(), array(
                'discount_description' => array(
                    'header' => Mage::helper('sales')->__('Discount Description'),
                    'index' => 'coupon_description',
                    'filter_index' => 'salesrule_rule.description'
                )
            ));
        }
 
        return $this->_defaultField;
    }
 
}

You look satisfied with the solution. Module has been changed, but in an unobtrusive way. Original logic has been kept, and new functionality added. With a few keystrokes, code is committed to a feature branch, and pushed to dev server. You test it there once more, and it looks like it’s working. Response message is sent:

“Hi WeHeart”, (no need to call them by their full name, they’re used to it), “changes have been made and pushed to dev server for you to test. Regards, Inchoo.

You close down the browser, and check the time: “Oh, looks like it’s time for my break”.

The post How I learned to stop worrying and love 3rd party modules for Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/learned-stop-worrying-love-3rd-party-modules-magento/feed/ 0
Take a final look at Meet Magento Croatia activities! http://inchoo.net/life-at-inchoo/meet-magento-croatia-2017-kickoff/ http://inchoo.net/life-at-inchoo/meet-magento-croatia-2017-kickoff/#respond Thu, 16 Mar 2017 12:24:07 +0000 http://inchoo.net/?p=29028 We are about to officially kick off the first ever Meet Magento Croatia. Have a final look at what awaits you in Osijek! We are thrilled to be able to welcome over 200 amazing individuals from 18 countries and from 5 different continents for an event packed with hot Magento 2 topics and lots of...

The post Take a final look at Meet Magento Croatia activities! appeared first on Inchoo.

]]>
We are about to officially kick off the first ever Meet Magento Croatia. Have a final look at what awaits you in Osijek!

We are thrilled to be able to welcome over 200 amazing individuals from 18 countries and from 5 different continents for an event packed with hot Magento 2 topics and lots of eCommerce inspired fun.

We’ll be, unofficially, starting today (Thursday, March 16th) afternoon due to the fact there will be a special add on to the agenda – Magento 2 workshop with Magento architects. Magento Contribution Day, as Magento officially calls it, is a perfect chance to work side by side with Magento core team and contribute to Magento 2 improvement.

In case you’ll miss the workshop, make sure to join us for the official agenda because, apart from Magento 2 architects, we gathered a whole bunch of experts ready to share their knowledge. We got you covered with some pretty familiar faces (hi Ben Marks!) and some people you’ll see conquering the stage for the first time.

So, in case you didn’t see the agenda, feel free to take a closer look here.

As you probably know by now, having a carefully arranged agenda is only a half of the work. Apart from interesting topics, you’ll be able to join us for interesting networking activities too!

First off, make sure to meet us at Merlon (Franje Markovića 3, 31000 Osijek) tonight where we’ll have a small get together after the Magento 2 workshop. You can simply drop by any time that suits you after 8PM.

You will also be able to enjoy the conference afterparty which will be held in a club called Outside (Trg Vatroslava Lisinskog bb, 31000, Osijek) on Friday evening. The party will start at 8PM but for any additional info on both the afterparty and any conference activities, follow the official conference hashtag on Twitter – #MM17HR!

And, of course, for the party survivors – don’t forget to join us on Saturday morning (7AM to be precise) in front of the Hotel Osijek from where we’ll start our #RealMagento inspired run.

As you can see, we’ve carefully selected both the speakers and the conference activities so we hope you’re as excited as we are to get the event started. See you soon! 🙂

The post Take a final look at Meet Magento Croatia activities! appeared first on Inchoo.

]]>
http://inchoo.net/life-at-inchoo/meet-magento-croatia-2017-kickoff/feed/ 0
Commitment – foundations and expectations http://inchoo.net/life-at-inchoo/company-culture-commitment/ http://inchoo.net/life-at-inchoo/company-culture-commitment/#respond Mon, 13 Mar 2017 12:16:45 +0000 http://inchoo.net/?p=29013 Company culture defines its journey. One of the definitions says culture is based on shared attitudes, beliefs, customs, and written and unwritten rules that have been developed over time and are considered valid. It includes both work and general behaviour philosophy and set of values thus shaping company efforts and overall activities. One of the...

The post Commitment – foundations and expectations appeared first on Inchoo.

]]>
Company culture defines its journey. One of the definitions says culture is based on shared attitudes, beliefs, customs, and written and unwritten rules that have been developed over time and are considered valid. It includes both work and general behaviour philosophy and set of values thus shaping company efforts and overall activities. One of the main attributes of Inchoo’s company culture is commitment to the local community.

How sharing leads to engaging?

When I asked Tomislav, our CEO, why commitment to the local community is so important, this was his answer:

“Inchoo has an office in a small Croatian town called Osijek with a population of 100.000. Since the inception in ’08, we were hiring from the local pool of talent.

Industry in Osijek collapsed after Croatian war for independence. Today, economy is still struggling and a lot of young people are leaving the city. Some are moving to Zagreb (capital of Croatia) and some are leaving the country because they just can’t see future. Let’s say that things are a bit apathetic. Fortunately, we are working in the software development niche that is highly dynamic, interesting and can enable an individual to shape his career from any part of the world. We realise that, by sharing our experience, we can influence many young people in the local community to engage in programming, design, analytics or digital entrepreneurship.“

Inchoo is not a lone wolf in its efforts. Many companies in Osijek share the same goal. That’s why in 2012, we joined forces and founded “Osijek Software City, a non-profit association that’s now shaping the identity of modern Osijek, making it recognised as a strong technology centre.

With the aim to make positive impact, we organize events, seminars, workshops, sessions and hackathons. These events are free. The community is where we give back but without guaranteed benefit for the company. Reward is realizing that we’ve made a positive change in someone’s life.

These inspired individuals will make the ecosystem stronger, engaged and more fun. Some of them might become future Inchooers at some point, but some of them will get recruited by other companies or they will start their own businesses. No matter the path they choose, ecosystem will be better and more young people will decide to build their future and stay here.

Focusing on young people

I am glad to see Inchooers accept and adhere such approach and their willingness to participate in those events and workshops affirms that. In 2016., due to an increasing challenge in finding new team members, we endeavoured to influence the change ourselves (more on that topic here). Therefore, we focused our efforts on young people by organizing and supporting various events and mentored, custom tailored internships.

Here’s an overview of activities tailored to support earlier mentioned values for 2016.:

  • Organizing 19 internships in 6 departments for students from 5 different faculties and 1 highschool
  • Support to “EWob Business Hackathon” in organization of a students association EWoB
  • Sponsoring Osijek’s students trip “Elektrijada”, international competition of students from electrical engineering faculties
  • Coorganizing CodeCAMP “Magento 2 Frontend Overview” workshop
  • Coorganizing CodeCAMP “Knockout.JS Basics” workshop
  • Presenting Inchoo internships on Faculty of Electrical Engineering, Computer Science and Information Technology Osijek
  • Organizing “PHP talks”, event on getting to know students with PHP
  • Organizing “PHP academy”, project on teaching students PHP over 5 weeks courses
  • Participating in Europe Code Week by organizing “Web shop – simple interface for administering data and products by category” workshop for highschool students
  • Participating in Web programming course in Faculty by organizing “eCommerce development” lecture
  • Participating in Databases course in Faculty by organizing “MySQL in web development” lecture
  • Coorganizing “Managing growth at Inchoo” lecture for Faculty of economics students
  • Mentoring student’s thesis

As seen, we voluntarily teach skills, offer knowledge and experience in order to contribute to student’s life. These efforts were officially recognized last year by students themselves when they rewarded Inchoo with a “Golden Index 2015.” in a “Special committee award” category.

“Golden Index” is a project in which companies that contributed to students education and professional development are awarded a unique prize in Croatia by a student association “eStudent”. Firms can, depending on initiated activities, apply in several categories for the reward. This year, Inchoo has, for the second time, applied in “Students interns” and “Expert support and project organizing” since that is where our efforts lay the most.

Being recognized by our own “target group” puts our endeavour in the best light and gives us motivation to keep on doing what we do.

The post Commitment – foundations and expectations appeared first on Inchoo.

]]>
http://inchoo.net/life-at-inchoo/company-culture-commitment/feed/ 0
Using PostCSS with Sass http://inchoo.net/magento/magento-frontend/using-postcss-sass/ http://inchoo.net/magento/magento-frontend/using-postcss-sass/#comments Tue, 07 Mar 2017 07:37:01 +0000 http://inchoo.net/?p=28940 In this article we’ll be looking to a basic overview of PostCSS from the perspective of a developer whose current CSS development process includes use of CSS preprocessor, or in particular, Sass. If you’re a Sass user, there are a couple of approaches when starting out with PostCSS. You could make a complete switch and...

The post Using PostCSS with Sass appeared first on Inchoo.

]]>
In this article we’ll be looking to a basic overview of PostCSS from the perspective of a developer whose current CSS development process includes use of CSS preprocessor, or in particular, Sass. If you’re a Sass user, there are a couple of approaches when starting out with PostCSS. You could make a complete switch and recreate your basic preprocessing environment with PostCSS, or you could start using it as a supplement.

Many of you will say that you still only rely on your favorite preprocessor, but then, it’s possible that you’re also using Autoprefixer for vendor prefixing, and guess what? In this case, you have already included PostCSS into your workflow.

What exactly are we talking about?

PostCSS is a tool, or basically, just an API which handles its plugins written in JavaScript.

Comparing to Sass, which has a bunch of features out of the box, PostCSS comes as a blank plate, ready to be filled with the ingredients you need.

Basic Setup

Including PostCSS into your project is not a complicated process, especially if you have a basic experience of using some of the task runners, such as Gulp or Grunt.

As a simple example, let’s take a look at the following gulpfile.js.

var gulp = require('gulp'),
    postcss = require('gulp-postcss'),
    autoprefixer = require('autoprefixer');
 
gulp.task('css', function() {
  return gulp.src('src/style.css')
    .pipe(postcss(
      autoprefixer()
    ))
    .pipe(gulp.dest('dest/style.css'));
});

What we see here is a two step process:

  1. First, we include the main PostCSS module.
  2. Next, we add PostCSS plugin(s) we want to use (which in these short example is the only one – Autoprefixer).

Of course, like with any new gulp plugin which you include into your gulpfile.js, PostCSS module and any additional PostCSS plugin need to be installed first. This can be done in a terminal, with a simple command, familiar to all Gulp users:

npm install gulp-postcss autoprefixer --save-dev

Choosing plugins

So, which plugins do we need? Well, this comes to your individual choice. For an easy start or just for supplementing your preprocessing workflow with some additional power, you will certainly gain an instant benefit with these two:

  • Autoprefixer – probably the most popular PostCSS plugin, used for adding required vendor prefixes. As already mentioned at the beginning, there is high chance that you’re already using this one.
  • .box {
      display: flex;
    }
     
    // Result after processing
    .box {
      display: -webkit-box;
      display: -webkit-flex;
      display: -ms-flexbox;
      display: flex;
    }

  • Stylelint – a linting plugin useful for maintaining consistent conventions and avoiding errors in your stylesheets.

If you want to get in more deeper and recreate your basic Sass environment, most likely you’ll also need to require the following plugins:

$blue: #056ef0;
$column: 200px;
 
.menu_link {
    background: $blue;
    width: $column;
}
 
// Result after processing
.menu_link {
    background: #056ef0;
    width: 200px;
}
    • Postcss-nested – gives us a functionality of unwrapping nested rules like how Sass does it.
.phone {
    &_title {
        width: 500px;
        @media (max-width: 500px) {
            width: auto;
        }
    }
}
 
// Result after processing
.phone_title {
    width: 500px;
}
@media (max-width: 500px) {
    .phone_title {
        width: auto;
    }
}
@define-mixin icon $network, $color: blue {
    .icon.is-$(network) {
        color: $color;
        @mixin-content;
    }
    .icon.is-$(network):hover {
        color: white;
        background: $color;
    }
}
 
@mixin icon twitter {
    background: url(twt.png);
}
@mixin icon youtube, red {
    background: url(youtube.png);
}
 
// Result after processing
.icon.is-twitter {
    color: blue;
    background: url(twt.png);
}
.icon.is-twitter:hover {
    color: white;
    background: blue;
}
.icon.is-youtube {
    color: red;
    background: url(youtube.png);
}
.icon.is-youtube:hover {
    color: white;
    background: red;
}

One of the most interesting plugins that we’re mentioning last, is CSSNext. This is actually a collection of plugins that, together, give us a possibility to use the latest CSS syntax today. It transforms new CSS specs into more compatible CSS without a need to waiting for browser support. CSSNext has a lot of features and some of them are:

  • custom properties set & @apply
  • custom properties & var()
  • custom selectors
  • color() function
  • :any-link pseudo-class, etc.

In your CSS file you can do something like this:

// Example for custom properties set & @apply
:root {
  --danger-theme: {
    color: white;
    background-color: red;
  };
}
 
.danger {
  @apply --danger-theme;
}

Why should you use PostCSS?

So, if you already have an effective workflow and you’re satisfied with using your favorite preprocessor for some time now, you might be still asking yourself why do I need to learn another tool (or make the switch from Sass)? What are the benefits?

To answer these questions, let’s summarize some of the advantages:

  • Speed – even though in the meantime Sass got a significantly faster (e.g., LibSass), PostCSS is still the winner here
  • Modularity – reduces bloat; you only include the functionality that you need
  • Lightweight – with previous benefit, you get also this one
  • Immediate implementation – if you want a new functionality, you don’t have to wait for Sass to be updated; you can make it on your own

Of course, everything’s not ideal and there are also certain drawbacks:

  • Increased complexity – more planning is required (e.g., plugins must be called in a specific order)
  • A different syntax (compared to Sass)
  • PostCSS processing requires valid CSS

What’s next

It’s perfectly clear that PostCSS is all about the plugins. At the time of writing, there are more than 200 plugins available (and this number is only getting bigger). So, to go beyond the basics, you’ll need to search for other plugins that will extend this barebones setup.

Of course, if you find out that some handy functionality is missing, go ahead and solve the problem by making your own PostCSS plugin.

The post Using PostCSS with Sass appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-frontend/using-postcss-sass/feed/ 1
How to improve your Magento store performance by using Fastly http://inchoo.net/magento-2/how-to-improve-your-magento-store-performance-by-using-fastly/ http://inchoo.net/magento-2/how-to-improve-your-magento-store-performance-by-using-fastly/#comments Tue, 28 Feb 2017 12:02:45 +0000 http://inchoo.net/?p=28890 If you’re looking for a way to improve your user’s experience in terms of speed – try Fastly. What is Fastly? Fastly is a modern CDN service – it uses SSD disks in their cache servers to ensure fast content access & great cache hit ratio, it offers over 30 POP (point of presence) locations...

The post How to improve your Magento store performance by using Fastly appeared first on Inchoo.

]]>
If you’re looking for a way to improve your user’s experience in terms of speed – try Fastly.

What is Fastly?

Fastly is a modern CDN service – it uses SSD disks in their cache servers to ensure fast content access & great cache hit ratio, it offers over 30 POP (point of presence) locations placed on strategic places all over the world (Asia, Europe, North & South America, Australia, New Zeland), it uses reverse proxying which means that the content is being fetched from your origin server as it’s requested and on top of that – they have greatly supported extensions for Magento 1 & Magento 2.

Benefits

  • Faster load times for web and mobile users
  • Better site stability in the event of traffic surges
  • Easy to configure with your Magento store

Installation

In this article, I will show you how to install and configure Fastly CDN extension for Magento 2.
To install the extension, just follow instructions from their Github repository.

You may choose between three installation methods – composer installation, installation through the Magento Marketplace and manual installation by downloading the zip file.

I will use composer as installation method.

1. Open terminal \ console, go to your Magento installation directory and type these two commands in the following order:

composer config repositories.fastly-magento2 git "https://github.com/fastly/fastly-magento2.git"

Then:

composer require fastly/magento2

Once the installation is completed, enable the Fastly CDN module:

bin/magento module:enable Fastly_Cdn

Immediately after that, run the setup:upgrade command:

bin/magento setup:upgrade

And finally, clear the cache:

bin/magento cache:clean

You can read more detailed step by step instructions here.

That’s it, you have successfully installed the Fastly CDN extension. Let’s move to configuration.

Configuration

In order to use the Fastly Cdn extension, you will have to register a free Fastly account.

Once you register and verify your account, login to Fastly:

Fastly wizard

You will see a welcome wizard with two input fields which you should fill with:

  • Your website domain for Fastly to use when routing requests
  • The hostname (or IP address) and port number for your origin server

On the next screen, Fastly is offering you to enable gzip, logging and health check of your origin – you can enable this later. Click continue.

On the final screen, you will have to point your CNAME to Fastly. Doing this, you will direct traffic from the Internet through Fastly instead of immediately through your store. You can read more here on how to achieve this.


Once you’ve finished with pointing your CNAME to Fastly, let’s configure Magento.

Login to you Magento admin and go to:
Stores > Configuration > Advanced > System

Under the Full page cache tab, untick the Use system value checkbox next to the Caching Application and choose Fastly CDN.

Click on Fastly Configuration tab and enter your Fastly Service ID* and Fastly API key**.

*To find out you Service ID, login to the Fastly dashboard, locate your Service name and click on the Show Service ID link.

**To find out your API key, while in the Fastly dashboard, select Account from the user menu and scroll way down to the bottom of the page. In the Account API Key area, click the Show button.

You can press the Test credentials button just to make sure that you have entered valid credentials.

If you have received a success message, press the Save Config button and clear cache by going to System > Cache Management.

Once you have cleared the cache, go back to Stores > Configuration > Advanced > System and click on the Fastly Configuration. The final step is to upload the VCL to Fastly. You can do this by pressing the Upload VCL to Fastly button.

The modal window will pop up, make sure that the Activate VCL after upload is ticked and press the Upload button in the top right corner:

Once the upload process is done, the modal window will automatically close and the success message will show:

That’s it, you have successfully configured your Magento store with the Fastly CDN.

Advanced configuration

You can configure Advanced options by clicking the Advanced Configuration tab under the Fastly configuration. You have a detailed description under every option – read it and configure it according to your needs.

You can read more about advanced configuration here.

Purging

You can purge Fastly CDN content through the Magento admin by going to System > Cache Management. You can purge content by the following options:

  • Purge by content type
  • Purge by store
  • Purge a URL
  • Purge all

You can read more about purging here.

The post How to improve your Magento store performance by using Fastly appeared first on Inchoo.

]]>
http://inchoo.net/magento-2/how-to-improve-your-magento-store-performance-by-using-fastly/feed/ 6
Create Admin Menu Item in Magento 2 http://inchoo.net/magento-2/admin-menu-item-magento-2/ http://inchoo.net/magento-2/admin-menu-item-magento-2/#comments Tue, 21 Feb 2017 12:21:54 +0000 http://inchoo.net/?p=28843 When it comes to Magento customization, every now and then, there’s a need for a custom configuration option that needs to be placed somewhere in the administration menu. Magento 2 comes with well organized admin menu, but what if newly created configuration option doesn’t fit anywhere? In that case, a new menu item can be...

The post Create Admin Menu Item in Magento 2 appeared first on Inchoo.

]]>
When it comes to Magento customization, every now and then, there’s a need for a custom configuration option that needs to be placed somewhere in the administration menu. Magento 2 comes with well organized admin menu, but what if newly created configuration option doesn’t fit anywhere?

In that case, a new menu item can be created to accommodate that option. Here’s a tutorial on how to do it in Magento 2.

Unlike Magento 1, the admin menu in Magento 2 is located on the left side of the screen. Reason for that is to simplify access to a menu from a tablet or smartphone.

What is a menu item?

Basically, menu item is a link that leads to another admin configuration page. An example of that would be something like this:

href="http://m2.loc/admin/sales/order/index/key/9f8n6825d41694450594r4efc3c779f6df8a8191ffca03f2113ece65436h076e/"

Part of this URL is a sales/order/index, which is an area of interest here. It consists of three parts that need to be defined in menu item XML configuration.

Front Name:		sales
Controller Name:	order
Action Name:		index

There is also a key variable in the link, which serves as a protection from cross site script attacks. This is actually a real reason for menu item to be created the “Magento way”. Any admin request without this variable will be invalidated. Other link parts are created automatically by the Magento.

Create new basic module

In order to demonstrate how menu item is created, a new basic module structure is needed. There’s a nice article on how to create a basic module in Magento 2.

Inchoo/MenuItem/registration.php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Inchoo_MenuItem',
    __DIR__
);

Inchoo/MenuItem/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Inchoo_MenuItem" setup_version="1.0.0" />
</config>

 

Create new menu item

Menus are configured by the file menu.xml which is located in module’s etc/adminhtml folder. It consists of config and menu nodes and add directives. Menu node may consist of multiple add directives.

Inchoo/MenuItem/etc/adminhtml/menu.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <add id="Inchoo_MenuItem::first_level_demo"
             title="Inchoo First Level"
             module="Inchoo_MenuItem"
             sortOrder="9999"
             resource="Magento_Backend::content" />
    </menu>
</config>

As it can be seen from the menu.xml structure, new menu item is typically added using an add directive with it’s parameters. It is wrapped up in a menu tag.

Explanation of the directive’s attributes is as follows:

  • id – unique node identifier, should follow the format: Vendor_Module::menu_item_description
  • title – text that is shown in menu
  • module – current module
  • sortOrder – where to place menu item
  • resource – defines the ACL rule which the admin user must have in order to see and access this menu, it is defined in acl.xml. Otherwise, menu item won’t be rendered. For simplicity, Magento_Backend::content is used.

After running Magento’s CLI command cache:clean, new menu item should be visible. But for now, it leads to nowhere, it is not a hyperlink. It’s not usual for a first level menu items to be hyperlinks, but only to be containers of a second or third levels.

All it takes for a menu item to become an actual link is an action attribute. For menu item that is located on a second level for example, a parent attribute must also be used. It defines on which first level it depends. It can be current module or any other from the menu (e.g. system module Magento_Backend::system). Same logic applies to third level menu item.

Additional attributes:

  • action – defined above, tells Magento to generate link to certain admin controller
  • parent – defines on which first level a menu item depends

Example for a second level menu item with an action:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <add id="Inchoo_MenuItem::first_level_demo"
             title="Inchoo First Level Item"
             module="Inchoo_MenuItem"
             sortOrder="9999"
             resource="Magento_Backend::content" />
 
        <add id="Inchoo_MenuItem::second_level_demo"
             title="Inchoo Second Level Item"
             module="Inchoo_MenuItem"
             sortOrder="0"
             action="menuitem/index/index"
             parent="Inchoo_MenuItem::first_level_demo"
             resource="Magento_Backend::content" />
    </menu>
</config>

 

Flushing the magento cache

After everything is defined, the magento cache should be cleaned with the following CLI command:

php -f bin/magento cache:clean

 

Result

That’s it. The menu item should now be visible in admin menu under the first level “Inchoo First Level Item” at the end and it should lead to a defined action. In this example, for simplicity, action leads to nowhere because module’s controller should be created.

Here’s how it looks like at the end:

Admin Menu Items

In case you feel you could use some extra help with your Magento 2 project, feel free to reach out. We’ve been a trained Magento 2 partner for quite a while now and we’d like to help you make your shop extraordinary! 🙂

The post Create Admin Menu Item in Magento 2 appeared first on Inchoo.

]]>
http://inchoo.net/magento-2/admin-menu-item-magento-2/feed/ 2
Magento, where to start? http://inchoo.net/magento-2/magento-start/ http://inchoo.net/magento-2/magento-start/#comments Tue, 14 Feb 2017 12:18:35 +0000 http://inchoo.net/?p=28658 Hello fellow developers! You always wanted to learn Magento and get a job as a Junior Magento Developer? Read on. What is Magento? Magento is an eCommerce platform built on open source tehnology. It is used to create custom, scalable online shops to suit merchant needs. It is a very flexible platform where we can...

The post Magento, where to start? appeared first on Inchoo.

]]>
Hello fellow developers! You always wanted to learn Magento and get a job as a Junior Magento Developer? Read on.

What is Magento?

Magento is an eCommerce platform built on open source tehnology. It is used to create custom, scalable online shops to suit merchant needs. It is a very flexible platform where we can extend or create new Magento functionalities. But you knew that already, right? Move on.

Magento 1 or Magento 2

“To be or not to be”; that is the question.

Magento 2 is better, bigger and “faster” than Magento 1 but, as you’re starting out, you might encounter some challenges. However, that is not the reason not to learn about the platform. Remember that learning new things never comes of as easy – until it’s done. 😉 You can always start with basics of Magento 1 and get in the “groove” of Magento 2 a bit later. After all, Magento 1 still leads with the number of online shops and in the end, someone needs to maintain them. It is just important to note that you’ll have to learn Magento 2 at some point. Magento 1 will be supported by 2018 only and it will be crucial that you’re ready for the platform of the future once the time runs out on Magento 1.

Neural Prerequisites?

To start working with Magento 2, for Backend developers, it is recommended to have advanced knowledge of “PHP” and basic knowledge of MySQL, XML, Javascript.

Frontend developers should know the ways of HTML, CSS, LESS, RWD, Javascript, Knockout JS and basic understanding of PHP and XML.

Download and Install

To install Magento 2 take a look into this article to guide you trough installation process.

Documentation

Magento is very well documented. Once installed go trough Magento Community User Guide and get familiar with it.

Next step for Backend developers is PHP Developer Guide. I know there is a ton of stuff there. But be persistent, don’t give up, and in the end it will be worthwhile. For Frontend developers there is Frontend Developer Guide.

Backend Training Videos

For Backend developers Magento offeres FREE training online course until 31, March, 2017. So make sure to watch them. They are a very good source of information for what and where is in Magento.

Frontend Training Videos

For Frontend developers Magento offers two on-demand online courses:

Conclusion

As I stated before, Magento is huge and there is a ton of stuff to go through. I’m not gonna lie to you, it’ll be weeks before you start to feel comfortable with Magento. But I promise you it will be worth it. However, keep in mind that these trainings alone wont be enough. What will set you apart from others and get you a job? Being persistent and not giving up!! Write your own code and you’re sure to land an offer!!

The post Magento, where to start? appeared first on Inchoo.

]]>
http://inchoo.net/magento-2/magento-start/feed/ 4
Magento Enablement training in Paris – overview by Inchoo http://inchoo.net/magento-2/magento-enablement-training/ http://inchoo.net/magento-2/magento-enablement-training/#comments Fri, 10 Feb 2017 11:07:23 +0000 http://inchoo.net/?p=28802 Magento often organizes Partner Enablement Events, such as live demos, webinars and quick start trainings. While we participated in a number of such demos and webinars, we’ve never been a part of an on-site training for sales and business development staff aimed at solution and technology partners. This changed last week in France, and here’s...

The post Magento Enablement training in Paris – overview by Inchoo appeared first on Inchoo.

]]>
Magento often organizes Partner Enablement Events, such as live demos, webinars and quick start trainings. While we participated in a number of such demos and webinars, we’ve never been a part of an on-site training for sales and business development staff aimed at solution and technology partners. This changed last week in France, and here’s our overview of what happened in Paris.

Our Paris adventure started when we received an email in December from Magento’s Amy Schade informing us about the upcoming Magento Quick Start Training (targeted for sales people and Magento new hires) and when we saw her invitation, we decided to add another event to our list.

As a prerequisite, we all had to complete free Quick start pre-learning courses which really helped us to sharpen our Magento skills, fill the gaps and learn a thing or two about new Magento products.

Time passed by really quickly and we found ourselves in Paris, sitting in PayPal offices, sipping coffee, eating croissants (if you’re in France, you must try their croissants, they are something special) and waiting for the training to start.

Training started on February 1st and lasted for three days including lectures, case studies, group activities and final team assignment. First day was reserved for Magento Corporate overview and overview of Magento B2B features.

Second day included overview of Digital Commerce platform, discussion about differences between Magento 1 and Magento 2 and overview of Magento Enterprise and Magento Enterprise Cloud Edition functionalities.

Some of Magento’s latest products such as Business intelligence (previously Magento Analytics), MCOM (Magento Commerce Order Management) and Magento’s Payment and Risk were presented on the last day of training after which we had a team’s contest based on a fictive case study. We had to come up with the most suitable and creative solution for their case and most importantly within the client’s budget.

Our sales team, consisting of Aron Stanić, Antonija Tadić, Želimir Gusak and myself rolled up our sleeves, presented our ideas and won the prize with the most creative solution for our fictitious client.

Once the training was over, we just had to try some of the famous French cuisine and visit the most popular tourist destinations such as Louvre and Orsay museums, palace of Versailles and climb on the top of the Eiffel tower.

 

This whole event helped us to feel like we’re on top of everything (not just Eiffel tower) and I was happy to be part of this event. It was a great opportunity to catch up with the latest Magento changes, accelerate our knowledge and learn directly from Magento people – such as Craig Peasley (Head of Product Marketing) and Bob Moore (Head of Magento BI, Founder of RJMetrics that’s behind this new tool). It was also a great opportunity to meet with other Magento partners like Smile (FR), Alpenite (IT), Uniway (BE), Youwe and Jaagers (NL) – just to name a few.

And this is where our Paris adventure ends. After finishing this course of training we’re back in the office and feel even more confident that we’ll bring the best (if not the most creative) solutions to our clients. So, if you’re looking for a Magento development partner feel free to contact us, and someone from our sales team (now officially Magento trained sales team :)) will get back to you ASAP.

How about you, have you ever attended Magento training and what are your experiences?

Featured photo credit goes to – Laura Kimball, Magento Inc.

The post Magento Enablement training in Paris – overview by Inchoo appeared first on Inchoo.

]]>
http://inchoo.net/magento-2/magento-enablement-training/feed/ 2
Build your 2017 Magento SEO strategy with these tips http://inchoo.net/online-marketing/build-2017-magento-seo-strategy-tips/ http://inchoo.net/online-marketing/build-2017-magento-seo-strategy-tips/#comments Thu, 09 Feb 2017 13:17:23 +0000 http://inchoo.net/?p=28784 As we determinedly move through 2017, we decided to look into things which we’re building upon. The old saying goes “If you don’t know history, you are doomed to repeat it.” so we’d like to avoid any mistakes possible by learning from the previous happenings. Here’s what happened in the SEO world in 2016. and what...

The post Build your 2017 Magento SEO strategy with these tips appeared first on Inchoo.

]]>
As we determinedly move through 2017, we decided to look into things which we’re building upon. The old saying goes “If you don’t know history, you are doomed to repeat it.” so we’d like to avoid any mistakes possible by learning from the previous happenings. Here’s what happened in the SEO world in 2016. and what you can do to make those trends work in your favour.

Tea Pisac is one of our consultants. She works as a part of the Inchoo Consulting Group (ICG) which handles various marketing channels for our clients (PPC, SEO, Quality Assurance and User Experience) in order to make their websites work better and their businesses earn more.

She has been with us for almost 2 years now and can openly talk about what has changed in the consulting world during that period. Here’s an interview with Tea which will guide you through 2016 trends and what they mean for your store in the upcoming year.

Tea, hi! How are you? How does it feel to leave 2016 and all of the trends we’ve seen throughout the year behind? Or, is that really the case, are we really leaving them?

Tea: Well, if we say that the main things/trends that happened during 2016 confirm the importance of mobile first approach, usability improvements and the importance of producing useful, top quality content, then no, we are definitively not leaving them.

Most of the discussions that were active in 2016. already started some time before and those concepts will continue to evolve and develop, we just need to take them seriously and start to adapt on those as fast as we can.

To give us some perspective, can you tell us what were the most important moments when it comes to SEO in 2016?

Tea: As you know, there is always some kind of fuss regarding different changes that were spotted by different SEO tools. But in case some changes appear, it is not always easy to match those with Google’s updates as they rarely confirm them. From the updates that were confirmed by Google during 2016., things that affected eCommerce sites are listed below:

  • Removing right column ads. It actually concerns paid results but also has an impact on organic results as those are now pushed further down the SERP.
  • Mobile friendly update. Another ranking boost for mobile searches (mobile friendly sites).
  • The Penguin 4.0. was moved into the core algorithm. The way it impacts sites changed as it started to devalue bad links instead of penalizing the whole sites. It also started to be real time which gave the chance to those affected by Penguin to recover faster as they don’t have to wait until the new update gets released.

Which of the ones you mentioned had the biggest impact on eCommerce and why?

Tea: Each of those probably had certain impact on eCommerce sites. It is really hard to tell which one had the biggest impact as each and every site is specific. Some sites were already mobile friendly and weren’t affected by mobile friendly update, some sites still aren’t responsive and had additional issues after the update. Some sites had active PPC campaigns and maybe compensated potentially lower number of organic clicks (that happened due to SERP layout change), some didn’t had any or didn’t optimized campaigns enough…

When it comes to our clients, we can say that we actually loved everything that the Penguin everflux brought.  Some of our clients noticed improvements as the update started to roll out. Once again it was proven that having a well managed SEO site pays off. What happened is that one of our biggest and dearest clients finally managed to make peace with Google in terms of ranking one specific fashion brand page (that they sell on their site).

What happened? Due to some spammy events that took place several years ago, before we started working with the aforementioned client, Google devaluated those few urls and didn’t wanted to give them back their old ranking, regardless of our efforts. After the last update, those url’s finally began to recover and gain several times higher position in SERP which naturally resulted in several times increase in clicks.

Do you believe those moments (for example, rolling out Penguin updates) will have a long term impact? What can we expect in 2017?

Tea: It is almost impossible to predict what exactly will happen in a certain period. We can only say those trends and directions where updates are pointing to will keep on happening and evolving during the future period.

Besides the new, future updates, here is a short list of some other SEO related things you should count on during 2017:

  • Voice search. Certain shifts in volume can be expected in favour of a more descriptive, “common language” queries compared with those queries that people use while typing them. For example: “the best rated smartphones by the millennials in 2017” vs “top smartphones 2017”;
  • While doing a keyword research, you should focus on the concept, not on keywords themselves. You probably stopped with pure keyword stuffing long, long time ago, but this is not enough. Google’s algorithms keeps on evolving, they become smarter every day and your keyword research and creating content tactics should evolve as well.
  • Bing is on the path to become more important search engine, especially as the voice search evolves and it is the search engine the top virtual assistants currently use.
  • Mobile first indexing which will change the way Google ranks your pages. The ranking signals will come from the mobile version of the page instead of desktop as it did until now. Your site has to be responsive and serve the same top quality content on mobile as it does on desktop.
  • Implementing structured data (especially on mobile) stays important as proper setup helps search engines to understand your content better. When it comes to Magento 2, it intended to help merchants with the implementation of microdata markup with out of the box solution but in order to be implemented correctly, there is still some custom work to be done.

In order to keep up with all those trends and help our client’s sites to get in shape (and stay in shape), a large number of people with different skills and expertise must be involved – from SEO experts, eCommerce consultants, designers, developers etc.

Besides taking care of the new Google updates and making sure that everything from technical perspective is correctly set, it’s crucial to make a continuous efforts in usability improvements. Google really cares about their users and their search results relevancy for those users. Therefore, they are constantly improving their algorithms in order to get better and more accurate understanding about how happy people are with those results that Google serves them. People that click on your organic results and their satisfaction with the things they find on your site are the most important ranking factor.

I’ve read a lot of articles about the trends we should look after in 2017. Some of them are about mobile-first indexing, some mention AMP’s, others mention dense content, machine learning and featured snippets – which of them you see as the most important one?

Tea: Each and every one of those trends should be taken care of in general but some of those are less applicable on Magento eCommerce sites than others. For example, AMP’s are something that isn’t too easy to implement on Magento stores at the moment and we should wait some time to see how this technology will evolve.

It is interesting to see that just by looking at your short list, we can see that all of those things are directly related to user experience and satisfaction, especially on mobile.

How any of these things relate to Magento 2 SEO? Well, at least to things which come out of the Magento 2 box. Could you give us a heads up on what to look out for when building a Magento 2 site?

Tea: Well, from technical SEO perspective it is actually the same thing like the recommendations for building any Magento site. When we look specifically at Magento 2 you can check for more info in my mate Toni’s blog post. Besides that, merchants have to make sure to keep on working on all of the things that were mentioned previously in this interview.

So, are you making sure your Magento 2 site is optimized properly?

If you’re not, let us know. We know Magento can be confusing sometimes and that’s why our team of consultants is here. We can help you out by creating a custom made report based on our SEO Audit (all with tips on what to improve to make your web shop work better and earn more!)

The post Build your 2017 Magento SEO strategy with these tips appeared first on Inchoo.

]]>
http://inchoo.net/online-marketing/build-2017-magento-seo-strategy-tips/feed/ 4
Session storage and influence on performance in large PHP applications http://inchoo.net/magento/programming-magento/session-storage-php/ http://inchoo.net/magento/programming-magento/session-storage-php/#respond Tue, 07 Feb 2017 12:32:30 +0000 http://inchoo.net/?p=28686 Session is something that PHP developers use in their everyday work. But how many of you did took some time to actually consider where are they stored and how does that impact your application? Should you even care? Does number of sessions influence performance of your application? Setting up the test for file-based sessions To test this,...

The post Session storage and influence on performance in large PHP applications appeared first on Inchoo.

]]>
Session is something that PHP developers use in their everyday work. But how many of you did took some time to actually consider where are they stored and how does that impact your application? Should you even care? Does number of sessions influence performance of your application?

Setting up the test for file-based sessions

To test this, lets create some code. Keep in mind that same apply to larger applications such us Magento as well, but for the purposes of this article, we will keep it simple:

<?php
 
// Start profiling
$start = microtime(true);
 
// Set header
header('Content-type: text/plain');
 
// Adjust session config
ini_set('session.save_path', __DIR__ . '/sessions/');
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 10);
ini_set('session.gc_maxlifetime', 3000);
 
// Init session
session_start();
 
// End profiling
$duration = (microtime(true) - $start);
 
// Output the data
echo number_format($duration, 12) . PHP_EOL;

As you can see from the code above, the script doesn’t actually do anything. It allows you to set session configuration (to allow easier testing) while the only operational code here is – starting a session.

Before we go any further, let’s revise what those session configuration settings actually mean (though, you should already know this):

  • save_path – This is self-explanatory. It is used to set location of session files. In this case this is on file system, but as you will se later, this is not always the case.
  • gc_probability – Together with gc_divisor setting, this config is used to determine probability of garbage collector being executed.
  • gc_divisor – Again, used to determine probability of garbage collector being executed. This is actually calculated as gc_probability/gc_divisor.
  • gc_maxlifetime – Determines how long should session be preserved. It doesn’t mean that it will be deleted after that time, just that it will be there for at least that long. Actuall lifetime depends on when the garbage collector will be triggered.

Results of file-based session storage

To complete the test, I have executed 1000 curl requests to my server and stored the result returned by our ‘profiler’. With that data, I have created histogram of the response time:

As you can see, nothing really happens, application is working well in all cases. On the other hand, these are only 1000 sessions on extremely simple script, so let’s generate better environment. For the purpose of the test, I have used 14 days. For the number of sessions per day, I have opened random Google Analytics account of one of my Magento project, and determined that we will use 40’000 session per day. Roughly saying, we need 550’000 sessions to begin testing, which were generated by executing same amount of CURL requests to the server.

So we are ready to check what happens. I have executed another 1000 curl requests, again logging the response and generating the histogram of results:

session stored in file with GB turned on

As you can see, the results are very different. We know have two kind of results – ones that are executed properly, and ones that have severe performance penalty (700% longer execution time than usual). So what happened here? Well, simply said – garbage collector was triggered.

Explaining the results

If you recall from the beginning of the post (or take a look at the source), we defined some probability of GC being triggered. This means that given the calculate probability, some requests (to say at random) will trigger GC. This doesn’t mean that actual sessions will be deleted, but all of them will be checked, and delete if required. And that exactly is the problem.

Looking back at the script, we have defined the probability of GC being triggered as 1/10, and that is exactly what is observed on image above – 900 out of 1000 executed properly, while the rest 100 which executed GC took significantly longer to process. To confirm our findings, we will adjust gc_probability to 0 and thus disable GC. Repeating the same test, we observe following:

session stored in file with GB turned off

Now, we have only one data set, and that is the first group from the graph before. Difference in execution time is minimal, and application runs evenly across all requests. One may say that this s the solution to the problem, but keep in mind that currently nothing is deleting those session from our storage.

And last thing to note here, that in the example above with th GC turned on, due to my settings, none of the session files were actually deleted. When I did trigger deletion of the files, it took about 27 seconds to clear 90% of the files. If this is going to occur on the production server, you would have nasty problems during those 27 seconds.

Setting up the test for redis-based sessions

Next, I have tried what happens if you put those sessions into Redis server, instead of keeping them in files. For that, we need, among other things, altered version of our script:

<?php
 
// Start profiling
$start = microtime(true);
 
// Set header
header('Content-type: text/plain');
 
// Adjust session config
ini_set('session.save_handler', 'redis');
ini_set('session.save_path',    'tcp://127.0.0.1:6379');
ini_set('session.gc_probability', 0);
ini_set('session.gc_divisor', 10);
ini_set('session.gc_maxlifetime', 30000);
 
// Init session
session_start();
 
// End profiling
$duration = (microtime(true) - $start);
 
// Output the data
echo number_format($duration, 12) . PHP_EOL;

There is a very small difference in code, we only told PHP to used redis-php module to store sessions into Redis server specified by IP. Since I know nothing will happen with lower number of sessions in storage, I went and regenerated those 550’000 session before executing any tests.

Results of redis-based session storage

With everything read, we can execute the tests, the same way we previously did:

Once completed, I have adjusted gb_probabilty gain, and repeated the test:

Explaining the results

Unlike the previous test with file-based sessions, there is not really a difference in performance here. And this is basically because Redis internally can deal with session lifetime, which means that PHP does not have to. In other words, GC settings related to session no longer have influence on application performance.

Conclusion

Looking back at two examples, you can clearly see the difference between two storage types. Even though file storage is somewhat faster for majority of requests, it becomes an issue with large number of files. Even though only smaller portion of session files is actually deleted, when GC is triggered, all files will be checked. You can overcome this by disabling GC, but keep in mind that in such case, you must setup your own GC to serve the same purpose (cron process that relays on file system time-stamps). Of course, you can better time it, and it is enough to execute it once per day to lower stress, but it needs to exists.

On the other hand, you can use Redis, which is somewhat slower. How much slower, it depends on your setup. In my case, Redis was running on the same machine and we can observe performance penalty of 1ms in average. If the setup is different and Redis is running on remote server, you can expect heavy impact on the performance (for an example, in case of poor connection between servers).

My recommendation would be to use files with custom GC when application is running on single web server. If you have Multi-node setup, than Redis will be better option for you, but keep an extra eye on speed of the linke between your servers.

The post Session storage and influence on performance in large PHP applications appeared first on Inchoo.

]]>
http://inchoo.net/magento/programming-magento/session-storage-php/feed/ 0
Snatch your Meet Magento Croatia ticket – while you still can! http://inchoo.net/life-at-inchoo/snatch-meet-magento-croatia-ticket-still-can/ http://inchoo.net/life-at-inchoo/snatch-meet-magento-croatia-ticket-still-can/#respond Thu, 02 Feb 2017 15:03:41 +0000 http://inchoo.net/?p=28740 Please update your WordPress asap. You don't wanna get hacked, do you?

The post Snatch your Meet Magento Croatia ticket – while you still can! appeared first on Inchoo.

]]>
We’re a bit more than a month away from the amazing Meet Magento Croatia experience in Osijek.

We are thrilled to welcome some of the most prominent Magento 2 community members, as well as various top notch eCommerce experts.

Here is the first real batch of information regarding the upcoming Meet Magento Croatia and why you should snatch up your ticket – if you haven’t already. 😉

Who’s going to be there?

Developers, merchants and eCommerce enthusiasts. Our delegates range from the skilled Magento sailors to those interested in testing the Magento waters. Some of our guests have been building their shops for years, others just decided to start that adventure off.

No matter the level of experience you have when it comes to dealing with Magento pickles, #MM17HR will be the perfect place for exploring the whereabouts of Magento possibilities and pioneering Magento 2 solutions!

So, who’s going to entertain the crowd?

Entertain is an understatement!

With the first batch of speakers looking like it does, it’s certain they will bring more than fun things to the table. Get ready to enjoy meeting some of the most amazing #RealMagento faces because they have you covered with everything from Magento 2 Frontend to Magento 2 testing.

Magento 2? Spot on!

Did we mention this is the perfect place to get your Magento 2 knowledge on point?

If we haven’t, it is! You’ve seen the roster of the amazing speakers who already booked their place on the MM17HR stage – how can you expect anything less than hot Magento 2 topics?

After all, DevParadise set the bar high when it comes to meeting expectations regarding organizing Magento conferences. It’s only fair to match that standard this time around too!

So, if you’re reading this and thinking: “Man, I’m glad I have my ticket!” – good for you! We’re looking forward to seeing you in Osijek.

If, on the other hand, you’re one of those people who still need some convincing, we’ll let you be convinced by the experiences of people who attended our last event.

Talesh Seeparsan: “Magento events in Croatia always seem to have a buzz of excitement around them. It’s one of those places where gathering of brilliant Magento minds seems almost natural. I’m looking forward to attending in 2017!”

Olena Sadoma (Atwix): “Don’t go alone. Come with a team. Spend time together, learn new stuff, meet new people. Inspired by speakers, make an action plan. This is the best you can do for your employees and your company.”

Dmitrijs Sitovs (Scandiweb): “Croatia is definitely one of the best countries for such events – good weather, beautiful places and great possibility to exchange your experience with other Magento colleagues. What else is needed? :D”

And don’t forget to snatch up your ticket, it still goes by the “save-some money” price! 😉

The post Snatch your Meet Magento Croatia ticket – while you still can! appeared first on Inchoo.

]]>
http://inchoo.net/life-at-inchoo/snatch-meet-magento-croatia-ticket-still-can/feed/ 0
Installing OroCommerce B2B Application http://inchoo.net/ecommerce/installing-orocommerce-b2b-application/ http://inchoo.net/ecommerce/installing-orocommerce-b2b-application/#respond Tue, 31 Jan 2017 12:43:47 +0000 http://inchoo.net/?p=28661 OroCommerce application version 1.0 has been released last month with a premise that it will „disrupt B2B Online Commerce“ and „change the world of B2B eCommerce“. With the noticeable rise of Business-To-Business eCommerce in the recent years, it was only a matter of time when the supporting B2B applications and platforms would show up and...

The post Installing OroCommerce B2B Application appeared first on Inchoo.

]]>
OroCommerce application version 1.0 has been released last month with a premise that it will „disrupt B2B Online Commerce and „change the world of B2B eCommerce“. With the noticeable rise of Business-To-Business eCommerce in the recent years, it was only a matter of time when the supporting B2B applications and platforms would show up and we at Inchoo are excited to see what OroCommerce brings to the table. We will be taking a look at the installation process and some useful tips on getting started with recently fully-released OroCommerce B2B platform.

Installing OroCommerce on localhost

In this article, I am going to cover general steps when installing OroCommerce on a local development machine. I have successfully installed OroCommerce on both AMPPS and XAMPP local development environments on MacOS and Windows respectively and I will be listing some common general errors that you could encounter and how to fix them.

Step 1: Preparation – Domain and Config

AMPPS – integrated domain creation tool

If you are using AMPPS, you will need to create a new domain using AMPPS Add Domain tool, let’s call it orocommerce.loc and make it point to the web subfolder (orocommerce.loc/web). Also, make sure to check the options “Add an SSL entry” and Add an entry to hosts file”.

XAMPP – manual domain creation

If you are using XAMPP, add the following line to your hosts file:

127.0.0.1 orocommerce.loc

After that, we are going to set up a domain for XAMPP. Open the following file:

xampp\apache\conf\extra\httpd-vhosts.conf

Add the following configuration: 

<VirtualHost *:80>
DocumentRoot "C:/xampp/htdocs/orocommerce.loc/web"
ServerName orocommerce.loc
ServerAlias www.orocommerce.loc
<Directory "C:/xampp/htdocs/orocommerce.loc/web">
AllowOverride All
Require all Granted
</Directory>
</VirtualHost>

Change the config to fit your path and domain. Save the config and restart Apache. Now you will be able to use orocommerce.loc domain on your local machine.

General preparation

OroCommerce requires PHP 5.6 (or above) and the following configuration in your php.ini file:

  • memory_limit=512M
    • You can also set it to 1024M or higher for better performance
  • date.timezone=UTC

Following PHP extensions are required:

  • ctype
  • fileinfo
  • GD 2.0 and above
  • intl
  • JSON
  • Mcrypt
  • PCRE 8.0 and above
  • SimpleXML
  • Tokenizer

These are the settings you’d have to adjust if you are setting up OroCommerce on your local dev machine for the first time. For the full list of requirements, see OroCommerce System Requirements.

This guide relies on Composer for installation. You can install OroCommerce without Composer and download a full installed package, but you can run into some issues if your system is missing some dependencies. Composer ensures that all necessary dependencies have been installed on your system.

Step 2: Cloning a Git repo and Composer Installation

Before cloning OroCommerce git repo and installing OroCommerce, you will need to setup your database. On a local machine, you can either create a user with an assigned database or use the default root access.

For AMPPS, default username is “root” and password is “mysql” and for XAMPP the username is “root” and password is empty.

After that, open a command line interface and navigate to the localhost folder. In AMPPS that is a www folder, and in XAMPP that is a htdocs folder. If you have used AMPPS Add Domain tool, you will see OroCommerce domain folder (orocommerce.loc) folder. If you are using XAMPP, you will have to create this folder manually. Open the folder in your command line interface and run the following command:

git clone https://github.com/orocommerce/orocommerce-application.git .

This will clone the git repository to the current folder. If there are issues with this command (“folder not empty”), you can run the following command without the dot at the end and copy the content of the orocommerce-application folder to the orocommerce.loc folder and delete the orocommerce-application folder after that.

If you are preferring not to use git, you can download the repository as a .zip file from the git repository and extract the content to your domain folder.

In any case, your domain folder should look like this before installation.

Step 3: Composer Installation

After the contents of the repository have been copied or cloned to the localhost domain folder, you can initiate composer installation with the following command in your command line interface:

composer install --prefer-dist

210 repositories to be installed, this can take a bit longer if you are installing OroCommerce for the first time. With “–prefer-dist” option, this method is faster than regular “composer install” command.  

Composer will then create parameters.yml file and you will be prompted to input database credentials and some other data that can be safely ignored for now. I have used XAMPP’s default “root” username and no password.

Step 4: OroCommerce Installation

After Composer has successfully finished installing the necessary items, you can run the following command inside your command line interface to install OroCommerce:

php app/console oro:install --env=prod

OroCommerce will run the check for the requirements which we have covered in the “Step 1 – Prepararation” section of this article. There are several warnings that you can optionally ignore, mostly related to the PHP accelerator, COM extension and Tidy extension.

If OroCommerce gives you an error during this check, the installation will be canceled until all requirements have been met (except ones marked with “Warning”). You will need to run “php app/console oro:install –env=prod” command again after you have made the necessary adjustments.

If all requirements have been met, OroCommerce will proceed with installing the database. If you get an error during this step, there might be an error with database credentials which you provided during the Composer installation. It is recommended either to start over with a new database or check the following config file:

\app\config\parameters.yml

After the database setup, you will be prompted to enter the admin information, application URL, Organization name and you will be given an option to load sample data (products, categories, CMS, etc.).

This will initiate the final step of the installation where sample data will be installed (if user enabled it), full reindex will occur, translations and assets will be installed and dumped and cache will be cleared.

Please note that the cache cleanup can take a while (whole eternity) when clearing cache during installation or when manually clearing cache during the development. Hopefully, cache clearing time will be improved in the upcoming versions of OroCommerce.

After the installation has been finished, you can open the localhost domain on your browser (in our case, that is orocommerce.loc) and you will be greeted with the OroCommerce demo homepage. Make sure to also check out if the admin is working as expected and that you can log into admin with your set credentials.

OroCommerce command console

You can access the OroCommerce console commands by running the following command in your command line interface while in the OroCommerce installation folder (this may take a while to execute for the first time):

php -f app/console

This will list all available commands. If you need information or help with the commands, you can run the following command:

php -f app/console help command-name

Fixes for common installation issues

Multiple PHP installs issue – specific to MacOS

OroCommerce has a PHP path hard-coded in its installation which can cause issues in some specific cases. This fix applies to the following or similar issues with PHP during the OroCommerce installation step:

  • An issue related to PHP where PHP 5.5 is being detected (5.6 is minimum required) even though higher version of PHP is enabled in AMPPS or other similar software
  • Multiple versions of PHP are installed
  • Missing PHP extensions (intl, for example) even though it is enabled in AMPPS or other similar software

You need to edit your httpd-vhosts.conf file. For AMPPS, that file is located in:

AMPPS/apache/conf/extra/httpd-vhosts.conf

Add the following line after ServerAlias for both VirtualHost and SSL entries for your OroCommerce domain (orocommerce.loc)

SetEnv ORO_PHP_PATH /Applications/AMPPS/php/bin/php

To get a correct path to PHP of your local development environment, you can run a following command from your command line interface: 

which php

Please note that you may need to re-add the SetEnv line each time a new domain has been created if the httpd-vhosts.conf file is refreshed each time new domain has been added. 

Generating Github token for Composer

If you don’t have a Github token added to your Composer installation, you will be prompted to add it during the OroCommerce installation due to the Github’s rate limit. In order to add the token, you will have to:

  • Log into your GitHub account and open Settings
  • Click on Personal Access Tokens in Settings sidebar
  • Click on Generate new token button
  • Add a token description and select public_repo scope under repo and leave other fields unchecked
  • Click on Generate Token button and copy the token
  • Copy the token and paste it into command line interface when prompted

Enabling PHP commands in Windows Command Prompt

In order to enable PHP command in your Windows Command Prompt, you will need to add a new Environment variable:

  • Right click on My Computer and click Properties
  • Under Advanced tab click on Environment Variables
  • Under System Variables, find Path and click Edit button
  • Edit Variable window will open and you need to add a path to php.exe by clicking on New button. For XAMPP default installation, the path is “C:\xampp\php”
  • Save the settings and close the windows

You can test if the path has been added correctly by running the following command in Windows Command Prompt:

php -v

Had an error which isn’t mentioned in this article?

Search through posts or write a post on OroCommerce forums. It is quite active and both users and OroCommerce devs are offering assistance.

I hope you enjoyed reading this article and that it was helpful. In case you need any extra help, get back to us – we would be happy to help you out with setting your store!

Thank you for reading and happy coding.

The post Installing OroCommerce B2B Application appeared first on Inchoo.

]]>
http://inchoo.net/ecommerce/installing-orocommerce-b2b-application/feed/ 0
Here’s how our learning process helped us prepare for Magento 2! http://inchoo.net/life-at-inchoo/learning-process-magento-2/ http://inchoo.net/life-at-inchoo/learning-process-magento-2/#comments Thu, 26 Jan 2017 13:10:55 +0000 http://inchoo.net/?p=28636 You probably always wondered why those guys from Inchoo are so awesome and how come they always seem to know what are they talking about! 😉 Well, we research, talk about it, ask questions and write blog posts. Let’s take hot Magento 2 topic as an example… When it comes to Magento 2, early adopters from our company...

The post Here’s how our learning process helped us prepare for Magento 2! appeared first on Inchoo.

]]>
You probably always wondered why those guys from Inchoo are so awesome and how come they always seem to know what are they talking about! 😉 Well, we research, talk about it, ask questions and write blog posts. Let’s take hot Magento 2 topic as an example…

When it comes to Magento 2, early adopters from our company started with their blog posts back in late 2014, e.g. Filip Svetličić wrote about Magento 2 frontend architecture in September 2014, Marko Martinović had detailed review of Magento 2 caching features as a conference speaker at Meet Magento Poland in November, Stjepan Udovičić wrote about basics of dependency injection and its usage in Magento 2 in December and Domagoj Potkoč covered Magento 2 logging in January 2015.

Back in mid 2015 the focus was on migration planning, so in August Ivan Weiler wrote about testing Magento 2 Data Migration Tool, and Ivona Namjesnik addressed merchants with Talking Merchants: Migrating to Magento 2 blog post.

At the same time, our early adopters prepared an in-house Magento 2 workshop for backend developers, all in favor of sharing findings and thoughts with all of them. Through that entire learning process we created our own learning materials, and we used them together with the MagentoU official educational materials while training our teams for the Magento 2.

And when I talk about in-house trainings and say “we”, it usually means Ivan Weiler, our guy for in-house education. He works with our newcomers to help them with smoother integration in the company and technologies we work with, but also helps our teams when he believes its needed. Educations come in two forms – separate trainings (for Frontend developers, Backend developers, certain team) or a lecture/workshop for the whole company.

After Magento officially scheduled the date of Magento 2 release for the November of 2015, and we had Developers Paradise preparations going on for April 2016, we decided that we’ll turn it into a Magento 2 conference! There was enough time between the release and the conference for the first impressions, so we knew that a well-chosen topic should (and will) provoke quality discussions, especially because we had few guys from the core team as speakers!

We like to mix learning methods – here’s which ones we love!

In reality, of course, blog posts will get you so far. While 2016 was all about various methods of learning (such as organizing conferences and a whole lot of in-house educations), we love seeing our developers taking it all a step farther. Well researched topic can be a great blog post, but also an amazing conference topic and can get you some points on your Magento certification exam!

So, I feel I should mention the following two things which help Inchooers learn and evolve!

Certifications are important step in our learning curve. MCD+, MCFD, MCSS… We tend to send every employee to take a Magento certificate (or more of them because hey, the more, the merrier!)! Even our designers and marketing & sales guys have a confirmation of knowing how to handle your Magento store. It’s important for understanding of our core business and helps with communication on the projects.

Conference speaking is also encouraged, so if you like to talk about what you do and how you do it, or you’ve never done it but you would like to try – just find a conference and apply a topic! It’s a great experience for a personal and professional growth, and a good value for the company itself.

All this steps are quite important to us. Although there is no strict and linear learning process, but a combination of all things written above, you can say that we have a modular learning process, kind of agile approach that suits us better.

The final stage of educational process is, of course, working on the very Magento 2 projects, so I left some impressions for the end…

Hrvoje Ivančić: I really like how the Magento 2 file structure is organized compared to previous version. However, working on the M2 project requires a huge effort and a good knowledge because the technology stack is modernized and several new technologies are embedded into it. This can be overwhelming for a developer.

Ivan Miškić: Magento 2 development experience was very different compared to Magento 1. However, it was surprisingly easy to quickly get into all the new things that it brings, like dependency injection, factories and many more. Despite many troubles earlier during the installation, latest versions are much better and work without any issues. Events, observers and plugins make it easy to create changes throughout all core functionalities. On the other hand, some parts seem to require too much effort for not much gain, for example admin development. Magento 2 definitely does things better than Magento 1, but it still need some core framework improvements and has a lot of bugs to fix in core features.

Now, I know Magento 2 can be confusing sometimes. Good learning process will definitely make it easier for you but in case you still need a helping hand of Magento 2 enthusiasts, feel free to get in touch, we’d like to help!

The post Here’s how our learning process helped us prepare for Magento 2! appeared first on Inchoo.

]]>
http://inchoo.net/life-at-inchoo/learning-process-magento-2/feed/ 2
Sass Output Styles http://inchoo.net/dev-talk/tools/sass-output-styles/ http://inchoo.net/dev-talk/tools/sass-output-styles/#respond Tue, 24 Jan 2017 12:23:15 +0000 http://inchoo.net/?p=28622 Sass has four different CSS output style. By changing setting for :style option or using the --style command-line flag. I will show you how Sass outputs this piece of SCSS code in all style options with style option explanation. main { padding: 12px 24px; margin-bottom: 24px; }   article { background-color: #00ff00; color: red; border:...

The post Sass Output Styles appeared first on Inchoo.

]]>
Sass has four different CSS output style. By changing setting for :style option or using the --style command-line flag.

I will show you how Sass outputs this piece of SCSS code in all style options with style option explanation.

main {
 padding: 12px 24px;
 margin-bottom: 24px;
}
 
article  {
 background-color: #00ff00;
 color: red;
 border: 1px solid blue;
 
 p {
   font-size: 18px;
   font-style: italic;
   margin-bottom: 12px;
 }
}

:nested

Nested style is the default Sass style because it reflects the structure of the CSS styles in which each property has its own line, but the indentation is based on how deeply it’s nested. Example bellow:

main {
  padding: 12px 24px;
  margin-bottom: 24px; }
 
article {
  background-color: #00ff00;
  color: red;
  border: 1px solid blue; }
  article p {
    font-size: 18px;
    font-style: italic;
    margin-bottom: 12px; }

:expanded

In expanded style properties are indented within the rules, but the rules aren’t indendented in any special way like in :nested output style. Example bellow:

main {
  padding: 12px 24px;
  margin-bottom: 24px;
}
 
article {
  background-color: #00ff00;
  color: red;
  border: 1px solid blue;
}
article p {
  font-size: 18px;
  font-style: italic;
  margin-bottom: 12px;
}

:compact

In compact style each rule takes up only one line with every property defined on that line. It takes up less space than :nested and :expanded. Nested rules are placed next to each other with no newline, while separate groups of rules have newlines between them. Example bellow:

main { padding: 12px 24px; margin-bottom: 24px; }
 
article { background-color: #00ff00; color: red; border: 1px solid blue; }
article p { font-size: 18px; font-style: italic; margin-bottom: 12px; }

:compressed

Compressed styles takes up the minimum amount of space possible. There is no whitespace except space that is necessary to separate selectors and the newline on the end of the document. It is not meant to be human-readable but more for the production version . Also, it has some minor compression such as choosing the smaller representation for colours. Example bellow:

main{padding:12px 24px;margin-bottom:24px}article{background-color:#00ff00;color:red;border:1px solid blue}article p{font-size:18px;font-style:italic;margin-bottom:12px}

How to set output style

Output style can be set in different ways depending how you compile Sass files. If you are using GUI tools like CodeKit or LiveReload, they have option to set the Sass output style. You can set output style via command line by passing the --style flag in the setting, like this:

sass --watch style.scss:style.css --style compressed

 If you are using Gulp or Grunt task runners with the Sass package than you can pass style option to the Sass config, like this:

gulp.task('sass', function () {
    gulp.src('scss/style.scss')
        .pipe(sass(outputStyle: 'compressed'))
        .pipe(gulp.dest('css'))
});

Thanks for reading!

The post Sass Output Styles appeared first on Inchoo.

]]>
http://inchoo.net/dev-talk/tools/sass-output-styles/feed/ 0
Implementing javascript minifier http://inchoo.net/magento/programming-magento/implementing-javascript-minifier/ http://inchoo.net/magento/programming-magento/implementing-javascript-minifier/#comments Tue, 17 Jan 2017 13:08:47 +0000 http://inchoo.net/?p=27989 Implementing javascript minimization in Magento can help your page load time by compressing your javascript files and making them smaller for users to download. Along with CSS minimisation it can be a great asset for decreasing page loading time. In this article I will primary cover where minimization should be implemented, since writing a full code for...

The post Implementing javascript minifier appeared first on Inchoo.

]]>
Implementing javascript minimization in Magento can help your page load time by compressing your javascript files and making them smaller for users to download. Along with CSS minimisation it can be a great asset for decreasing page loading time.

In this article I will primary cover where minimization should be implemented, since writing a full code for dependable minifier is not a small task.

Although any custom php minimization code for javascript can be used, I will use slightly adjusted version of open source php minifier I tested and found really good written. Changes are done mainly to adapt it to Magento best practices, functionality remains the same. Open source PHP Javascript Minifier link.

First step in implementing this should be simple config switch in order to be able to turn it off for debugging purposes (place this is system.xml):

<?xml version="1.0"?>
<config>
    <sections>
        <dev>
            <groups>
                <js>
                    <fields>
                        <minimize_js translate="label">
                            <label>Minimize JavaScript Files</label>
                            <frontend_type>select</frontend_type>
                            <source_model>adminhtml/system_config_source_yesno</source_model>
                            <sort_order>20</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>1</show_in_store>
                        </minimize_js>
                    </fields>
                </js>
            </groups>
        </dev>
    </sections>
</config>

Set the default value in config.xml by adding:

<default>
        <dev>
            <js>
                <minimize_js>0</minimize_js>
            </js>
        </dev>
</default>

Add declaration of core data helper rewrite also in your config.xml :

<helpers>
        <core>
             <rewrite>
                     <data>Inchoo_Minifier_Helper_Core_Data</data>
             </rewrite>
        </core>
</helpers>

Adjust the rewritten core helper code where Magento merges javascript, so we also minify it after merging (changes are marked between “INCHOO EDIT” in comments, and function minimizeJs is added, which serves as main handler of minifier code we’re about to add):

class Inchoo_Minifier_Helper_Core_Data extends Mage_Core_Helper_Data
{
    /**
     * INCHOO EDIT - added call for JS minimizing
     * @param array $srcFiles
     * @param bool $targetFile
     * @param bool $mustMerge
     * @param null $beforeMergeCallback
     * @param array $extensionsFilter
     * @return bool|string
     */
    public function mergeFiles(array $srcFiles, $targetFile = false, $mustMerge = false,
                               $beforeMergeCallback = null, $extensionsFilter = array())
    {
        try {
            // check whether merger is required
            $shouldMerge = $mustMerge || !$targetFile;
            if (!$shouldMerge) {
                if (!file_exists($targetFile)) {
                    $shouldMerge = true;
                } else {
                    $targetMtime = filemtime($targetFile);
                    foreach ($srcFiles as $file) {
                        if (!file_exists($file) || @filemtime($file) > $targetMtime) {
                            $shouldMerge = true;
                            break;
                        }
                    }
                }
            }
 
            // merge contents into the file
            if ($shouldMerge) {
                if ($targetFile && !is_writeable(dirname($targetFile))) {
                    // no translation intentionally
                    throw new Exception(sprintf('Path %s is not writeable.', dirname($targetFile)));
                }
 
                // filter by extensions
                if ($extensionsFilter) {
                    if (!is_array($extensionsFilter)) {
                        $extensionsFilter = array($extensionsFilter);
                    }
                    if (!empty($srcFiles)){
                        foreach ($srcFiles as $key => $file) {
                            $fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION));
                            if (!in_array($fileExt, $extensionsFilter)) {
                                unset($srcFiles[$key]);
                            }
                        }
                    }
                }
                if (empty($srcFiles)) {
                    // no translation intentionally
                    throw new Exception('No files to compile.');
                }
 
                $data = '';
                foreach ($srcFiles as $file) {
                    if (!file_exists($file)) {
                        continue;
                    }
                    $contents = file_get_contents($file) . "\n";
                    if ($beforeMergeCallback && is_callable($beforeMergeCallback)) {
                        $contents = call_user_func($beforeMergeCallback, $file, $contents);
                    }
                    $data .= $contents;
                }
                if (!$data) {
                    // no translation intentionally
                    throw new Exception(sprintf("No content found in files:\n%s", implode("\n", $srcFiles)));
                }
                if ($targetFile) {
                    /** INCHOO EDIT START **/
                    if(isset($file)){
                        // Minimize only .js files
                        $fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION));
                        if(Mage::getStoreConfigFlag('dev/js/minimize_js') && $fileExt === 'js'){
                            $data = $this->minimizeJs($data);
                        }
                    }
                    /** INCHOO EDIT END **/
                    file_put_contents($targetFile, $data, LOCK_EX);
                } else {
                    return $data; // no need to write to file, just return data
                }
            }
 
            return true; // no need in merger or merged into file successfully
        } catch (Exception $e) {
            Mage::logException($e);
        }
        return false;
    }
 
    /**
     * INCHOO - main JS minimizer function
     * @param $data
     * @return bool|string
     */
    public function minimizeJs($data)
    {
        $minifer = Mage::helper('inchoo_minifier/minifier');
        $result = $minifer->minify($data);
 
        if($result !== false) {
            return $result;
        }
        return $data;
    }
}

Lastly, minifier code we will use to compress our merged javascript files:

<?php
/**
 * Copyright (c) 2009, Robert Hafner
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Stash Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Robert Hafner BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Magento port adjustment by Tomislav Nikčevski, 2016
 * JS Minifier
 * Usage - $this->minify($js);
 */
 
class Inchoo_Minifier_Helper_Minifier
{
    /**
     * The input javascript to be minified.
     *
     * @var string
     */
    protected $_input;
 
    /**
     * Output javascript buffer - this will be returned
     * @var
     */
    protected $_text;
 
    /**
     * The location of the character (in the input string) that is next to be
     * processed.
     *
     * @var int
     */
    protected $_currentPosition = 0;
 
    /**
     * The first of the characters currently being looked at.
     *
     * @var string
     */
    protected $_firstChar = '';
 
    /**
     * The next character being looked at (after firstChar);
     *
     * @var string
     */
    protected $_secondChar = '';
 
    /**
     * This character is only active when certain look ahead actions take place.
     *
     *  @var string
     */
    protected $_endChar;
 
    /**
     * Contains lock ids which are used to replace certain code patterns and
     * prevent them from being minified
     *
     * @var array
     */
    protected $locks = array();
 
    /**
     * Takes a string containing javascript and removes unneeded characters in
     * order to shrink the code without altering it's functionality.
     *
     * @param  string      $js      The raw javascript to be minified
     * @return bool|string
     */
    public function minify($js)
    {
        try {
            $js = $this->lock($js);
 
            $this->initialize($js);
            $this->loop();
            $js = $this->unlock(ltrim($this->_text));
            $this->clean();
 
            return $js;
 
        } catch (Exception $e) {
            $this->clean();
            Mage::log('Minifier failed. Error: ' . $e->getMessage());
            return false;
        }
    }
 
    /**
     *  Initializes internal variables, normalizes new lines,
     *
     * @param string $js      The raw javascript to be minified
     */
    protected function initialize($js)
    {
        $js = str_replace("\r\n", "\n", $js);
        $js = str_replace('/**/', '', $js);
        $this->_input = str_replace("\r", "\n", $js);
 
        // We add a newline to the end of the script to make it easier to deal
        // with comments at the bottom of the script- this prevents the unclosed
        // comment error that can otherwise occur.
        $this->_input .= PHP_EOL;
 
        // Populate "firstChar" with a new line, "secondChar" with the first character, before
        // entering the loop
        $this->_firstChar = "\n";
        $this->_secondChar = $this->getReal();
    }
 
    /**
     * The primary action occurs here. This function loops through the input string,
     * outputting anything that's relevant and discarding anything that is not.
     */
    protected function loop()
    {
        while ($this->_firstChar !== false && !is_null($this->_firstChar) && $this->_firstChar !== '') {
 
            switch ($this->_firstChar) {
                // new lines
                case "\n":
                    // if the next line is something that can't stand alone preserve the newline
                    if (strpos('(-+{[@', $this->_secondChar) !== false) {
                        $this->_text .= $this->_firstChar;
                        $this->saveString();
                        break;
                    }
 
                    // if secondChar is a space we skip the rest of the switch block and go down to the
                    // string/regex check below, resetting secondChar with getReal
                    if($this->_secondChar === ' ')
                        break;
 
                // otherwise we treat the newline like a space
 
                case ' ':
                    if(static::isAlphaNumeric($this->_secondChar))
                        $this->_text .= $this->_firstChar;
 
                    $this->saveString();
                    break;
 
                default:
                    switch ($this->_secondChar) {
                        case "\n":
                            if (strpos('}])+-"\'', $this->_firstChar) !== false) {
                                $this->_text .= $this->_firstChar;
                                $this->saveString();
                                break;
                            } else {
                                if (static::isAlphaNumeric($this->_firstChar)) {
                                    $this->_text .= $this->_firstChar;
                                    $this->saveString();
                                }
                            }
                            break;
 
                        case ' ':
                            if(!static::isAlphaNumeric($this->_firstChar))
                                break;
 
                        default:
                            // check for some regex that breaks stuff
                            if ($this->_firstChar === '/' && ($this->_secondChar === '\'' || $this->_secondChar === '"')) {
                                $this->saveRegex();
                                continue;
                            }
 
                            $this->_text .= $this->_firstChar;
                            $this->saveString();
                            break;
                    }
            }
 
            // do reg check of doom
            $this->_secondChar = $this->getReal();
 
            if(($this->_secondChar == '/' && strpos('(,=:[!&|?', $this->_firstChar) !== false))
                $this->saveRegex();
        }
    }
 
    /**
     * Resets attributes that do not need to be stored between requests so that
     * the next request is ready to go. Another reason for this is to make sure
     * the variables are cleared and are not taking up memory.
     */
    protected function clean()
    {
        unset($this->_input);
        $this->_currentPosition = 0;
        $this->_firstChar = $this->_secondChar = $this->_text = '';
        unset($this->_endChar);
    }
 
    /**
     * Returns the next string for processing based off of the current index.
     *
     * @return string
     */
    protected function getChar()
    {
        // Check to see if we had anything in the look ahead buffer and use that.
        if (isset($this->_endChar)) {
            $char = $this->_endChar;
            unset($this->_endChar);
 
            // Otherwise we start pulling from the input.
        } else {
            $char = substr($this->_input, $this->_currentPosition, 1);
 
            // If the next character doesn't exist return false.
            if (isset($char) && $char === false) {
                return false;
            }
 
            // Otherwise increment the pointer and use this char.
            $this->_currentPosition++;
        }
 
        // Normalize all whitespace except for the newline character into a
        // standard space.
        if($char !== "\n" && ord($char) < 32)
 
            return ' ';
 
        return $char;
    }
 
    /**
     * This function gets the next "real" character. It is essentially a wrapper
     * around the getChar function that skips comments. This has significant
     * performance benefits as the skipping is done using native functions (ie,
     * c code) rather than in script php.
     *
     *
     * @return string            Next 'real' character to be processed.
     */
    protected function getReal()
    {
        $startIndex = $this->_currentPosition;
        $char = $this->getChar();
 
        // Check to see if we're potentially in a comment
        if ($char !== '/') {
            return $char;
        }
 
        $this->_endChar = $this->getChar();
 
        if ($this->_endChar === '/') {
            return $this->processOneLineComments($startIndex);
 
        } elseif ($this->_endChar === '*') {
            return $this->processMultiLineComments($startIndex);
        }
 
        return $char;
    }
 
    /**
     * Removed one line comments, with the exception of some very specific types of
     * conditional comments.
     *
     * @param  int    $startIndex The index point where "getReal" function started
     * @return string
     */
    protected function processOneLineComments($startIndex)
    {
        $thirdCommentString = substr($this->_input, $this->_currentPosition, 1);
 
        // kill rest of line
        $this->getNext("\n");
 
        if ($thirdCommentString == '@') {
            $endPoint = $this->_currentPosition - $startIndex;
            unset($this->_endChar);
            $char = "\n" . substr($this->_input, $startIndex, $endPoint);
        } else {
            // first one is contents of $this->_endChar
            $this->getChar();
            $char = $this->getChar();
        }
 
        return $char;
    }
 
    /**
     * Skips multiline comments where appropriate, and includes them where needed.
     * Conditional comments and "license" style blocks are preserved.
     *
     * @param  int               $startIndex The index point where "getReal" function started
     * @return bool|string       False if there's no character
     * @throws \RuntimeException Unclosed comments will throw an error
     */
    protected function processMultiLineComments($startIndex)
    {
        $this->getChar(); // current endChar
        $thirdCommentString = $this->getChar();
 
        // kill everything up to the next */ if it's there
        if ($this->getNext('*/')) {
 
            $this->getChar(); // get *
            $this->getChar(); // get /
            $char = $this->getChar(); // get next real character
 
            // Now we reinsert conditional comments and YUI-style licensing comments
            if ($thirdCommentString === '!' || $thirdCommentString === '@') {
 
                // If conditional comments or flagged comments are not the first thing in the script
                // we need to append firstChar to text and fill it with a space before moving on.
                if ($startIndex > 0) {
                    $this->_text .= $this->_firstChar;
                    $this->_firstChar = " ";
 
                    // If the comment started on a new line we let it stay on the new line
                    if ($this->_input[($startIndex - 1)] === "\n") {
                        $this->_text .= "\n";
                    }
                }
 
                $endPoint = ($this->_currentPosition - 1) - $startIndex;
                $this->_text .= substr($this->_input, $startIndex, $endPoint);
 
                return $char;
            }
 
        } else {
            $char = false;
        }
 
        if($char === false)
            throw new \RuntimeException('Unclosed multiline comment at position: ' . ($this->_currentPosition - 2));
 
        // if we're here endChar is part of the comment and therefore tossed
        if(isset($this->_endChar))
            unset($this->_endChar);
 
        return $char;
    }
 
    /**
     * Pushes the index ahead to the next instance of the supplied string. If it
     * is found the first character of the string is returned and the index is set
     * to it's position.
     *
     * @param  string       $string
     * @return string|false Returns the first character of the string or false.
     */
    protected function getNext($string)
    {
        // Find the next occurrence of "string" after the current position.
        $pos = strpos($this->_input, $string, $this->_currentPosition);
 
        // If it's not there return false.
        if($pos === false)
 
            return false;
 
        // Adjust position of index to jump ahead to the asked for string
        $this->_currentPosition = $pos;
 
        // Return the first character of that string.
        return substr($this->_input, $this->_currentPosition, 1);
    }
 
    /**
     * When a javascript string is detected this function crawls for the end of
     * it and saves the whole string.
     *
     * @throws \RuntimeException Unclosed strings will throw an error
     */
    protected function saveString()
    {
        $startpos = $this->_currentPosition;
 
        // saveString is always called after a gets cleared, so we push secondChar into
        // that spot.
        $this->_firstChar = $this->_secondChar;
 
        // If this isn't a string we don't need to do anything.
        if ($this->_firstChar !== "'" && $this->_firstChar !== '"') {
            return;
        }
 
        // String type is the quote used, " or '
        $stringType = $this->_firstChar;
 
        // append out that starting quote
        $this->_text .= $this->_firstChar;
 
        // Loop until the string is done
        while (true) {
 
            // Grab the very next character and load it into firstChar
            $this->_firstChar = $this->getChar();
 
            switch ($this->_firstChar) {
 
                // If the string opener (single or double quote) is used
                // output it and break out of the while loop-
                // The string is finished!
                case $stringType:
                    break 2;
 
                // New lines in strings without line delimiters are bad- actual
                // new lines will be represented by the string \n and not the actual
                // character, so those will be treated just fine using the switch
                // block below.
                case "\n":
                    throw new \RuntimeException('Unclosed string at position: ' . $startpos );
                    break;
 
                // Escaped characters get picked up here. If it's an escaped new line it's not really needed
                case '\\':
 
                    // firstChar is a slash. We want to keep it, and the next character,
                    // unless it's a new line. New lines as actual strings will be
                    // preserved, but escaped new lines should be reduced.
                    $this->_secondChar = $this->getChar();
 
                    // If secondChar is a new line we discard firstChar and secondChar and restart the loop.
                    if ($this->_secondChar === "\n") {
                        break;
                    }
 
                    // append the escaped character and restart the loop.
                    $this->_text .= $this->_firstChar . $this->_secondChar;
                    break;
 
                // Since we're not dealing with any special cases we simply
                // output the character and continue our loop.
                default:
                    $this->_text .= $this->_firstChar;
            }
        }
    }
 
    /**
     * When a regular expression is detected this function crawls for the end of
     * it and saves the whole regex.
     *
     * @throws \RuntimeException Unclosed regex will throw an error
     */
    protected function saveRegex()
    {
        $this->_text .= $this->_firstChar . $this->_secondChar;
 
        while (($this->_firstChar = $this->getChar()) !== false) {
            if($this->_firstChar === '/')
                break;
 
            if ($this->_firstChar === '\\') {
                $this->_text .= $this->_firstChar;
                $this->_firstChar = $this->getChar();
            }
 
            if($this->_firstChar === "\n")
                throw new \RuntimeException('Unclosed regex pattern at position: ' . $this->_currentPosition);
 
            $this->_text .= $this->_firstChar;
        }
        $this->_secondChar = $this->getReal();
    }
 
    /**
     * Checks to see if a character is alphanumeric.
     *
     * @param  string $char Just one character
     * @return bool
     */
    protected static function isAlphaNumeric($char)
    {
        return preg_match('/^[\w\$\pL]$/', $char) === 1 || $char == '/';
    }
 
    /**
     * Replace patterns in the given string and store the replacement
     *
     * @param  string $js The string to lock
     * @return bool
     */
    protected function lock($js)
    {
        /* lock things like <code>"asd" + ++x;</code> */
        $lock = '"LOCK---' . crc32(time()) . '"';
 
        $matches = array();
        preg_match('/([+-])(\s+)([+-])/S', $js, $matches);
        if (empty($matches)) {
            return $js;
        }
 
        $this->locks[$lock] = $matches[2];
 
        $js = preg_replace('/([+-])\s+([+-])/S', "$1{$lock}$2", $js);
        /* -- */
 
        return $js;
    }
 
    /**
     * Replace "locks" with the original characters
     *
     * @param  string $js The string to unlock
     * @return bool
     */
    protected function unlock($js)
    {
        if (empty($this->locks)) {
            return $js;
        }
 
        foreach ($this->locks as $lock => $replacement) {
            $js = str_replace($lock, $replacement, $js);
        }
 
        return $js;
    }
 
}

That’s it, enable your minifier in admin under System->Configuration->Developer->JavaScript Settings->Minimize Javascript Files -> Yes.

Good luck, and don’t forget to clear cache.

In case you feel you need some extra help, we can offer you a detailed custom report based on our technical audit – feel free to get in touch and see what we can do for you!

The post Implementing javascript minifier appeared first on Inchoo.

]]>
http://inchoo.net/magento/programming-magento/implementing-javascript-minifier/feed/ 5
Here’s why we loved 2016 and why we’ll grind even harder in 2017 http://inchoo.net/life-at-inchoo/infographic-2016/ http://inchoo.net/life-at-inchoo/infographic-2016/#respond Thu, 12 Jan 2017 11:54:04 +0000 http://inchoo.net/?p=28485 2016 has been a great year. It brought us many challenging and even more rewarding and amazing moments. It turned 365 days into another winning streak we couldn’t be more proud of. After all, that’s a signal all that hard work pays off. 😉  Check out what we’ve been through and what are some of...

The post Here’s why we loved 2016 and why we’ll grind even harder in 2017 appeared first on Inchoo.

]]>
2016 has been a great year. It brought us many challenging and even more rewarding and amazing moments. It turned 365 days into another winning streak we couldn’t be more proud of. After all, that’s a signal all that hard work pays off. 😉  Check out what we’ve been through and what are some of the things which will keep us motivated in the upcoming year. We can’t wait to push even harder – c’mon 2017, let’s see what you have in store!

The most important moment of all was definitely moving to the new office. We are now officially taking up 702 square meters in the Osijek centre and we couldn’t be more proud.

info-eng-02

This was the result of all the hard work we’ve put into building online stores over the years but 2016 itself brought us over 40 currently active projects from over 40 countries (some of them, joining the list for the first time!). We also got 7 new Inchooers joining the team and 16 fresh interns spicing up our daily activities.

info-eng-03

Half of the Inchooers went to 10 different international conferences and we organized one ourselves. Developers Paradise was a one of a kind event when it comes to the Magento community and it was an honour to organize it (don’t miss Meet Magento Croatia we’ll be hosting this year 😉 ). Developers Paradise accounted for 235 delegates, 30 countries and 23 speakers from all over the world. Those of you who joined us know how active we’ve all been on Twitter (700+ posts) and that we took a whole lot of memorable photos (2500+ in total, almost 500 decent enough to make it on Facebook).

info-eng-04

We are very proud that the team worked hard on being active on the blog with 113 articles published throughout the year which led to over 3 million page views on this very blog! Thank you all for reading us – you helped us hit yet another milestone!

All of that can be translated to social media language: 1 verified Twitter account with over 9000 followers (9164 in the moment when we were creating this infographic but we hope the number will continue to grow) and almost 5500 likes on Facebook.

info-eng-05

We are also proud we ate over 100 pizzas celebrating various important moments (such as birthdays, anniversaries aaaaaand Fridays) and drank over 19000 cups of coffee solving Magento mysteries.

Congrats to all of the Inchoo team which now has 1439 years in total, especially to those 3 that got married in 2016 and those 3 who welcomed their newborn babies to the world.

Thank you all for making 2016 a great and wonderful year and pushing us to strive to achieve even more in 2017 We will do our best, we promise!

NOTE: You can check out the full infographic here.

If you’d like to be part of our 2017 story, feel free to drop us a line. We’d like to make yet another memorable year by helping businesses thrive!

The post Here’s why we loved 2016 and why we’ll grind even harder in 2017 appeared first on Inchoo.

]]>
http://inchoo.net/life-at-inchoo/infographic-2016/feed/ 0
Custom data components in Pimcore http://inchoo.net/magento/magento-integration/custom-data-components-pimcore/ http://inchoo.net/magento/magento-integration/custom-data-components-pimcore/#comments Tue, 10 Jan 2017 12:00:55 +0000 http://inchoo.net/?p=28447 Today I will show you how to create custom data component in Pimcore 4.4.. If you are using Pimcore, sooner or later you will end up with requirement for new form element (complex type). Data components in Pimcore are object types used for complex data modeling on object. Data component consists from data, tag Extjs...

The post Custom data components in Pimcore appeared first on Inchoo.

]]>
Today I will show you how to create custom data component in Pimcore 4.4.. If you are using Pimcore, sooner or later you will end up with requirement for new form element (complex type). Data components in Pimcore are object types used for complex data modeling on object. Data component consists from data, tag Extjs scripts and one Model which is responsible for saving/loading configuration. Currently Pimcore has 19 complex data components (you can read more on https://www.pimcore.org/docs/latest/Objects/Object_Classes/Data_Types/index.html), and they will cover most requirements you will ever need. However, if (when) you’ll need a custom data component, here’s how you can create it.

Custom component

For demonstration we’ll create custom data component which will use source from custom controller and show data as select field (dropdown). Also we’ll cover how to add custom options in data script and use it on tag script. First, we’ll create plugin skeleton with basic plugin data in plugins folder and our folder will be called “Customdatacomponent”. If you want to know how to create plugin please read article “Extending Pimcore with plugins” on our blog. After we have created plugin skeleton it’s time to work on the component configuration script.

Data script

Data script is required for component configuration, and without this script our custom component will not be displayed in Pimcore. Let’s create script on location [Pimcore root]/plugins/Customdatacomponent/static/js/data-components/data/customcomponent.js, and include script in plugin configuration (plugin.xml):

<pluginJsPaths>
	<path>/plugins/Customdatacomponent/static/js/data-components/data/customcomponent.js</path>
</pluginJsPaths>

and we can write some code after that. First, we need to register pimcore.object.classes.data.customdatacomponent namespace and add new component class in pimcore.object.classes.data which will create class from pimcore.object.classes.data.data. To make it all work we also need to set type, allowed in, initialize logic, type name, icon class and group. We also need to work on getLayout method where we’ll add one custom option. But first, let’s write complete data script:

/**
 * Custom data component - configuration
 */
 
pimcore.registerNS("pimcore.object.classes.data.customdatacomponent");
pimcore.object.classes.data.customdatacomponent = Class.create(pimcore.object.classes.data.data, {
 
    type: "customdatacomponent",
 
    /**
     * define where this datatype is allowed
     */
    allowIn: {
        object: true,
        objectbrick: false,
        fieldcollection: false,
        localizedfield: false,
        classificationstore : false,
        block: true
    },
 
    initialize: function (treeNode, initData) {
        this.type = "customdatacomponent";
 
        this.initData(initData);
 
        this.treeNode = treeNode;
    },
 
    getTypeName: function () {
        return 'Custom data component';
    },
 
    getIconClass: function () {
        return "pimcore_icon_select";
    },
 
    getGroup: function () {
        return "select";
    },
 
    getLayout: function ($super) {
        $super();
 
        this.specificPanel.removeAll();
        this.specificPanel.add([
            {
                xtype: "textfield",
                fieldLabel: 'Custom option',
                name: "customoption",
                value: this.datax.customoption
            }
        ]);
 
        return this.layout;
    },
 
    applyData: function ($super) {
        $super();
        delete this.datax.options;
    },
 
    applySpecialData: function(source) {
        if (source.datax) {
            if (!this.datax) {
                this.datax =  {};
            }
        }
    }
 
});

You can see we have added Custom option (text field) in get layout method. We’ll call that value in tag script for label value. For demonstration we have set data component to be available only on objects (allowIn array). This is how it should look on data components menu:

customdatacomponent-1

and this is how it should look on configuration form:

customdatacomponent-2

Model

To save and load data into component configuration we’ll need to create corresponding model. When you click on save button in class form, our custom data component Pimcore will lookup for model in Pimcore\Model\Object\ClassDefinition\Data namespace, file with Customdatacomponent name and class Customdatacomponent which extends Model\Object\ClassDefinition\Data\Select. Now we need to create model on path [Pimcore root]/plugins/Customdatacomponent/lib/Pimcore/Model/Object/ClassDefinition/Data/Customdatacomponent.php. As you can see, starting point for autoloader in our plugin is in lib folder. Here is the model logic:

<?php
namespace Pimcore\Model\Object\ClassDefinition\Data;
 
use Pimcore\Model;
 
class Customdatacomponent extends Model\Object\ClassDefinition\Data\Select
{
 
    /**
     * Static type of this element
     *
     * @var string
     */
    public $fieldtype = "customdatacomponent";
 
    public $customoption;
 
    /** Restrict selection to comma-separated list of countries.
     * @var null
     */
    public $restrictTo = null;
 
    public function __construct()
    {
    }
 
    public function setcustomoption($customoption)
    {
        $this->customoption = $customoption;
    }
 
    public function getcustomoption()
    {
        return $this->customoption;
    }
}

We have extended Model\Object\ClassDefinition\Data\Select class because our custom data component is also select type and we already have all the logic. For custom option we’ll create class variable “customoption”, and the name matches as option added in data component. We also need setter and getter methods (setcustomoption and getcustomoption). Now we can test save and load configuration for our component on class form.

Tag script

We’ll create tag script which will be used to render data component. First, we’ll create the whole script logic and then I will explain every part. For this example we’ll cover getLayoutEdit method which is used by edit form. Create data script on location [Pimcore]/plugins/Customdatacomponent/static/js/data-components/tags/customcomponent.js

/**
 *
 */
pimcore.registerNS("pimcore.object.tags.customdatacomponent");
pimcore.object.tags.customdatacomponent = Class.create(pimcore.object.tags.select, {
 
    type: "customdatacomponent",
 
    initialize: function (data, fieldConfig) {
        this.data = data;
        this.fieldConfig = fieldConfig;
    },
 
    getLayoutEdit: function ($super) {
        $super();
 
        this.storeoptions = {
            autoDestroy: true,
            proxy: {
                type: 'ajax',
                url: "/plugin/Customdatacomponent/index/values",
                reader: {
                    type: 'json',
                    rootProperty: 'id'
                }
            },
            fields: ["id", "value", "text"],
            listeners: {
                load: function() {
                }.bind(this)
            }
        };
 
        this.store = new Ext.data.Store(this.storeoptions);
 
        var options = {
            name: this.fieldConfig.title,
            triggerAction: "all",
            editable: true,
            typeAhead: true,
            forceSelection: true,
            selectOnFocus: true,
            fieldLabel: this.fieldConfig.customoption,
            store: this.store,
            componentCls: "object_field",
            width: 250,
            style: "margin: 10px",
            labelWidth: 100,
            valueField: "value"
        };
 
        if (this.fieldConfig.width) {
            options.width = this.fieldConfig.width;
        } else {
            options.width = 300;
        }
 
        options.width += options.labelWidth;
 
        if (this.fieldConfig.height) {
            options.height = this.fieldConfig.height;
        }
 
        if (typeof this.data == "string" || typeof this.data == "number") {
            options.value = this.data;
        }
 
        this.component = new Ext.form.ComboBox(options);
 
        this.store.load();
 
        return this.component;
    }
});

After we set type and initialize class, we have created getLayoutEdit logic. First, we need to create storeoptions which are used to create store that will store pulled data from the custom controller. Data will be formatted to populate options for dropdown. For options we need to set proxy to point on url “/plugin/Customdatacomponent/index/values” and enable auto destroy and fields which will be returned by controller per item. After we create store options we’ll instantiate Ext.data.Store class. Next thing is to create options for component. Most of them are generic, but it’s important to set name (from configuration object), field label and store (instantiated store). Notice that we have set fieldLabel to value of our customoption from configuration to demonstrate how to use custom option field from data script on tag script. After options we need to create Ext.form.ComboBox, load store and return instantiated component. This is how our component looks like in object form:

customdatacomponent-3

Controller

Only thing we are missing to have data for tag script is custom controller. We’ll create custom controller on location [Pimcore root]/plugins/Customdatacomponent/controllers/IndexController.php

<?php
 
class Customdatacomponent_IndexController extends \Pimcore\Controller\Action\Admin
{
    public function indexAction()
    {
 
        // reachable via http://your.domain/plugin/Customdatacomponent/index/index
    }
 
    public function valuesAction()
    {
 
        $response = array(
            array(
                'id' => 0,
                'value' => 0,
                'text' => 'Value 0'
            ),
            array(
                'id' => 1,
                'value' => 1,
                'text' => 'Value 1'
            ),
            array(
                'id' => 2,
                'value' => 2,
                'text' => 'Value 2'
            )
        );
 
        return $this->_helper->json($response);
    }
}

Our logic is in valuesAction method. As you can see, we have prepared sample array with fields defined in store configuration in tag script. Important thing is to return array as json, for that we’ll use json method from our helper. Note that we are extending \Pimcore\Controller\Action\Admin class because it may contain sensitive data, so only admin should be able to access it.

Conclusion and Github

Now you have a basic knowledge on how to extend data components in Pimcore and you can additionaly improve our plugin to cover, for example, grid logic for select field. Hope this will help you to understand data components and Pimcore itself. Thanks for reading, soon we’ll cover more topics for extending Pimcore.

You can find our example plugin on https://github.com/zoransalamun/pimcore-custom-data-component

However, if you’re having trouble with Magento or Pimcore or need our technical assistance, feel free to drop us a note – we’ll be glad to check out your code and help you with development or review your site and help you get rid of any bugs there.

The post Custom data components in Pimcore appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-integration/custom-data-components-pimcore/feed/ 2
Overriding classes in Magento 2 http://inchoo.net/magento-2/overriding-classes-magento-2/ http://inchoo.net/magento-2/overriding-classes-magento-2/#comments Wed, 04 Jan 2017 12:11:24 +0000 http://inchoo.net/?p=28293 Compared to its previous version, Magento 2 came out with a new concept of dependency injection where classes inject dependencies (different objects) for an object instead of that object manually creating them internally. That way overriding and manipulating with classes is much easier and allows us more ways of extending the native functionalities. Which dependencies...

The post Overriding classes in Magento 2 appeared first on Inchoo.

]]>
Compared to its previous version, Magento 2 came out with a new concept of dependency injection where classes inject dependencies (different objects) for an object instead of that object manually creating them internally. That way overriding and manipulating with classes is much easier and allows us more ways of extending the native functionalities.

Which dependencies have to be injected in classes are controlled by the di.xml file. Each module can have a global and area-specific di.xml file that can be used depending on scope. Paths for module di.xml files:

<moduleDir>/etc/di.xml
<moduleDir>/etc/<area>/di.xml

It’s important to note that there are no more differences between overriding block, model, helper, controller or something else. They are all classes that can be overridden. We’ll go through three different ways of extending native Magento classes and methods.

Class preference

Let’s call this the old-fashioned way of overriding classes that we got used to, but slightly different. All the classes are defined by their interfaces and configured by di.xml files. There’s an abstraction-implementation mapping implemented when the constructor signature of a class requests an object by its interface. That means that interfaces should be used, where available, and the mapping will tell which class should be initiated.

Let’s take a look at how catalog product class is defined in the di.xml file of the Catalog module:

<config>
    <preference for="Magento\Catalog\Api\Data\ProductInterface" type="Magento\Catalog\Model\Product" />
</config>

To override Magento\Catalog\Model\Product class all we need is to define our preference and to create a file that will extend the original class:

<config>
    <preference for="Magento\Catalog\Api\Data\ProductInterface" type="Inchoo\Catalog\Model\Product" />
</config>
<?php
namespace Inchoo\Catalog\Model;
 
class Product extends \Magento\Catalog\Model\Product
{
// code
}

To make sure we have the right order of module dependencies, etc/module.xml should have defined module sequence for Magento_Catalog in our example.

 Plugins

Rewriting by class preference can cause conflicts if multiple classes extend the same original class. To help solve this problem a new concept of plugins is introduced. Plugins extend methods and do not change the class itself as rewriting by class preference does, but intercept a method call before, after or around its call.

Plugins are configured in the di.xml files and they are called before, after or around methods that are being overridden. The first argument is always an object of the observed method’s name followed by arguments of the original method.

As an example we’re going to extend a few methods from the catalog product module. This is how the di.xml file would look like:

<config>
    <type name="Magento\Catalog\Api\Data\ProductInterface">
        <plugin name="inchoo_catalog_product" type="Inchoo\Catalog\Plugin\Model\Product" />
    </type>
</config>
Before method

Before plugin is run prior to an observed method and has to return the same number of arguments  in array that the method accepts or null – if the method should not be modified. Method that’s being extended has to have the same name with prefix “before”.

<?php
namespace Inchoo\Catalog\Plugin\Model;
 
class Product
{
    public function beforeSetPrice(\Magento\Catalog\Model\Product $subject, $price)
    {
        $price += 10;
        return [$price];
    }
}
After method

After methods are executed after the original method is called. Next to a class object the method accepts one more argument and that’s the result that also must return. Method that’s being extended has to have the same name with prefix “after”.

<?php
namespace Inchoo\Catalog\Plugin\Model;
 
class Product
{
    public function afterGetName(\Magento\Catalog\Model\Product $subject, $result)
    {
        $result .= ' (Inchoo)';
        return $result;
    }
}
Around method

Around methods wrap the original method and allow code execution before and after the original method call. Next to a class object the method accepts another argument receives is callable that allows other plugins call in the chain. Method that’s being extended has to have the same name with prefix “around”.

<?php
namespace Inchoo\Catalog\Plugin\Model;
 
class Product
{
    public function aroundSave(\Magento\Catalog\Model\Product $subject, \callable $proceed)
    {
        // before save
        $result = $proceed();
        // after save
 
        return $result;
    }
}

Using plugins looks like an ideal solution for overriding methods, but it comes with limitations. Plugins cannot be used for all types of methods and other solutions will have to be looked for when trying to extends the following methods:

  • Objects that are instantiated before Magento\Framework\Interception is bootstrapped
  • Final methods
  • Final classes
  • Any class that contains at least one final public method
  • Non-public methods
  • Class methods (such as static methods)
  • __construct
  • Virtual types

Constructor arguments

di.xml configures which dependencies will be injected into a class what means that they can be controlled and changed to something that will be useful for us. If a change does not need to be global, but for a specific class, instead of overriding the whole class or creating plugins for different methods we’re able to configure arguments that the class receives.

Type configuration

One of the arguments that catalog product module receives is a helper \Magento\Catalog\Helper\Product $catalogProduct. With di.xml we can configure to use our helper instead:

<config>
    <type name="Magento\Catalog\Api\Data\ProductInterface">
        <arguments>
            <argument name="catalogProduct" xsi:type="object">Inchoo\Catalog\Helper\Product</argument>
        </arguments>
    </type>
</config>

Different argument types are allowed, depending of what is being changed. Allowed types are object, string, boolean, number, const, null, array and init_parameter.

Virtual type configuration

In the documentation virtual type is defined as a type that allows you to change the arguments of a specific injectable dependency and change the behavior of a particular class.

If we go back to a helper example, instead of injecting a new helper, there are occasions when changing just one argument in the original helper will do the job and creating a new file is redundant. In our example that would mean creating a virtual type from Magento\Catalog\Helper\Product, changing its arguments and using that virtual helper as an argument of a catalog product class.

<config>
    <type name="virtualHelper" type="Magento\Catalog\Helper\Product">
        <arguments>
            <argument name="catalogSession" xsi:type="object">Inchoo\Catalog\Model\Session\Proxy</argument>
        </arguments>
    </type>
    <type name="Magento\Catalog\Api\Data\ProductInterface">
        <arguments>
            <argument name="catalogProduct" xsi:type="object">virtualHelper</argument>
        </arguments>
    </type>
</config>

Virtual types come in handy when only construct arguments of dependencies have to be changed without creating any additional files and by just configuring it in xml files.

There’s more than one way of overriding classes and methods and choosing which one to use will depend on a situation you run into. While using a class preference as a way of overriding may look like the easiest way which will work in most situations, it’s the cause of many conflicts when different modules try to override the same classes and the same methods, which is why all the ways should be taken into consideration.

The post Overriding classes in Magento 2 appeared first on Inchoo.

]]>
http://inchoo.net/magento-2/overriding-classes-magento-2/feed/ 1