Magento – Inchoo http://inchoo.net Magento Design and Magento Development Professionals - Inchoo Thu, 20 Jul 2017 11:04:27 +0000 en-US hourly 1 https://wordpress.org/?v=4.7.3 How to run a quick security check of your Magento store? http://inchoo.net/magento/quick-security-check-magento-store/ http://inchoo.net/magento/quick-security-check-magento-store/#comments Wed, 12 Jul 2017 09:34:24 +0000 http://inchoo.net/?p=29983 Security of any software system, let alone an eCommerce one, is becoming one of the hottest topics out there. How can you as a store owner do a free and quick security check of your Magento website, even without immediate development assistance? Read on and make sure to stay secure! Magento has a vibrant ecosystem...

The post How to run a quick security check of your Magento store? appeared first on Inchoo.

]]>
Security of any software system, let alone an eCommerce one, is becoming one of the hottest topics out there. How can you as a store owner do a free and quick security check of your Magento website, even without immediate development assistance? Read on and make sure to stay secure!

Magento has a vibrant ecosystem and a huge community of people mostly willing to help. They often provide quality insights into their own findings by sharing the knowledge, in most cases for free. So, if you are a developer or a Magento store owner, you can find many useful tools out there to help you run your businesses. One such tool allows you to put your site through a quick security check and get some good pointers into how to solve some of the identified issues.

What’s up with security of Magento shops?

Any open source system, especially one of the leading eCommerce software platforms, is open to, among others, persons and organizations that don’t have only good intentions, to put it nicely. To put it accurately, there are a lot of hackers out there who are jumping at opportunities when certain security exploits are found. Some of them are even actively creating such opportunities for themselves.

What is Magento doing?

Magento is among rarely active open source eCommerce platforms when it comes to addressing any potential security exploits. They are releasing frequent security patches that should be applied by store owners or (in most cased) their development partners as soon as possible.

What are the most important patches?

Every security patch is important, however this all started back in 2015 with the infamous Shoplift Bug and SUPEE-5344 patch. This patch came out to help with the issue which saw thousands of websites vulnerable to potential store hijacking.

So, if this patch is not applied to your store, make sure to act swiftly. Additionally, the patches you should be on the lookout are: SUPEE-7405 and SUPEE-8788. There are many more, but these specifically can cause quite a lot of issues for you and your customers.

What can you do?

If you are on the merchant side of the equation (store owner, manager, anyone handling daily operations really), you can do a quick security check of your store by simply running your website through MageReport scan. If you are a developer, well – you can do the same 🙂

What is MageReport and what is it telling me?

Here’s a snippet from their own website:

This free service gives you a quick insight in the security status of your Magento shop(s) and how to fix possible vulnerabilities. MageReport.com is made by the Magento hosting specialists of Dutch provider Byte.

Essentially, the service is looking for proof/indication that you have official Magento patches installed. It also does security check over some other more or less known threats that can be mitigated rather easily.

IMPORTANT: If you are working with the latest versions of Magento 1 and/or Magento 2 – the latest stable releases being 1.9.3.3 (CE), 1.14.3.3 (EE), 2.1.7 (CE and EE) – chances are you are pretty safe, because in the process of upgrading your team have probably patched the site already. And there is also a chance that you can get false positives/negatives from this report in some cases. Still, it doesn’t hurt to check.

What to do next?

First off, if you’re seeing mostly red or orange in your scan results, you know you’re in a bad place. Depending on which patches your store are missing and your Magento version, the security of your store data may have been compromised.

Not good

Good

No need to panic right away, though. You should get in touch with your development team and ask them about this. As MageReport says, their report isn’t 100% accurate because they don’t have direct access to your store’s code. So, if you have a development team you trust, you should be in good hands. Simply ask them what’s the status about some of the missing patches (whether those are indeed missing). Then – work out a plan together to improve the security of your overall installation.

If you don’t have anyone actively working on or monitoring your store, you can get in touch with us directly to see how we can help.

If you’re getting mostly green results – well, hats off to you and your development team. You’re keeping the installation mostly safe and up to date with the latest security patches. Keep up the good work and don’t let new patches slide by you 🙂

Also, make sure to bookmark Magento Security center and keep your eye open on the incoming security news.

Stay safe!

The post How to run a quick security check of your Magento store? appeared first on Inchoo.

]]>
http://inchoo.net/magento/quick-security-check-magento-store/feed/ 1
CSS Media queries in Magento 2 http://inchoo.net/magento/magento-frontend/css-media-queries-magento-2/ http://inchoo.net/magento/magento-frontend/css-media-queries-magento-2/#respond Tue, 11 Jul 2017 11:54:58 +0000 http://inchoo.net/?p=30014 It’s highly unlikely that there are some frontend developers involved in a responsive website development who are not familiar with the concept of CSS Media queries. In this post, I’ll be looking at Magento 2 way of implementing this functionality in its default theme development workflow. How do Magento 2 media queries work? In the...

The post CSS Media queries in Magento 2 appeared first on Inchoo.

]]>
It’s highly unlikely that there are some frontend developers involved in a responsive website development who are not familiar with the concept of CSS Media queries. In this post, I’ll be looking at Magento 2 way of implementing this functionality in its default theme development workflow.

How do Magento 2 media queries work?

In the heart of Magento 2 media queries mechanism, defined in Magento UI library, is a .media-width() mixin.

.media-width(<@extremum>, <@break>);

@extremum: max|min – sets whether to use min-width or max-width in media query condition

@break: value – sets the value of breakpoint to compare with in media query condition

We can see the practical use of it in the following example:

// In .less file
.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__s) {
    your styles
}
 
// After compilation, in .css file
@media only screen and (max-width: 640px) {
    your styles
}

This mixin, which is used for grouping style rules in certain media queries, can be used whenever you need it, in any .less file in your theme. In the end, it will be invoked only once – in lib/web/css/source/lib/_responsive.less file.

If you check the end product of .less files compilation, styles.css file, you’ll see there each media query with all its rules only once, so there are no a multiple calls for the same query.

Default settings

By default, media queries are outputting styles in two different files:

1. styles-m.less – which generates basic and mobile-specific styles

2. styles-l.less – generates desktop-specific styles (for 768px width screen and higher)

This means that, when the website is opened on a mobile device, it will load only styles compiled from the styles-m.less file. Extra styles from the second file (styles-l.less) are compiling and loading only if the screen width is 768px or higher.

For this style groups separation, special variables @media-target and @media-common are used:

// For targetting device for styles output
@media-target: all|desktop|mobile
 
// In practice
& when (@media-target = 'mobile'), (@media-target = 'all') {
    @media only screen and (max-width: (@screen__xs - 1)) {
        .media-width('max', @screen__xs);
    }
}
 
// For deciding whether to output common styles.
@media-common: true|false
 
// Adding common styles
& when (@media-common = true) {
    your styles
}

Out of the box, Magento UI library has a set of predefined variables for breakpoints. We can use them in any case we need them and they can be changed or extended them with a new ones:

  • 320px
  • 480px
  • 640px
  • 768px (breakpoint for switching between mobile and desktop views)
  • 1024px
  • 1440px

Custom breakpoints

Quite often, we’ll find ourselves in a situation where we’ll need to add an additional breakpoint (apart from default ones) in which we can apply a different styling. There are three things which need to be done in order to add a custom breakpoint in our theme.

1. Define a variable for the new breakpoint.

First, we’ll have to create a /web/css/source/variables.less file. If we would use a standalone theme, this file would be new and empty. But, since we’re following the best practice (inheriting from Magento Blank theme), we’ll have to copy this file from our parent theme.

Here we can define our custom breakpoint:

@custom__breakpoint: 1280px;

2. Implement our new breakpoint

To do so, we’ll override _responsive.less file from Magento UI library by copying it to our theme in /web/css/source/lib/. Now, in our theme’s _responsive.less file we have to edit .media-width() mixin by adding the appropriate rule for the new custom breakpoint.

& when (@media-target = 'desktop'), (@media-target = 'all') {
    @media all and (min-width: @custom__breakpoint) {
        .media-width('min', @custom__breakpoint);
    }
}

3. Implement the screen changes for the new breakpoint.

If everything is set properly, we should be able call our new .media-width() mixin whenever we need it in our theme .less files.

.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @custom__breakpoint) {
    // Stlying applied at @custom__breakpoint breakpoint
}

A small helper

In the end, to wrap things up, I’ll give you some quick jump-start piece of code; media queries which you can quickly apply, depending on which screen size you’d like to target.

// Common
// (for styles used in both mobile and desktop views)
& when (@media-common = true) {}
 
// Mobile
// (for all mobile styles.)
.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {}
 
// Tablet
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
 
// Desktop
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__l) {}

Thank you for reading!

The post CSS Media queries in Magento 2 appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-frontend/css-media-queries-magento-2/feed/ 0
Override Magento 2 Layout: Product page example http://inchoo.net/magento/magento-frontend/override-magento-2-layout-product-page-example/ http://inchoo.net/magento/magento-frontend/override-magento-2-layout-product-page-example/#respond Thu, 06 Jul 2017 11:00:10 +0000 http://inchoo.net/?p=29921 Layouts play a major roll in Magento. This roll is well known from Magento 1x layout approach. With Magento 2 couple of things are changed and improved. Really, we finally have on our disposal useful layout manipulation options. Layout basics If you are here just for example and you already familiar with Magento 2 Layout,...

The post Override Magento 2 Layout: Product page example appeared first on Inchoo.

]]>
Layouts play a major roll in Magento. This roll is well known from Magento 1x layout approach. With Magento 2 couple of things are changed and improved. Really, we finally have on our disposal useful layout manipulation options.

Layout basics

If you are here just for example and you already familiar with Magento 2 Layout, skip it this section.

Layout is a page structure, represented by elements hierarchy, which can be: blocks and containers.
Technically, layout is defined in the .xml files. Files which contain element declarations and different manipulation instructions.

Module and theme layout files

Magento 2 layouts are provided by different application components. We can split them in two major groups Base layout and Theme layout. Let us mention main difference between them.

Base layouts

This Layout files are provided by modules. We can find page configuration and generic layout files on this path:
module_dir/view/frontend/layout

And page layout files on path:
module_dir/view/frontend/page_layout

The main file of Base layout, if you wish to check it, is located on path:
Magento_Theme_module_dir/view/frontend/layout/default.xml

Theme layouts

Theme layouts are logical files provided by Magento themes. Here we can find page configuration and generic layout files. Check them on path:
theme_dir/Namespace_Module/layout

Also, the page layout files are on path:
theme_dir/Namespace_Module/page_layout

Override a layout

Above mentioned basic are necessary in order to understand how to properly override Magento 2 layout. The layout needs to be properly overridden when we perform some kind of customization, for example when we:

  • Modify method arguments
  • Remove blocks and/or containers
  • Set up XML attributes of blocks and containers
  • Remove block arguments
  • Modify and suppressing handles inclusion
  • Remove all handle instructions by declaring an overriding layout file with an empty handle

Override base layouts

To override base layout file (layout provided by the module) we need to put a layout file with the same name in the following location:
/Namespace_Module/layout/override/base/layout.xml

These file override the following layout:
/view/frontend/layout/layout.xml

Override theme layouts

To overriding theme layout (override a parent theme layout) put a layout file with the same name in the following location:
theme_dir/Namespace_Module/layout/override/theme/Vendor/theme/layout.xml

These files override the following layouts:
parent_theme_dir/Namespace_Module/layout/layout.xml
To override page layout files, use ‘page_layout’ directory name instead of ‘layout’.

Product page override

Now when we are familiar with layout overriding rules, let’s make a real life example. Let’s override product page layout.

Overriding product page for specific products

For example, our client has two or more types of products and on top of that, client also has one special product which is totally different. So client also wishes to present that product in different product page. Basically, we need at least three different product page layouts.

Luckily Magento 2 provides this kind of flexibility by using native addPageLayoutHandles method. Mentioned method provides possibilities of overriding layout using:

  • Product ID
  • Product SKU
  • Product TYPE

On top of that, method will also support your custom product type. For instance if we create a product type with the name “inchoo”, you can then create a file called catalog_product_view_type_inchoo.xml to handle specific layouts for this type of products.

So, let start first with overriding layout for just one specific product.

In order to do that we need to follow this steps:

  1. Create new layout file inside our theme scope, the name of layout file will be: catalog_product_view_id_number and we’ll place it in:
    theme_dir/Namespace_Module/layout/catalog_product_view_id_number.xml

    Similar like this:

  2. Override what we need inside of layout file (remove blocks, add new blocks etc.).
  3. Clear Magento’s cache and reload page.

We are preferring to use product ID. But if product ID isn’t good option for you, you can use SKU number for targeting product, in that case your layout file need have name in this form:
catalog_product_view_sku_productsku.xml

Now let’s create unique product page for specific product type. As you probably know Magento by default has few different product types (bundle, configurable, downloadable etc.). So for example, if we wish to have different layout for downloadable products, we need to use corresponding product type name to target that layout.

In order to override downloadable products, follow this steps:

  1. Create new layout catalog_product_view_type_downloadable.xml file inside Magento theme.

    theme_dir/Namespace_Module/layout/catalog_product_view_type_downloadable.xml

  2. Override what we need inside of layout file (remove blocks, add new blocks etc.).
  3. Clear Magento’s cache and reload page.

Same naming principle can be followed for other type of products, like this:

catalog_product_view_type_simple.xml
catalog_product_view_type_configurable.xml
catalog_product_view_type_grouped.xml
catalog_product_view_type_bundle.xml
catalog_product_view_type_virtual.xml
catalog_product_view_type_downloadable.xml

Important note: Current Magento 2 version has bug with configurable product type. Unfortunately it isn’t possible to override configurable products just by creating new catalog_product_view_type_configurable.xml layout file. We hope that the next version of Magento will solve this bug. Stay tuned here.

The example is short but I hope that this article can help you with overriding layouts.

If you have issues on your web store and you need frontend assistance, contanct us for usability audit.

Regards.

The post Override Magento 2 Layout: Product page example appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-frontend/override-magento-2-layout-product-page-example/feed/ 0
Klevu – search engine that will increase revenue, self-learn and improve UX http://inchoo.net/magento/magento-search/working-with-klevu-search/ http://inchoo.net/magento/magento-search/working-with-klevu-search/#comments Wed, 28 Jun 2017 11:17:03 +0000 http://inchoo.net/?p=29830 One of most important parts of every store is its Search functionality. Implementing a more advanced search solution can have positive and big impact on search function, which leads to significant increase in conversion rate. After all, visitors trust in search results. If your store’s search experience is good, users will use it more than...

The post Klevu – search engine that will increase revenue, self-learn and improve UX appeared first on Inchoo.

]]>
One of most important parts of every store is its Search functionality. Implementing a more advanced search solution can have positive and big impact on search function, which leads to significant increase in conversion rate. After all, visitors trust in search results. If your store’s search experience is good, users will use it more than browsing by clicking through categories.

With artificial intelligence and self learning processes, search engines became even more powerful and provide us with features that were not possible before.

Klevu is one of those search engines – it is fast (really fast), it provides high level of customisation and most important – it learns from your customers and self-improves which results in more accurate search results and increase in revenue.

A few key takeaways from developer’s side:

Responsive works well out of the box, styling is decent and code is something you can work with.

It’s dynamic filters automatically create all relevant filters in the search results.

Error tolerance, as an enhanced keyword search index, ensure that shoppers are always connected to the right products.

Klevu works with both Magento 1 and Magento 2.

(for full feature list, check this link)

Installing Klevu

Installation is pretty much straightforward and same as with other extensions so i won’t talk much about it. After Extension has been installed, you must configure it and create your account.

Go to:

System > Configuration > Klevu > Search configuration

and start configuration wizard:

There are some prerequisites to be met, so check them out in case of any issues.

Klevu Dashboard

When you finish with Klevu wizard, you will be provided with credentials to access Klevu Dashboard where you can configure and edit Klevu to suit your needs.

There are many options over there so go through tabs and get yourself familiar with features.

In terms of styling, you will have to edit 2 areas of Klevu search – Search results dropdown and Search results page.

Under Customization section, you can change the look and feel of your Klevu search.

In Search as You Type Layout tab, you can define the look of search dropdown:

You can choose whether you will use Instant faceted search or Instant autocomplete layout first one displays filters on the left while second one is displaying suggestions and results under.

You can choose between grid or list display, as well as how many products you wish to feature. Pretty much everything you need for start.

Klevu Styling

Under Customize CSS tab, you can edit CSS used for styling of search results dropdown.



You can then save changes and preview the styling on your testing site. Sometimes, Klevu needs few minutes to update changes so be patient. 🙂

If you click on Search results page tab, you will see screenshots and explanation on how to change styling of search results page.

As you can see, it is not possible to change CSS in Klevu Dashboard (like we did for search suggestions), instead we are informed that we can use our own default Magento search results page (in which case we will have our theme styling and no additional CSS modifications will be needed) or we can use Klevu search results page. In that case, styling is done like with any other extension – editing files locally.

Same story as always – copy files from base to your package/theme and start with your magic.

Klevu data

By default, Klevu uses the following attributes in search: name, sku, description, short_description, price and rating.

You can add other custom attributes as well:

As you can see, you can tell Klevu which attribute values will be used in search. Klevu works great with receiving data and sending results back to user but if you want to display some custom product data on search results page, you may be in problem.

Read next…

Klevu Search results page

You can use two types for search results page – Klevu search result page or Magento search results page. Although decision seems pretty much straightforward, there are few differences that you must be aware before making that decision.

If you use:

  • Magento search results page
    – no need for additional styling (if search results page is already styled)
    – no need for additional structural changes
    – attributes – multiselect doesn’t work
    – ajaxed search results don’t work
  • Klevu search results page
    – instant, fast ajaxed results
    – multiselect Attributes
    – requires styling
    – custom product-related information not shown

Using Klevu search results page may cause an issues with custom data attached on your products. Although you can send additional data to Klevu and Klevu will provide results based on that data, it will not send that data back which means these informations will be missing on frontend. Reason for that is simple – Klevu is not aware of custom data shown on your category listing/search results page. You can define which custom attributes will be included in search but Klevu is not sending that data back, it is only using it to output search results.
That is a problem since that custom data can be really important for users.

Hopefully, there is a solution – Klevu support team. Of course, you can’t modify core functionalities by yourself so you will have to contact these guys. They will then do necessarily changes on their end or you will receive additional code to implement on your site.

I must say that we did this kind of work with klevu support and they did great job. They always react fast and get the job done.

Big question now is – Should we use default Magento search results page template or we should go with Klevu?
Basically, it all depends on project scope.

For heavier projects – it is better to use Magento search results page as in that case you will have your own structure and control over what is shown. On the other side, you will have to do Ajax functionality by yourself, as well as multiselect of attributes in layered navigation.
Using Klevu search results page means you will have to work with Klevu team and if there is a heavy customisation needed, whole process may take a while.

For clean, straightforward project with less custom information, go with Klevu search results page as search speed is just amazing and like i said, results are ajaxed and multiselect works. Results page styling will not follow Theme design so that will be extra work from your side.

Conclusion

Klevu is definitely something you should try/suggest to your clients. For us, it has proven to be one of best search engines we have ever tried.

Klevu is running on several of our projects and clients are satisfied with it.
And the best part – it will only get better since it learns from users and adjusts search results accordingly.

Klevu also offers 14 days of free trial which is enough time for first impressions and looking “under the hood”.

If you wish to know more, go on their site and check out integration guides or other documentation – everything is well written and explained.

We’ve implemented Klevu for our client BAUHAUS Croatia. This home and garden specialists have seen great results after achieving new development milestone, which also included implementation of Klevu search. 74% higher eCommerce Conversion rate, 105% increase in total transactions and 143% jump in Revenue!

If these numbers sound great, let’s see what we can achieve for your store!

The post Klevu – search engine that will increase revenue, self-learn and improve UX appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-search/working-with-klevu-search/feed/ 2
Bypassing Magento 2 layout update restrictions in Admin http://inchoo.net/magento-2/bypassing-magento-2-layout-update-restrictions-admin/ http://inchoo.net/magento-2/bypassing-magento-2-layout-update-restrictions-admin/#comments Tue, 06 Jun 2017 10:42:28 +0000 http://inchoo.net/?p=29580 Magento’s layout system has been vastly improved in Magento 2 to be more accessible, more powerful and to be more streamlined. But, for some reason, only a very small subset of layout functions is made available for use in Magento 2 Admin (category layout XML updates, to be more precise). In this article, we’re going to...

The post Bypassing Magento 2 layout update restrictions in Admin appeared first on Inchoo.

]]>
Magento’s layout system has been vastly improved in Magento 2 to be more accessible, more powerful and to be more streamlined. But, for some reason, only a very small subset of layout functions is made available for use in Magento 2 Admin (category layout XML updates, to be more precise). In this article, we’re going to show a very simple way of bypassing this pesky restriction.

Layout update restrictions in Magento 2 Admin

In the interest of keeping the example simple and straightforward, let’s say we want to remove the page title element from a specific category. In Magento 2 admin, in the category layout update XML field we add the following line.

<referenceBlock name="page.main.title" remove="true" />

And when we click save, we get the following error:

Please correct the XML data and try again. Element ‘referenceBlock’: This element is not expected. Expected is one of (referenceContainer, container, update, move ). Line: 1

It’s important to note that this XML would be valid if added using XML files in a theme. It seems that different standards apply for the layout update field in Magento 2 Admin and for the theme layout files.

So, only the following XML is allowed:

  • referenceContainer – we can reference existing containers
  • container – we can create new containers
  • update – we can include various layout handles
  • move – we can move existing blocks around

This info will help us bypass the restriction.

Bypassing the layout update restrictions

We can simply bypass this restriction by wrapping our referenceBlock code in referenceContainer code. But we need to find the container which contains our page title element. By doing a simple search in IDE, we find the following code snippet.

<referenceContainer name="columns.top">
<!-- ... -->
    <block class="Magento\Theme\Block\Html\Title" name="page.main.title" template="html/title.phtml"/>
            <container name="page.messages" htmlTag="div" htmlClass="page messages">
                <block class="Magento\Framework\View\Element\Template" name="ajax.message.placeholder" template="Magento_Theme::html/messages.phtml"/>
                <block class="Magento\Framework\View\Element\Messages" name="messages" as="messages" template="Magento_Theme::messages.phtml"/>
            </container>
<!-- ... -->
</referenceContainer>

We use the columns.top container and reference it in our Layout Update XML in Admin and wrap it around our initial code.

<referenceContainer name="columns.top">
    <referenceBlock name="page.main.title" remove="true" />
</referenceContainer>

When we save this configuration, we do not get any errors on the category page in Admin and the page title block is removed on that specific category.

Conclusion

We hope to see these restrictions either removed from Magento 2 Admin XML layout update fields or better documentation that will explain the behavior in more detail. Magento 2 Admin would most certainly benefit of having unrestricted layout update features. Until then, we can hopefully use this bypass safely and hope that it’s upgrade-proof.

The post Bypassing Magento 2 layout update restrictions in Admin appeared first on Inchoo.

]]>
http://inchoo.net/magento-2/bypassing-magento-2-layout-update-restrictions-admin/feed/ 1
Version control systems in UI design http://inchoo.net/magento/design/version-control-systems-ui-design/ http://inchoo.net/magento/design/version-control-systems-ui-design/#comments Tue, 30 May 2017 10:58:43 +0000 http://inchoo.net/?p=29565 Our design team switched to working in Sketch a while ago but it wasn’t until version 43 we really started seeing some opportunities to change our workflows more significantly. It was also an opportunity for more designers to work on the same project as well as collaborate with developers more easily. The case of version...

The post Version control systems in UI design appeared first on Inchoo.

]]>
Our design team switched to working in Sketch a while ago but it wasn’t until version 43 we really started seeing some opportunities to change our workflows more significantly. It was also an opportunity for more designers to work on the same project as well as collaborate with developers more easily.

The case of version 43

With Sketch version 43, Bohemian Coding quietly announced – a revised file format. Behold. They introduced a new file format that allows reading and writing .sketch files manually or programmatically and go from Sketch straight to implementation. What does that mean for our workflow? Sketch had a file format that was workable not only from the design side but developer side too.

An unzipped Sketch file created .png preview and human-readable .json files. This created a question for us – could our design and development teams collaborate seamlessly? Are we finally to feed design assets directly to the development pipeline and integrate .sketch file at the same time as development?

By having similar repositories we could both edit the same designs.  That could be any element, modular component or an entire screen UI. Anything visual can now go back and forth between Sketch and UI frontend seamlessly – in theory. What does that mean for us? We’d have to apply distributed version control to support this new, high-level way of collaborating not only within our design team but with developers outside the team as well to create a safe and responsible environment for project development. We also needed to find a way of knowing which one of us made changes, when they were made and if we can roll back to them. Seeing visually what these changes would be an added bonus.

Just a side note, all of this also meant that documents created with Sketch 43 would not be compatible with previous versions. It was the quietest end of an era, perhaps for a reason.

Talking to our backend developer and testing this new file format we found that this new Sketch format offers a different workflow, but it wasn’t the smoothest one. We weren’t quite convinced in the widely spread “Design is code, code is design” line. Sure, this brings different disciplines closer, but it doesn’t equate them. At least not yet. The whole rename .sketch extension to .zip, unzip it and then connect to GitHub (which we use) process does work (and you get to see the changes in the preview .png). But, for starters, it would be a lot easier if there was some sort of Sketch launcher that would eliminate the extension renaming process.

It doesn’t seem possible to use branches in an effective way (they can’t be merged and all the integration work needs to be done manually) so we, designers can’t link our system with version control and as a consequence can’t have a good overview of the all the progress done on the design. Also, simply put, this is a great concept but we just don’t see a workflow where a developer might find himself changing design elements and checking that code back in so the Sketch app can render those changes next time we work on it.

Aaron Weyenberg (UX lead from TED) best described when our workflows would actually improve. “It’ll be from Sketch’s open format leading to more powerful plugins and third party tools to make the day to day collaboration of designers and developers faster while not upending their existing habits.”

Sharing is caring

Recently we had the issue of multiple designers working on the same project. There were constant debates as to which file is the “freshest” and manual overriding to create one master file. It moved us to explore versioning systems we could use within our design team. Libraries, as we’ve seen with Craft, works well if you’re willing to create separate text styles, color and uncategorised layers that can then be used through multiple .sketch files.

But, what if entire layouts and pages need to be changed and rearranged, not just styles and components. How do you update structure and flow?

Symbols work great for single-player, single-file mode, libraries are more multi-player, multi-file kind of situation, but what about multi-player, single-file scenario? At what point and how do we unify our work and keep a neat overview of it?

The tools of the trade

Developers already have tools like GitHub and Source Tree – we needed a similar version control system. Enter Folio, Plant and Abstract.

Folio

Folio is a Mac only app that allows you to join existing Git repositories. You can clone existing projects from anywhere, including Gitlab, Github, Bitbucket or your own Git server. Folio will automatically stay in sync. Unlike the other apps it has no Sketch plugin and is available for free in trial mode through a Free Viewer. It works with most files (Photoshop, Illustrator, Sketch, Antetype & SVG out of the box) and you can add support for most files that have Quicklook previews.

Folio makes it quite easy to add a Git repository and allows you to browse all files assets and work with them. When you update your Sketch file, Folio will automatically update (commit and push) it. It also keeps all versions of a file, so you can easily review them. However, it’s a bit messy since there is no folder organisation – just one library. Also, In Folio every commit you make is on a single file. Select a file on the left, show its version history on the right. Other than your written description, the only way to see which components on which artboards were changed is to compare them visually.

Plant

Plant is also a Mac only app with a Sketch plugin. It includes Cloud sync, version history, conflict resolution but seemingly you cannot use Plant with other version controlled softwares than the proprietary one (currently you get 1 GB). It’s based on pages (and has filtering options) – it recognises which artboards you made changes to and suggest them as changes ready to be sent. It has the ability to show only the modified artboards as well as comments to each version in a pretty neat preview mode of the artboard. It also syncs pretty fast. We only wish it was more clear as to what is the actual master file (since that’s our main goal). Right now, each version you change is listed in the history on the right and branching and merging is not as clear as we would like them to be.

Abstract

We have to give it up to Abstract as they seem to be our favourites. It took us no time to find our way around the app as it’s very clear what your commits and branches are as well as what the master file is. We had two questions for the crew that generously invited us to try out the Private Alpha – when can we buy Abstract and can we use our own GitHub (since that is what the company uses as a versioning tool) to clone projects and stay in sync. At the moment you can’t use Abstract with other version controlled softwares (i.e. Github and Bitbucket) than the proprietary one. We’re still waiting for a reply on if that would change. Currently it is available only for Mac and Sketch but it was announced that it would also be available for Adobe.

As they put it on their blog “Throw Abstract in the mix, and suddenly everyone on a team has access to a rich history of why certain things are the way they are, the decisions that led to the current state of the work, and a way to instantly start contributing in a meaningful way.” Can’t wait for the Public Beta!

 

I also have to give thanks where thanks is due – to the backend developer who helped create this article. Thanks for all the help and collaboration!

The post Version control systems in UI design appeared first on Inchoo.

]]>
http://inchoo.net/magento/design/version-control-systems-ui-design/feed/ 2
External database connection in Magento http://inchoo.net/magento/magento-database/external-database-connection-magento/ http://inchoo.net/magento/magento-database/external-database-connection-magento/#comments Thu, 27 Apr 2017 14:58:39 +0000 http://inchoo.net/?p=29408 Most of the time working with Magento, a single database connection is just enough. Magento has excellent system of adding new tables in database or extending existing ones. So, why would there be a need for an external database connection outside the Magento system? Well, one of the examples is data migration from another ecommerce...

The post External database connection in Magento appeared first on Inchoo.

]]>
Most of the time working with Magento, a single database connection is just enough. Magento has excellent system of adding new tables in database or extending existing ones. So, why would there be a need for an external database connection outside the Magento system? Well, one of the examples is data migration from another ecommerce system. In this article, a simple connection to external database is explained with CRUD (create, read, update, delete) examples.

Configuration

This external database connection is similarly defined as the Magento default one – in an XML configuration. The difference is that foreign connection is defined inside particular module’s XML configuration. It defines read and write adapters, setup and database credentials information. Foreign tables are defined in the same way as magento tables. They are under inchoo_foreignconnection_resource node so the model resource can be invoked later in the code. For demonstration purpose, there’s a frontend node in XML configuration that defines front name of the controller (fconn).

<?xml version="1.0"?>
<config>
    <modules>
        <Inchoo_ForeignConnection>
            <version>1.4.2</version>
        </Inchoo_ForeignConnection>
    </modules>
    <global>
        <models>
            <inchoo_foreignconnection>
                <class>Inchoo_ForeignConnection_Model</class>
                <resourceModel>inchoo_foreignconnection_resource</resourceModel>
            </inchoo_foreignconnection>
            <inchoo_foreignconnection_resource>
                <class>Inchoo_ForeignConnection_Model_Resource</class>
                <entities>
                    <product>
                        <table>product_description</table>
                    </product>
                </entities>
            </inchoo_foreignconnection_resource>
        </models>
        <resources>
            <inchoo_foreignconnection_write>
                <connection>
                    <use>inchoo_foreignconnection_database</use>
                </connection>
            </inchoo_foreignconnection_write>
            <inchoo_foreignconnection_read>
                <connection>
                    <use>inchoo_foreignconnection_database</use>
                </connection>
            </inchoo_foreignconnection_read>
            <inchoo_foreignconnection_setup>
                <connection>
                    <use>core_setup</use>
                </connection>
            </inchoo_foreignconnection_setup>
            <inchoo_foreignconnection_database>
                <connection>
                    <host><![CDATA[localhost]]></host>
                    <username><![CDATA[username]]></username>
                    <password><![CDATA[password]]></password>
                    <dbname><![CDATA[db_name]]></dbname>
                    <initStatements><![CDATA[SET NAMES utf8]]></initStatements>
                    <model><![CDATA[mysql4]]></model>
                    <type><![CDATA[pdo_mysql]]></type>
                    <pdo_type><![CDATA[]]></pdo_type>
                    <active>1</active>
                </connection>
            </inchoo_foreignconnection_database>
        </resources>
    </global>
    <frontend>
        <routers>
            <inchoo_foreignconnection>
                <use>standard</use>
                <args>
                    <module>Inchoo_ForeignConnection</module>
                    <frontName>fconn</frontName>
                </args>
            </inchoo_foreignconnection>
        </routers>
    </frontend>
</config>

Model

Next thing is a model that will use defined foreign connection to get data or to save data in a foreign database. Here, the model is initialized with the product table from XML configuration, that in this case defines product_description table.

class Inchoo_ForeignConnection_Model_Product extends Mage_Core_Model_Abstract
{
    protected $_eventPrefix = 'inchoo_foreignconnection_product';
    protected $_eventObject = 'product';
 
    protected function _construct()
    {
        $this->_init('inchoo_foreignconnection/product');
    }
}

Model resource class is also defined with the same xml configuration node in _init() function, but with the TABLE_PRIMARY_KEY parameter. In this class, several functions can be created that will work with external data.

First example is createDataInResource function, which inserts data in model’s table. It takes array of parameters that will be inserted.

Second example is a readDataFromResource function that fetches all data from model’s table. Read adapter must be defined first. It is a configuration node from xml that defines read connection. After read adapter definition, Magento database functions can be used (select(), from(), limit(), etc..). When query is constructed completely, it can be executed with read adapter. Data can be retrieved with fetchPairs() or fetchAll() function. fetchAll() is used to get all records returned from mysql.

updateDataInResource and deleteDataFromResource functions take additional $id parameter that defines which record will be updated or deleted.

class Inchoo_ForeignConnection_Model_Resource_Product extends Mage_Core_Model_Resource_Db_Abstract
{
    const TABLE_PRIMARY_KEY = 'product_id';
 
    protected function _construct()
    {
        $this->_init('inchoo_foreignconnection/product', self::TABLE_PRIMARY_KEY);
    }
 
    public function createDataInResource($values = array())
    {
        $writeAdapter = $this->_getWriteAdapter();
        try {
            $writeAdapter->insert(
                $this->getMainTable(),
                $values
            );
        } catch (Exception $e) {
            Mage::log('Unable to insert data to external resource. ' . $e->getMessage(), null, null, true);
        }
    }
 
    public function readDataFromResource()
    {
        $data = array();
        $readAdapter = $this->_getReadAdapter();
        $select = $readAdapter->select()
            ->from($this->getMainTable(), '*')
            ->limit(20);
 
        try {
            $data = $readAdapter->fetchAll($select);
        } catch (Exception $e) {
            Mage::log('Unable to fetch data from external resource. ' . $e->getMessage(), null, null, true);
        }
 
        return $data;
    }
 
    public function updateDataInResource($id, $values = array())
    {
        $writeAdapter = $this->_getWriteAdapter();
        try {
            $writeAdapter->update(
                $this->getMainTable(),
                $values,
                self::TABLE_PRIMARY_KEY . '=' . $id
            );
        } catch (Exception $e) {
            Mage::log('Unable to update data in external resource. ' . $e->getMessage(), null, null, true);
        }
    }
 
    public function deleteDataFromResource($id)
    {
        $writeAdapter = $this->_getWriteAdapter();
        try {
            $writeAdapter->delete(
                $this->getMainTable(),
                self::TABLE_PRIMARY_KEY . '=' . $id
            );
        } catch (Exception $e) {
            Mage::log('Unable to delete data from external resource. ' . $e->getMessage(), null, null, true);
        }
    }
}
class Inchoo_ForeignConnection_Model_Resource_Product_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract
{
    public function _construct()
    {
        $this->_init('inchoo_foreignconnection/product');
    }
}

Usage in controller

All these functions are demonstrated in IndexController class but since they are defined in model’s resource class, they can be called in any controller class.

class Inchoo_ForeignConnection_IndexController extends Mage_Core_Controller_Front_Action
{
    public function indexAction()
    {
        // Create
        $foreignProductCreate = Mage::getModel('inchoo_foreignconnection/product')->getResource();
        $foreignProductCreate->createDataInResource(
            array(
                'product_name' => 'Product name',
                'product_description' => 'Product description'
            )
        );
 
        // Read
        $foreignProductRead = Mage::getModel('inchoo_foreignconnection/product')->getResource();
        $result = $foreignProductRead->readDataFromResource();
        var_dump($result);
 
        // Update
        $foreignProductUpdate = Mage::getModel('inchoo_foreignconnection/product')->getResource();
        $foreignProductUpdate->updateDataInResource(
            3394,
            array(
                'product_name' => 'Product name updated',
                'product_description' => 'Product description updated'
            )
        );
 
        // Delete
        $foreignProductDelete = Mage::getModel('inchoo_foreignconnection/product')->getResource();
        $foreignProductDelete->deleteDataFromResource(3394);
    }
}

In most scenarios, Magento will use different type of external connection to retrieve or send data, but sometimes an external database connection like this will be the best way to go. One of the examples would be when you want to import products from another system to Magento with their xsell or upsell products. In that case, read connection would be used to retrieve product data and write connection would be used to save xsell or upsell product ids in a temporary table so they can be assigned to Magento product when all products from external system are imported. 

The post External database connection in Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-database/external-database-connection-magento/feed/ 2
Javascript Bundling in Magento 2 http://inchoo.net/magento-2/javascript-bundling-magento-2/ http://inchoo.net/magento-2/javascript-bundling-magento-2/#comments Fri, 21 Apr 2017 09:32:09 +0000 http://inchoo.net/?p=29344 Javascript bundling is a technique that groups separate files in order to reduce the number of HTTP requests that are required to load a page. Bundling is commonly used in today’s “module-based” development where some functionalities are basically split into Modules (roughly explained). For loading modules, we usually use some of popular module loaders such...

The post Javascript Bundling in Magento 2 appeared first on Inchoo.

]]>
Javascript bundling is a technique that groups separate files in order to reduce the number of HTTP requests that are required to load a page. Bundling is commonly used in today’s “module-based” development where some functionalities are basically split into Modules (roughly explained). For loading modules, we usually use some of popular module loaders such as rollup.js or RequireJS (which is Magento’s weapon of choice).

HTTP2 is here so this technique will be probably deprecated in the future. You could still use bundling for reducing the number of requests for a specific page but I don’t think it will be worth of it.
Right before we start with “reverse” optimisation, we can use bundling to help us organise our assets and serve less files to the client which will (should) result in faster website.

One of our services is Technical audit which covers various tests on both Frontend and Backend part of the client’s store. Since i’m Frontend Developer, performance is my key point during analysis.

First thing I check is wether Javascript and CSS files are merged/minified/bundled (hello bundle). When I was Junior Developer, I was so happy/angry when I discovered someone didn’t turn these ON since I was thinking that technical level of Developers who worked on site was low and we can do a lot to improve site… and of course, I could get a “quick win” with turning merging/minifying ON.

Well, in theory that was great. Usually I was right about their expertise level but I was wrong about one thing – they didn’t “forgot” to turn these ON.

Explaining to the client that you would probably have to refactor 80% of the code to change that “simple option” is something I wouldn’t want to go through again.

We were doing Technical audit of the Magento 2 store and when I found out that Javascript and CSS files were not merged/minified/bundled I was in shock and first question that was going through my head was “shall I report this to the client?!”.

Strange question, I know. But it does make sense because if I report it, I’ll have to fix it.

Just kidding, of course this was included in our report and was first thing I changed when we took over the project.
How did it go? Well, do you write articles about stuff that work as expected?

Magento 2 Bundling

I must note that official docs do not give us a lot of information and there are only few discussions online about it.
Therefore, I may be wrong in some of my conclusions so feel free to correct me in comments 🙂

For start, let’s see 2 proper ways to load assets in Magento 2.
In this example, you can see how assets are loaded on two different Magento pages – Homepage and product page.

As you can see on example above, requireJS loaded different assets through different pages while all bundles were loaded regardless of wether they are needed or not.
With RequireJS, we can load specific JS modules on specific pages which means you will load only necessarily assets and reduce number of requests.

(If you wish to know more about how to load Javascript files with RequireJS, you can read this excelent article from my coleague Domagoj Potkoc. )

While RequireJS did help us with reducing number of requests through the page, we still have few JS files being loaded and “few” in Magento 2 means 40-50. We need a way to merge those assets into just few files. We need to create 5 files out of 50.
Files are being loaded asynchronously but still, if we could merge these files into just few, we could improve performance even more.
Bundling comes as a problem solver since it is used to merge modules and it dependencies into single file.
Bundling denies main benefit of using module loaders such as RequireJS since assets aren’t loaded asynchronously. Bundles are included with <script> tag, inside <head> section of the page.

So, why use it?

With bundling we could decide on where to load specific bundle and that’s the best part of it! For example, we want to put all checkout-related stuff into one bundle and load it only on checkout page!

Feeling happy?

Well, Magento folks didn’t implement RequireJS optimizer which is required for ordering and organising module load across different pages. You can exclude some scripts from bundle but you CAN’T decide on bundle content for a specific page.

So, why use it?

With Merging, you can only merge Javascript files that are NOT being loaded through RequireJS. If you wish to “merge” JS modules, you will have to use bundling.

You probably have so many questions in your head right now. So do I. And I’m still searching for the answers.

Here is a random Zen image, just to chill you down.

Bundling misses key features (imho) but you can still change few things in order to organize your bundles.

Bundle size and exclude list

In Vendor/Theme/etc/view.xml you can change bundle size and exclude some scripts from bundle.

Default size for bundle is 1MB.
Bundle size determines number of bundles that will be created. For example, if you have 4MB of script files and bundle size is set to 1MB, you will have 4 bundles created.

If number is too low, you will probably have 10 and more small bundles which will block each other during rendering so be careful with this.
Remember that bundles are not loaded asynchronously.

We can also exclude certain scripts from bundles. They will be loaded with RequireJS when needed.
Keep in mind that Luma and Blank themes have their own exclude lists and if you are not properly fallbacking and don’t have your own exclude list, bundles will be huge since all JS modules will be bundled, regardless of whether you need them or not.

<exclude> handle takes care of files that should be excluded from bundle. As far Magento 2 is concerned – since we can’t decide on bundle content for each page, at least we can exclude assets that will not be required through the whole site, therefore bundles will consist only files that are required everywhere. As you can see in above example (Luma theme exclude list), jQuery assets are probably required everywhere so i don’t understand idea behind excluding these from bundles. Probably Magento folks wanted to leave most important stuff under RequireJS control.

Activating bundling

After we have configured our bundle size and exclude list, it is time that we turn bundling on and enjoy great performance impact.

We can turn bundling on here: Stores > configuration > advanced >developer

After bundling is ON, clear cache and switch to production mode (bundling will not work in “Developer” mode). Static files will be deployed and you will see your bundles being loaded on the Frontend.

What about performance?

We did a lot in order to reduce number of requests through the site. But, there is one small problem with performance.

This is Homepage of the Luma theme.
Testing was done on Magento 2.2.0 “dev” version, with following setting in Dev console (Chrome):

Before turning bundling ON:

  • Number of JS requests : 137
  • Size: 2.0MB
  • Loadtime: 9.46sec

With bundling turned ON:

  • Number of JS requests : 8
  • Size: 4.2MB
  • Loadtime: 20.12sec

Take a look at size and load time?
We did reduce the number of JavaScript files being loaded, but the total filesize of generated bundles is larger than the total filesize of all non-bundled JavaScript files on the frontend.
Reason? With RequireJS you load only needed JS files on a specific page. Bundling merges all JS  assets and serves them on all pages.

Conclusion

I must say I am disappointed with bundling, especially with the fact that we don’t have RequireJS optimizer by default. Without it, whole idea behind bundling is missed.

Pros:

  • Bundles multiple files into single file

Cons:

  • negates benefits of using module loader
  • filesize of bundles is larger than all non-bundled javascript files size in total (per page)
  • you can’t define bundle content precisely
  • you can’t decide in which pages which bundle will be loaded

I don’t see any reason we should use bundling right now.
Bundling will make sense in the future if Magento folks create additional functionalities for us to use.
We need configuration file where we will be able to decide on :

  • number of bundles
  • bundle size
  • bundle content
  • which pages specific bundle will be loaded

Thanks for reading and i hope we can roll up some discussion about this topic in comments 🙂

The post Javascript Bundling in Magento 2 appeared first on Inchoo.

]]>
http://inchoo.net/magento-2/javascript-bundling-magento-2/feed/ 10
Experiences of running Magento 1 on PHP 7 http://inchoo.net/magento/experiences-running-magento-1-php-7/ http://inchoo.net/magento/experiences-running-magento-1-php-7/#comments Tue, 18 Apr 2017 11:29:50 +0000 http://inchoo.net/?p=29318 How time flies! It’s been almost a year and a half since we released Inchoo_PHP7 extension for Magento 1 (https://github.com/Inchoo/Inchoo_PHP7), and announced that in a blog post (http://inchoo.net/magento/its-alive/). We were just scratching our own itch – wanting to use all the performance benefits PHP 7 brought to run Magento faster. But, as the cover image...

The post Experiences of running Magento 1 on PHP 7 appeared first on Inchoo.

]]>
How time flies! It’s been almost a year and a half since we released Inchoo_PHP7 extension for Magento 1 (https://github.com/Inchoo/Inchoo_PHP7), and announced that in a blog post (http://inchoo.net/magento/its-alive/).

We were just scratching our own itch – wanting to use all the performance benefits PHP 7 brought to run Magento faster. But, as the cover image of that post inadvertently prophesied, we created a monster that escaped, and is continuing to terrorize the villagers to present day.

So, what happened in the meantime?

  • M1 is still going strong. M2 will take over eventually, but, in my humble personal opinion, that’s going be a very slow process.
  • On the other hand, PHP 7 is overtaking PHP 5 much quicker than previous versions used to replace their predecessors. (https://seld.be/notes/php-versions-stats-2016-2-edition). This is logical, because it really brings huge performance improvements, and it really is quite compatible, considering the major version number change.
  • Magento core became PHP 5.6 compatible in 1.9.3.x. Inchoo_PHP7 became PHP 7.1 compatible.
    • But, believe it or not, my humble personal opinion is that it’s better to run Magento on PHP 7.0 than 7.1 (https://github.com/Inchoo/Inchoo_PHP7/wiki/RunningOnPHP7.1).
      It’s really difficult to say, but I guess there are hundreds of M1 sites powered by Inchoo_PHP. Just Inchoo alone built from scratch or upgraded double figure of sites to PHP 7. Community seems to be going strong with it too, so I think I can say that it is quite tried and true by now.
  • With help from community, we found and fixed a lot of edge cases, and can say quite comfortably that we can make pretty much any M1 site work on PHP 7 without a problem.
    • And, in the last release, we even created a testing shell script, which can be very useful to find and fix potential problems.
    • Just keep in mind that this still is, and always will be, a developer-level extension. It can be “install and forget” if you are lucky, but, it would be good to have someone knowledgeable to set up, test everything and fix any issues. We had clients who came to us just for this, and we were always able to help them upgrade to PHP 7. And I don’t even want to say you need Inchoo to set it up. There are a number of developers and agencies in the Magento community that can help you out.

What’s ahead?

  • Looks like M1 core will continue it’s evolution. Going to Composer install, PHP 7, full page cache on CE, etc. – these are all things that clients are demanding and Magento experts are able to provide. Whether someone likes it or not, the whole ecosystem is large enough to be able to live and evolve on its own, directed by market pressures, and not someone’s will.
  • 3rd party extensions are the part of code we can’t fix with our extension. So, whether you are a 3rd party extension creator, a client, an integrator or anywhere else in community, please help spread the awareness that it’s good for everyone to be PHP 7 compatible.
  • PHP 7.2 will remove Mcrypt extension, and M1 core is quite dependant on it. There are a few workarounds we already tried out, but it’s not going to be pretty. For the time being, stick to PHP 7.0, and you won’t have problems.
  • Personally, I can’t wait for the moment when I’ll be able to use PHP 7 features when programming Magento, not just it’s performance. Stronger typing, for one, should bring less bugs and a more secure system. But that is still in far future from now, unfortunately.

 

TL;DR;

M1 runs (much) faster on PHP 7. Quite easy to set up due to a 3rd party module Inchoo_PHP7. MIT license. Great community support.

If you are having issues with performance of your Magento store, feel free to contact us to check technical state of your shop!

See you around!

The post Experiences of running Magento 1 on PHP 7 appeared first on Inchoo.

]]>
http://inchoo.net/magento/experiences-running-magento-1-php-7/feed/ 1
Making FedEx api show shipping estimate http://inchoo.net/magento/make-magento-fedex-api-show-shipping-estimate/ http://inchoo.net/magento/make-magento-fedex-api-show-shipping-estimate/#comments Mon, 10 Apr 2017 12:42:56 +0000 http://inchoo.net/?p=29265 There always comes the time when shopkeeper decides that he want’s to inform his customer of shipping estimate on checkout, so they could know approximately when they will get their goods. And for that, many shops today rely on API-s like ones from USPS or FedEx. Both of which are available for Magento. In this article...

The post Making FedEx api show shipping estimate appeared first on Inchoo.

]]>
There always comes the time when shopkeeper decides that he want’s to inform his customer of shipping estimate on checkout, so they could know approximately when they will get their goods. And for that, many shops today rely on API-s like ones from USPS or FedEx. Both of which are available for Magento.

In this article I will be showing you how to override FedEx carrier to return shipping estimate for given rates.

Overriding carrier

FedEx carrier works by sending request with given flags to FedEx server, who then, based on given flags, prepares response. After Magento receives response, it parses each rate as “Mage_Shipping_Model_Rate_Result” which he will later pass on to “Mage_Sales_Model_Quote_Address_Rate”. From which we will be able to access in template to show in frontend. For that, we will first override “Mage_Usa_Model_Shipping_Carrier_Fedex”.

Adding a flag for api

For FedEx API to know that it needs to return shipping estimate it needs to be given a “ReturnTransitAndCommit” flag. If we look into “Mage_Usa_Model_Shipping_Carrier_Fedex::_formRateRequest()” method, we will see that all it does is prepare flags into an array before it is sent to be parsed into request.

All we need to do here is rewrite original method and at the end of the array add our own flag.

Like in given example:

public function _formRateRequest($purpose)
{
   $ratesRequest = parent::_formRateRequest($purpose);
   $ratesRequest['ReturnTransitAndCommit'] = true ;//Here we are adding flag
   return $ratesRequest;
}

Storing the response data

After we receive data from api it will be parsed into stdClass,and in that class we are only interested in ‘RateReplyDetails’ array, which holds all our rates and their details, including their shipping estimates.

What we need to do here is pass our shipping estimate data into rate that will be passed on. For that we will be rewriting “Mage_Usa_Model_Shipping_Carrier_Fedex::_prepareRateResponse()” method.

protected function _prepareRateResponse($response)
{
   $costArr = array();
   $priceArr = array();
   // Array in which to store timestamp
   $deliveryTimeStamp = array();         //
   $errorTitle = 'Unable to retrieve tracking';
 
   if (is_object($response)) {
       if ($response->HighestSeverity == 'FAILURE' || $response->HighestSeverity == 'ERROR') {
           if (is_array($response->Notifications)) {
               $notification = array_pop($response->Notifications);
               $errorTitle = (string)$notification->Message;
           } else {
               $errorTitle = (string)$response->Notifications->Message;
           }
       } elseif (isset($response->RateReplyDetails)) {
           $allowedMethods = explode(",", $this->getConfigData('allowed_methods'));
 
           if (is_array($response->RateReplyDetails)) {
               foreach ($response->RateReplyDetails as $rate) {
                   $serviceName = (string)$rate->ServiceType;
                   if (in_array($serviceName, $allowedMethods)) {
                       $amount = $this->_getRateAmountOriginBased($rate);
                       $costArr[$serviceName]  = $amount;
                       $priceArr[$serviceName] = $this->getMethodPrice($amount, $serviceName);
                       //Store timestamp into prepared array
                       $deliveryTimeStamp[$serviceName] = $rate->DeliveryTimestamp; //
                   }
               }
               asort($priceArr);
           } else {
               $rate = $response->RateReplyDetails;
               $serviceName = (string)$rate->ServiceType;
               if (in_array($serviceName, $allowedMethods)) {
                   $amount = $this->_getRateAmountOriginBased($rate);
                   $costArr[$serviceName]  = $amount;
                   $priceArr[$serviceName] = $this->getMethodPrice($amount, $serviceName);
               }
           }
       }
   }
 
   $result = Mage::getModel('shipping/rate_result');
   if (empty($priceArr)) {
       $error = Mage::getModel('shipping/rate_result_error');
       $error->setCarrier($this->_code);
       $error->setCarrierTitle($this->getConfigData('title'));
       $error->setErrorMessage($errorTitle);
       $error->setErrorMessage($this->getConfigData('specificerrmsg'));
       $result->append($error);
   } else {
       foreach ($priceArr as $method=>$price) {
           $rate = Mage::getModel('shipping/rate_result_method');
           $rate->setCarrier($this->_code);
           $rate->setCarrierTitle($this->getConfigData('title'));
           $rate->setMethod($method);
           $rate->setMethodTitle($this->getCode('method', $method));
           $rate->setCost($costArr[$method]);
           $rate->setPrice($price);
	   //Store timestamp into rate
           $rate->setDeliveryTimeStamp($deliveryTimeStamp[$method]); //
           $result->append($rate);
       }
   }
   return $result;
}

Almost done..

Before we start celebrating we will notice that we are still not getting that data in fronted. That is because we stored that data into “Mage_Shipping_Model_Rate_Result_Method” which is is not same type of rate as in template, where we have “Mage_Sales_Model_Quote_Address_Rate”. So we will additionally rewrite method “Mage_Sales_Quote_Address_Rate::importShippingRate()” and just pass our data whether its present or not.

public function importShippingRate(Mage_Shipping_Model_Rate_Result_Abstract $rate)
{
   parent::importShippingRate($rate);
   if ($rate instanceof Mage_Shipping_Model_Rate_Result_Method) {
       $this->setDeliveryTimeStamp($rate->getDeliveryTimeStamp());
   }
   return $this;
}

And we are done. Wherever you call for rates (in my case it was checkout/onepage/shipping_method/available.phtml – onepages shipping methods) fedEx api will provide shipping estimate.

Thanks for being with me and happy coding.

The post Making FedEx api show shipping estimate appeared first on Inchoo.

]]>
http://inchoo.net/magento/make-magento-fedex-api-show-shipping-estimate/feed/ 1
Wireframing a successful design for your online store http://inchoo.net/magento/design/wireframing-successful-design-online-store/ http://inchoo.net/magento/design/wireframing-successful-design-online-store/#respond Wed, 05 Apr 2017 10:33:18 +0000 http://inchoo.net/?p=29245 As designers, we’re often faced with a lot of questions about our process. We never just dive into design and bask in the glory of amazing typography and brilliant color schemes because without the phases that precede it – it just wouldn’t even begin to be possible. Beam me up, designer The worst feedback you...

The post Wireframing a successful design for your online store appeared first on Inchoo.

]]>
As designers, we’re often faced with a lot of questions about our process. We never just dive into design and bask in the glory of amazing typography and brilliant color schemes because without the phases that precede it – it just wouldn’t even begin to be possible.

Beam me up, designer

The worst feedback you can give to a designer is commenting how pretty the design is. We don’t aim for pretty, can’t learn from it and our clients aren’t always satisfied with just pretty. In the end, if our clients’ customers don’t see the use in the pretty that we’ve created – the webshop will fail at some point.

Our process includes mechanisms that minimise that risk and allow us to make informed decisions for a successful webshop. Before the actual design phase, we go through planning and wireframing.

At this point we answer some of the questions that even we can’t answer just by being designers. We have best practice knowledge and experience, but we need that custom touch. If we can take a sneak peek at your Google Analytics or Hotjar, we can answer everything from what resolutions we should be making our designs on (according to most visits on certain devices) to how descriptive we should make our labels (if the age group of your returning customers is a bit older).

Depending on if the project budget allows us, we’ll sit through Hotjar recordings to find out where the hiccups are on your current site and what user flows are actually not flowing so well. Our redesign or incremental changes need to perform better than that. The work becomes directed and concentrated like a tractor beam on a star ship. We want to pull your customers in, and do so pain free.

All work, some play

Along with all that analysing, we communicate with our clients – a lot.

We sit at our table a lot, drink coffee too much, headphones in ears – we look like we’re doing great in our own little world. The truth is there is always some communication going on. So. Much. Communication. Especially in the wireframing phase where a lot of relevant work gets done.

That’s great though – in some ways we, as designers, feel like the glue that keeps the project connected. We estimate the size of the project, provide input on what could work for the online store (through user flows and usability best practices) and we even get to draw it up. We get to experiment on features and be the ones to present it to the clients. Our wireframes, design layouts and quick prototypes are much more budget-friendly than actual implementation. All the while talking to frontend, backend, fellow design colleagues and of course – our clients.

There is a lot of dread amongst designers when it comes to client work and communication, but, we don’t think of this communication as trying to fight our client on every argument. Your client knows his business better than you, he knows the direction he wants to take it in – you need him just as much as he needs you, so, talk it out.

Say your client can’t visualise a wishlist flow or he doesn’t quite see how animating tiles on tablet will save vertical space and leave more room for products – wireframe it, prototype it, present it, resolve the issue.

                         

It’s not all black&white

This entire article went by without mentioning the latest design trends, without complaining how hard it is for us to get the exact design we have in our heads approved and how we’re missing proper tools to correspond to responsive demands.

The least of our problems is opening our tools and creating the design – it has its own challenges, but that’s on myself and my skills. It’s everything else – the planning, knowing the technology, the communication – that ends up being some of the hardest work and our best efforts in creating meaningful experiences and a successful online shop.

The post Wireframing a successful design for your online store appeared first on Inchoo.

]]>
http://inchoo.net/magento/design/wireframing-successful-design-online-store/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/#comments 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/ 2
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/ 9
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
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
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
How many Magento certified people do you know? http://inchoo.net/magento/magento-certifications/ http://inchoo.net/magento/magento-certifications/#comments Mon, 02 Jan 2017 11:29:40 +0000 http://inchoo.net/?p=28388 How many Magento certified people are out there? 5,288 to date, according to the official data at everyone’s disposal. What about their distribution – where do they come from, what countries have the most certified individuals? In this post you’ll find an overview of one interesting metric – Magento certified individuals per capita. Who tops...

The post How many Magento certified people do you know? appeared first on Inchoo.

]]>
How many Magento certified people are out there? 5,288 to date, according to the official data at everyone’s disposal. What about their distribution – where do they come from, what countries have the most certified individuals?

In this post you’ll find an overview of one interesting metric – Magento certified individuals per capita. Who tops the charts and where does your country stand? Read on to find out.

We’ve seen many different rankings in the Magento world, and thanks to Ben Marks and one of his recent tweets, I took it upon myself to dig just a bit deeper around the number of Magento certified people (totals include developers and solution specialists) per capita.

The initial conversation was about whether Netherlands or Croatia are leading these rankings globally, but as it turns out after I took a closer look at the official numbers, there are actually countries doing way better than us.

Why per capita?

Ok, but why use this metric in the first place? Well, it’s rather difficult to compare countries using absolute numbers and if we combine absolutes with something like this (number of Magento certified people per 1M of general population in each country) and then analyze the data, we can see some interesting trends and put things in a different perspective.

For example, we can see how in some countries the efforts of several individuals and companies can really make a difference and impact their local communities – and this is exactly what the metrics in this case are showing or hinting at.

The methodology behind this one was rather simple and straightforward:

    1. Go to official Magento certification directory
    2. Select country after country and simply collect these figures
    3. Then use some type of comparable statistics on population per country (rather difficult to find as each country has their own methods but I chose this one – the data set used was of December 29th, 2016)

Limitations of this method

It’s impossible to see people who have opted not to be visible in the directory, so the numbers are not 100% representative of the real situation with certifications.

Another one is – there are people (app. 140 out of 5288 listed) who don’t have any country associated with them, so these have not made the numbers.

And finally – smaller countries can improve their position more easily. But then again, there are only two countries with less than 1M people in Top 50 so that’s not that big of an issue.

You can check out some of the hard data below, but for the sake of this post, I’ll focus more of the analysis on the thing I found most interesting – finding out just how many certified Magento people per 1M of the total population in each of the countries globally.

The “hard data”

  • there are 5288 certified individuals in Magento directory
  • they come from 73 countries
  • 7 countries have only one certified individual
  • 11 countries have more than 100 certified people
  • USA has the highest number of certified individuals – 849

Baltic countries top the charts

magento-certified-per-1m

While it may come as a surprise that Latvia is the one leading the pack, Scandiweb team have been doing an amazing job over there in the last years, getting a lot of their employees certified in the process, and building on the community in this Baltic country. We’ve also gotten quite fond of them as they sent the largest delegation to Developers Paradise 2016 in Opatija so we’re happy to see our Latvian friends in the significant lead with 41,4 Magento certified individuals per one million people.

In not that close second, we’ll find Malta. Now, this one is the only really debatable country to make it into Top 5 as it doesn’t make sense to say they have 30,3 people per 1M if they have the total of 13 people certified (and the total population of less than 500k). They are also the only country besides Montenegro to have at least one person certified and the population of less than 1M, so they do deserve to get some credit in any case.

We can either take them out of the chart or change the metric to certified per 100k people (which we can do, or not), but after them there’s another Baltic country, a Latvian neighbour – Estonia – with 20,5 Magento certified people per 1M.

Yes, it helps that these three countries topping the charts have 1-2 million people (or much less in case of Malta), but still – the smaller the country, the smaller the pool of people you can find those willing to dedicate their everyday work to a specific eCommerce software, so these numbers do hold value.

The Netherlands is hot and getting hotter

And then we have Netherlands – and hats off to them and everyone around an amazing community they’ve created as they are only one of two countries with >10M people who made it into Top 10. Can you guess the second one? Well, it wasn’t that difficult to spot Ukraine, right?

If you add to that one of the latest similar investigation by Willem de Groot (who many of you will know as the man behind MageReport) which shows that the Dutch are way ahead of everyone in number of Magento stores per 1M, it clearly demonstrates the strength and impact Magento and its community has had on the businesses in this country, and vice versa.

Not sure whether Magento’s official orange color had something to do with the ease of its penetration to this market, but let’s leave that aside for someone else to analyze 🙂

What about Croatia?

The reason why I started this was, of course, a chance to do some humblebragging as well, since we invested a lot of efforts of our own into Magento and local community of developers, and Croatia is in fact right there, in Top 5, with just over 12 Magento certified people per 1M, chasing after our friends from Netherlands – let’s see how the tables will turn in 2017. It would be really nice to see how our city of Osijek would rank on a similar chart if we compared cities globally, but that one is much more difficult to get right (proper data, cities vs metropolitan areas etc.).

More charts

For you who like to play around with numbers, here are some more charts to check out. Click on the thumbnails for full images.

A) USA (849) holds a slim lead over India (804) in the total number of certified individuals, and these two together take over 30% share of the global total of all Magento certified people.
magento-certified-per-country

B) European countries hold almost half of all certificates globally, while having just 10% of the global population.

C) Magento certified per 1M – a list of Top 25 countries
magento-certified-per-1m-top25

So, what now?

If you’re Magento certified yourself, jump on to the directory to check if you have a city/country listed next to your name to see if you made these numbers and helped your country out 🙂 If there’s something missing, there’s always the FAQ to help you sort it out.

And let’s all keep an eye out on these numbers and use them to further improve on the co-opetition within Magento ecosystem.

With these metrics, chances are we won’t have that much influence on the total population numbers, but we can all pull our weight when it comes to the certifications – we’re already preparing some of the Inchooers for new exams to get closer to and, hopefully, surpass our Dutch friends soon 🙂

Keep them coming!

The post How many Magento certified people do you know? appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-certifications/feed/ 6
How to set up a CDN (Amazon CloudFront) in Magento http://inchoo.net/magento/set-up-cdn-in-magento/ http://inchoo.net/magento/set-up-cdn-in-magento/#comments Wed, 14 Dec 2016 12:32:58 +0000 http://inchoo.net/?p=28088 If you are using Amazon AWS CloudFront and you want to set up CDN in your Magento, this tutorial is going to take you step by step on how to set up your CloudFront and Magento to get it both working. STEP 1 – DISTRIBUTIONS The first thing you have to do is to set...

The post How to set up a CDN (Amazon CloudFront) in Magento appeared first on Inchoo.

]]>
If you are using Amazon AWS CloudFront and you want to set up CDN in your Magento, this tutorial is going to take you step by step on how to set up your CloudFront and Magento to get it both working.

STEP 1 – DISTRIBUTIONS

The first thing you have to do is to set up Distribution on CloudFront. Below you can see an example of my custom Distribution.

CloudFront Distributions

This is how general options should look like, of course you can edit them to suit your needs.

CloudFront general tab

As you may see here:CloudFront certificate

Amazon’s CloudFront uses its own certificate so if your site uses SSL you can just use CloudFronts’s domain name such as “https://xyz13zxy.cloudfront.net”. You can also use custom CDN url with SSL such as “https://cdn.yoursite.com” but then you’ll have to import your own certificate.

Two very important options you need to be careful about are:

Alternate Domain Names (CNAMEs): this is used if you want nicer URLs for your content. So for example, if your CNAME is “cdn.yoursite.com” you will have to do some configuration in your cPanel of your own server to make it work and instead of, for example “http://www.yoursite.com/images/image.jpg” it’s going to be “http://cdn.yoursite.com/images/image.jpg”

Domain Name: This is a domain name Amazon generates for you, for example: “xyz13zxy.cloudfront.net”. If you don’t care about URL for your CDN this is faster and easier way to set up CDN. So instead of, for example “http://www.yoursite.com/images/image.jpg” it’s going to be “http://xyz13zxy.cloudfront.net/images/image.jpg”.

So how Amazon knows which images to use if you are trying to access it via “xyz13zxy.cloudfront.net”. This is where the Origins come in.

STEP 2 – ORIGINS

Now you have to set Origins. Amazon’s official documentation says: “When you create or update a distribution, you provide information about one or more locations—known as origins—where you store the original versions of your web content. CloudFront gets your web content from your origins and serves it to viewers. Each origin is either an Amazon S3 bucket or an HTTP server, for example, a web server”.

Origin Domain Name: this is your source domain name, for example “www.yoursite.com

Origin ID: this is some ID you specify by yourself just to easily identify this Origin. It can be something similar to your CDN domain name.

Origin Path: it’s empty. Amazon’s official documentation says: “Optional. If you want CloudFront to request your content from a directory in your Amazon S3 bucket or your custom origin, enter the directory name here, beginning with a /. CloudFront appends the directory name to the value of Origin Domain Name when forwarding the request to your origin, for example, myawsbucket/production.”

CloudFront Origins

CloudFront Origins

STEP 3 – BEHAVIOURS

The next thing you have to do is to set Behaviours. Amazon’s official documentation says: “A cache behaviour lets you configure a variety of CloudFront functionality for a given URL path pattern for files on your website. For example, one cache behaviour might apply to all .jpg files in the images directory on a web server that you’re using as an origin server for CloudFront.

When using CDN sometimes you might get an error on your website similar to this one: “Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://xyz123zxy.cloudfront.net/images/images.jpg This can be fixed by moving the resource to the same domain or enabling CORS.”

To fix this problem you have to set Behaviours.

Origin: It’s very important to choose the right Origin from the dropdown box. This is your Origin you set up in the previous step.

Whitelist Headers: choose “Origin” from the left box and click on Add >>. When you choose your other options click on Create/Edit.

Path Pattern: This is path to a file you want to allow CORS for. I’m using “*” so it matches all files and allows Cross-origin resource sharing.

CloudFront Behavior

STEP 4 – INVALIDATIONS

You might be wondering how and when the cache is going to be invalidated or cached again. By default, each object automatically expires after 24 hours.

From Amazon’s official documentation: To change the cache duration for all objects that match the same path pattern, you can change the CloudFront settings for Minimum TTL, Maximum TTL, and Default TTL for a cache behaviour. For information about the individual settings, see Minimum TTL, Maximum TTL, and Default TTL. To use these settings, you must choose the Customize option for the Object Caching setting.

If you want to invalidate cache for all files (which is not recommended) or just for one specific file, you have to set “Invalidations”.

Enter path to your own files and click on Invalidate.

CloudFront Cache Invalidation

STEP 5 – MAGENTO SETTINGS

This was CloudFront’s side, now it’s time to configure Magento’s settings. It’s really easy to configure Magento. The only thing you have to do is to configure Magento’s URLs. Screenshots below are using SSL (HTTPS) so I used “https://…” everywhere. If you don’t use SSL, but regular HTTP, then you should use ”http://…”.

As you may see, nothing has changed in Default settings.

Magento Website Settings

Choose your Website from the dropdown and change only fields where you want to set CDN URL. I chose to set CDN only for images and CSS files.

Magento Website Settings

Save your settings, clear your cache and test your website. How do you know if your site uses CDN now? Open your website (if you are using Chrome) and press CTRL and U on your keyboard to open the source code. If you can find something like “.cloudfront.net” or “cdn.yoursite.com” and all your content is loading properly that means CDN is set up properly.

Magento CDN code

The post How to set up a CDN (Amazon CloudFront) in Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/set-up-cdn-in-magento/feed/ 14
We want you on stage at Meet Magento Croatia! http://inchoo.net/magento/meet-magento-croatia-speakers/ http://inchoo.net/magento/meet-magento-croatia-speakers/#comments Fri, 09 Dec 2016 13:32:26 +0000 http://inchoo.net/?p=28329 Thinking about whether you should join us for Meet Magento Croatia? If you haven’t thought about it by now, you should definitely start! Here’s why… Meet Magento Croatia is part of the global family of Meet Magento events which gathers eCommerce experts from all over the world. One, two or three-day events serve as the...

The post We want you on stage at Meet Magento Croatia! appeared first on Inchoo.

]]>
Thinking about whether you should join us for Meet Magento Croatia? If you haven’t thought about it by now, you should definitely start! Here’s why…

Meet Magento Croatia is part of the global family of Meet Magento events which gathers eCommerce experts from all over the world. One, two or three-day events serve as the best possible connection platforms for getting all the latest info from the Magento world.

And not only that – they are the perfect place where eCommerce trends are discussed and hot Magento topics tackled. Developers Paradise we organized in April proved, once again, that events channel the power of community. In fact, that’s what makes them special!

How can you join?

By signing up, of course. We’d like to see you as a speaker or, alternatively, as a delegate. We know many of you have a lot of miles in your Magento shoes and we’d like to hear all about the roads you’ve been on. It’s no secret you’ve breathed life into numerous web shops – now it’s the time to shine and share with community how you did it.

mm17hr-tekst

We are interested in hearing your story (inside out)!

Along the way, you overcame different obstacles and bridged various challenges – by sharing your story, you are empowering the community to grow. We all want to improve, we would highly appreciate you telling us how exactly you are doing it – day in, day out.

The only thing is…

There’s not much time left to apply so you better hurry!

Final deadline for getting your applications in is December 31st. We’ll make sure to review all of your applications and get back to you as soon as possible. At this point, we can promise you, if you make it to the #MM17HR stage, you’ll be around some pretty amazing Magento and eCommerce experts. 😉 We’ll be dropping names soon, so make sure to stay tuned to see who we’ll host.

So, come on – tell us what you did, and more importantly, how did you do it and we’ll get you on the MM17HR stage. 

See you in Osijek, Croatia, home of Inchooers!

The post We want you on stage at Meet Magento Croatia! appeared first on Inchoo.

]]>
http://inchoo.net/magento/meet-magento-croatia-speakers/feed/ 2