Inchoo » Magento http://inchoo.net Magento Design and Magento Development Professionals - Inchoo Mon, 31 Aug 2015 11:19:30 +0000 en-US hourly 1 http://wordpress.org/?v=4.2.4 Magento 2 Luma Theme Under The Scope http://inchoo.net/magento/design/magento-2-luma-theme-under-the-scope/ http://inchoo.net/magento/design/magento-2-luma-theme-under-the-scope/#comments Wed, 26 Aug 2015 13:36:33 +0000 http://inchoo.net/?p=24767 Magento 2 brings new default theme under the name “Luma”. Luma is a very clean, easy on the eyes and elegant theme that has adopted better usability practices than it’s predecessor, Magento 1 default “Madison Island” theme. In this blog post I’ve prepared an overview of the main differences between these two themes and the crucial UX...

The post Magento 2 Luma Theme Under The Scope appeared first on Inchoo.

]]>
Magento 2 brings new default theme under the name “Luma”. Luma is a very clean, easy on the eyes and elegant theme that has adopted better usability practices than it’s predecessor, Magento 1 default “Madison Island” theme.

In this blog post I’ve prepared an overview of the main differences between these two themes and the crucial UX issues that were found in Beta version of Luma theme. As Magento 2 is still in the development phase, this article will be updated when some major changes occur.

Homepage

One of the main differences between Madison Island and Luma themes, aside of updated look and feel is the Homepage where substantial changes have been made and that especially goes for carousel that has been replaced with tiles.

Why is stepping away from carousel a step forward to better usability?

Many studies of Homepage carousels have proved that they are completely ineffective and anything beyond the initial view has a huge decrease in visitor interaction.

Carousels are one (of many) methods of displaying information on a homepage whose goal is to catch the user’s eye and showcase information that merchant wants to emphasize. Because carousels at first give a visual boost to the homepage, are fairly easy to implement and can show a lot of information without sacrificing screen real estate, most merchants use them as a means to communicate new information to users. Unfortunately, most carousels fail to follow basic accessibility and usability guidelines which effectively renders them useless.

One of the issues with a carousel is automatic advancement of slides which reduces accessibility. Low literacy users or international users who may read more slowly if the store is not in their native language and users with motor skill problems may have difficulties using the carousel. Also, users’ cognitive load increases with each slide as there is no indication of what content is on next or previous slide, forcing them to remember each slide’s position.

Luma solves those issues with tiles. Tiles are a great solution as you can display more banners at the same time without forcing users to remember on which slide they saw the content that appealed to them. Even better, users don’t have to deal with carousel nor its controls, making their experience and search for information easier.

desktop-tiles

Luma tiles seen from higher resolution displays

While desktop tiles mostly look clean and nice, if you take out middle tiles where readability is slightly lower, text and layout on other resolutions are not properly scaling towards smaller screen sizes.

Below is the picture of tiles on 768px where you can clearly see how issues arise. Example; white space appears below the second tile (tile with “20% OFF”) and the title is not fully visible. Readability of “Take it from Erin” tile decreases as there is not enough contrast between the text and the background. Padding of last two text containers breaks.

tablet-tiles

Luma tiles seen from medium to low resolution displays

On 480px you can easily notice issue with inequality of the tiles’ width. That inequality creates inconsistency and makes it difficult for users to track content.

Here’s an example:

“Luma” tiles seen from mobile displays

Luma tiles seen from mobile displays

Since Luma and Magento 2 are still in Beta, we can clearly see that there is work left to be done regarding responsive images and responsive typography.

Next difference between the themes is the area for Featured products on the Homepage. On Madison Island theme, products are presented in 1 row with small image thumbnails (155px x 155px).

"Madison Island" product listing

Madison Island theme product listing

In Luma, images are larger (195px x 243px), but products are displayed in 2 rows. If the second row is not completely filled with products (and that would mean you’d need to have 10 products showcased instead of current 7), a visual gap is created and eye loses focus. Example is in the image below:

"Luma" product listing

Luma product listing

There are 2 solutions to fix that:

  • Restrict the number of products to 5 or 10 so the whole row is filled and a gap is avoided.
  • Implement a small slider with pagination controls that appear inside of it.

Another thing with the Homepage, most specifically in the navigation area, there is another usability issue – users do not have a clear visual indicator of a dropdown menu which shows that a certain category has its subcategories, not just the link to it.

Without visual dropdown indicators, users have to resort to guesswork and method of trial-and-error. They can only see subcategories if they hover over the specific link in navigation. Solution for this would be to include a small dropdown indicator, like a downward-pointing arrow to inform the user that there is more than one layer of navigation available.

Example of Luma’s navigation.

Example of Luma’s navigation.

Category Page

Great improvement is accomplished on the Category page. Luma’s Category page now has clean lines which makes it easy to follow the page, products are in focus and images are big enough so users should not have troubles identifying the product and see its details even from thumbnails (226px x 282px).

Main difference between the themes, and also a big improvement, is the product listing. In Madison Island the default width is 680px and Luma expands that to 980px. Because of that extra 300px, product image thumbnails are more prominent.

Why is that important?

Users are attracted to high-quality product images and they are extremely important to online shoppers. Using the right images can boost your site’s conversions and get you to connect better with your target audience.

Even though the category page is clean, there are still some issues to be solved.

First issue is with certain resolutions between the breakpoints. On resolution range from 640px to 768px filters are not on the left sidebar as is the case with other resolutions. Instead, they are placed below the product list, making them difficult to find. That means that users have to scroll through all products and then narrow down the choice with filters.

Next issue is with the “Add to Compare” option. If a user selects several items for comparison and decides to clear the list, he is taken to the previous page he visited. It means that if a user was on the Homepage before clearing compared items from the list, it will take him there and possibly lead him away from the desired category. Aside from that, the icon for comparison is not clearly representing its purpose.

Product Page

Product page is very clean and understandable, with focus on the product image and with clear call to action and product details.

One thing that could be improved is the notification that shows when a user adds the product to the cart. Instead of displaying notifications at the top of product page, they should be placed near the area that user interacted with – “Add to Cart” button. This way, we will display right information to the user without creating additional friction by displaying notifications outside of that area, forcing users to ask themselves if the product was actually added to the cart or not.

Cart and Checkout

Both are very simple, intuitive and easy to navigate.

On the Cart page, within “Discount Code” area, there is no information how to get a code and customers could feel that they are overpaying when they see a coupon code field for coupon that they do not have and do not know how to obtain. Because it’s so obvious others are getting a discount, some might feel it’s unfair that they aren’t getting a discount as well. A better solution would be to change the wording a bit and add a simple link saying “Have a coupon code?” which would display the form field and an “Apply Coupon” button would be shown only after the click.

Second solution would be to include link “How to get a coupon code” next to the “Discount Codes” area and display a modal window with those information on click, so users don’t need to leave the cart.

Required fields label on the checkout is marked with a red colored asterisk (*). Research proved that most users are still not sure if the field without an asterisk is optional or not. Solution that would be of bigger benefit to the user is to write “Optional” to indicate optional fields, possibly in a slightly grey tone, in parentheses at the end of the label.

Conclusion

Magento 2 Luma is a great improvement compared to it’s Madison Island predecessor featured in latest versions of Magento 1.x. Adopting modern design trends and implementing them in current flow that should serve as a starting point for merchants is not an easy task and there are still details to be polished before the stable version is released to the public.

However, we can tell this early on that the general look and feel, as well as the performance of this theme shows promising future for Magento’s frontend.

While using the default theme can be a good starting point for the merchants, it is not for everyone and it certainly doesn’t cover specific needs a business will have.

Building a custom theme specifically for the needs of merchants and their customers across different industries is where we excel at, and we can help you create a completely custom solution or evaluate your current site’s design and usability if you are looking to improve your user experience, so why don’t you get in touch to see what we can do for you?

The post Magento 2 Luma Theme Under The Scope appeared first on Inchoo.

]]>
http://inchoo.net/magento/design/magento-2-luma-theme-under-the-scope/feed/ 0
Shell script for converting configurable to grouped products http://inchoo.net/magento/shell-script-for-converting-configurable-to-grouped-products/ http://inchoo.net/magento/shell-script-for-converting-configurable-to-grouped-products/#comments Thu, 20 Aug 2015 11:53:48 +0000 http://inchoo.net/?p=24751 A couple of weeks ago my colleague Attila Fabrik and I had a really complex but interesting task. We had to develop a shell script that converts configurable products to grouped products. Our Magento catalog was special. It contained mostly configurable products and it seemed that using configurable products was a good idea 4 years...

The post Shell script for converting configurable to grouped products appeared first on Inchoo.

]]>
A couple of weeks ago my colleague Attila Fabrik and I had a really complex but interesting task. We had to develop a shell script that converts configurable products to grouped products. Our Magento catalog was special. It contained mostly configurable products and it seemed that using configurable products was a good idea 4 years ago when the shop was set up but the current business conditions were pushing us to look for other, more flexible options.

The script can be downloaded, cloned or installed via MODMAN from: https://github.com/ceckoslab/convert-configurable2grouped

Below I am showing a configurable product that is about to be converted and the grouped product that is the result of the converted configurable product. Basically, the main difference lies in the manner in which “add to cart” form is rendered.

Configurable product:

Configurable add to cart form

Grouped product:

Grouped add to cart form

Why convert?

So far so good, but you are probably wondering why we’ve ended up with this crazy idea to convert configurable products to grouped products?

1. We needed better control over the prices we showed on the configurable product page.

It’s well known that if we set a special price on an individual simple product, then this price is not shown on the configurable product page and it’s not used when the configurable product is added to the cart – but, this is exactly what we wanted. Having grouped products allowed us to have better price control because we’ve always wanted to use the prices of the linked simple products. As we know, price calculation of configurable products works in a different way. The only problem was that the grouped product renders the simple products in a table rather than in a select box. Changing the rendering from table to select box required minimal front-end work effort, so I am going to skip the explanation how we did it.

We had an extension installed that solved the pricing problems mentioned in point (1) but this extension was rewriting many classes and had many collisions with other extensions. We just wanted to make our shop use as many core features as possible, be more robust and less complex.

But again, why convert?

Why not delete the configurable products and import the same product data but for the grouped product type?

2. We wanted to keep the original products IDs!

Those IDs existed in product review tables, the URL rewrite table, external systems, product list widgets, static blocks, etc. Of course we could delete the configurable products and preserve their IDs for the next grouped products import, but this task was also time-consuming and could require the same amount of effort as converting the products.

What DB table operations are done while running the script?

catalog_product_entity:

  • updates type_id value from configurable to grouped
  • updates has_options to 0
  • updates required_options to 0

catalog_product_link:

  • creates required links between grouped and simple products

catalog_product_option_*:

  • deletes custom options because grouped products don’t have custom options

catalog_product_super_link and catalog_product_super_attribute:

  • deletes all rows related to the converted configurable product

catalog_product_entity_*:

  • deletes EAV catalog attribute values that were valid for the configurable product but are not required for the grouped product

catalog_product_link_attribute_int:

  • creates records that describe how the simple products are positioned on the grouped product page (ordered from lowest to highest price)

Usage:

  • php shell/convert-configurable2grouped.php convert_all

* After the script completes execution, you will see an information table about which products have been converted.

Reindex the Magento catalog

Example output after the script was run:

Screen Shot 2015-08-13 at 17.48.39

Room for improvement:

So far the script converts all configurable products and doesn’t offer the option to convert a predefined list of configurable products. Basically, I don’t have a problem with this because I really want to convert all configurable products but if you want to use a list of products, please modify the function CeckosLab_Convert_Configurable2Grouped::_getConfigurableProductIds()

The script doesn’t have a function for dry-running in order to show which products are going to  be converted but if the community is interested, we could add such functionality.

The script removes a fixed number of eav attribute values that are redundant for grouped products. Those attribute codes are currently hardcoded inside the shell script because it works with the default attribute sets. The script doesn’t cover the cases where the catalog contains custom created attributes that are associated individually for configurable products but are not available for grouped products.

Special thanks:

Many thanks to my good friends from Inchoo who made it possible for this article to reach many Magneto community members! Many thanks to my colleague Attila Fabrik who made the proof of concept to this script! Many thanks to my Magento Community friend Alexander Turiak who made a very good code review of the shell scrip and suggested that I should show some visual feedback after the scrip gets executed.

If you would like to read more of my Magento articles, please visit my blog.

Your thoughts?

The post Shell script for converting configurable to grouped products appeared first on Inchoo.

]]>
http://inchoo.net/magento/shell-script-for-converting-configurable-to-grouped-products/feed/ 0
MageMeter – a new way to present and share Magento performance benchmarks http://inchoo.net/magento/magemeter-a-new-way-to-present-and-share-magento-performance-benchmarks/ http://inchoo.net/magento/magemeter-a-new-way-to-present-and-share-magento-performance-benchmarks/#comments Wed, 29 Jul 2015 11:55:41 +0000 http://inchoo.net/?p=24565 When Marko Martinović started working at Inchoo, we asked him what he likes to do in his free time. Enthusiastically enough, he replied: ”I write some code.” That was a bit over two years ago. He has been a member of the Inchoo family since April 2013 and today, we talked about his new project...

The post MageMeter – a new way to present and share Magento performance benchmarks appeared first on Inchoo.

]]>
When Marko Martinović started working at Inchoo, we asked him what he likes to do in his free time. Enthusiastically enough, he replied: ”I write some code.” That was a bit over two years ago. He has been a member of the Inchoo family since April 2013 and today, we talked about his new project – MageMeter. All that ”I write some code.” wasn’t only talk, he made a catalog of Magento and Magento2 benchmarks metered using official Performance Toolkit.

We couldn’t restrain ourselves from asking a few questions and getting more info on the subject.

Hi Marko! For starters, tell us a bit about your role at Inchoo at the moment.

Marko: Hi all, at the moment I lead growing team of talented Magento developers, with goal of advancing assigned projects and improving development practices wherever possible. I also lead development efforts for one of valued Inchoo clients Zee&Co Online Limited, more precisely I coordinate development activities for fashion retail store Zee&Co.

​How did you choose the name of the team?

Marko: Actually picking the name was a team effort. Since all the teams at Inchoo are named after cities, we simply sat in front of Google Maps and each member selected his own list of candidates. Winner turned out to be “Echo” after small city of Echo, Oregon, United States and was selected by voting. I must point out that any resemblance to one of the basic PHP programming language constructs is purely coincidental :)

You were one of the speakers at MM14PL with a topic “Review of Magento 2 Caching Features”. You criticized performance results at that time. What changed in the last 9 months?

Marko: According to Magento2 Github repository, between 0.1.0-alpha101 I first benchmarked and what we have right now in master branch, 31.052 files changed with 2.079.084 insertions and 1.284.890 deletions :) Joking aside, there are couple of areas like the new DI compiler or caching changes we can blame for most of the improvement, but it’s actually the combination of everything that went into repository this year, what made Magento 2 couple of times more performant now when compared to what I tested for my Meet Magento talk in Poland. I like to believe that my constructive critique assisted Magento 2 development team with directing some focus to performance and scalability aspect of Magento 2.

Tell me about your MageMeter.com personal project. You’ve been working on for the last few weeks. What’s the story behind it?

Marko: Yes, MageMeter.com is alive and kicking for some time now, main goal was to make it easier for Magento community to present Magento and Magento 2 performance benchmark results. While preparing my talk for Poland and Berlin, I found it difficult to visually present and compare jMeter based Magento benchmark results. At the time there was no specialized tool for the task, and doing it all using Google Spreadsheet charts required a lot of manual labour in order to aggregate all the data from CSV file, into meaningful comparison chart.

With MageMeter.com, one can simply upload CSV file produced by jMeter during benchmark, and all the data will get aggregated on upload allowing visual presentation of benchmark results in the form of bar charts, as well as up to 4-way comparison with other benchmark results already uploaded to MageMeter.com. Just what I needed couple of months ago 😉

Technical background for those interested, MageMeter.com was built with latest versions of Laravel framework in the backend, and Twitter’s Bootstrap for the responsive frontend.

Can community contribute to the project?

Marko: Of course! Although starting idea was to solve my own issue, we all know that real strength of Magento is in it’s community. That’s why I built MageMeter.com as data presentation platform for all Magento performance enthusiast, as well as hosting companies and development agencies out there. One can simply do the benchmark using official Magento Performance Toolkit, log into MageMeter.com to upload resulting CSV file, and MageMeter.com will do everything else required for quality presentation/comparison of these benchmark results on wide variety of devices. And it’ll do all that completely free of charge.

What Marko created seems to be both helpful and yet another proof that Magento has a strong community surrounding it. Using the catalog should be easy – have you tried it already?

The post MageMeter – a new way to present and share Magento performance benchmarks appeared first on Inchoo.

]]>
http://inchoo.net/magento/magemeter-a-new-way-to-present-and-share-magento-performance-benchmarks/feed/ 0
Should encrypted Magento extensions be allowed on the new Magento Connect? http://inchoo.net/magento/encrypted-magento-extensions/ http://inchoo.net/magento/encrypted-magento-extensions/#comments Mon, 06 Jul 2015 11:41:09 +0000 http://inchoo.net/?p=24448 A heated Twitter debate is under way over what Magento should or should not allow in the new, updated Connect, and it was started by Magento themselves. The plans are that new Magento Connect will focus on the quality of extensions listed and will, ultimately, become a much more merchant- and developer-friendly place where everyone...

The post Should encrypted Magento extensions be allowed on the new Magento Connect? appeared first on Inchoo.

]]>
A heated Twitter debate is under way over what Magento should or should not allow in the new, updated Connect, and it was started by Magento themselves.

The plans are that new Magento Connect will focus on the quality of extensions listed and will, ultimately, become a much more merchant- and developer-friendly place where everyone around Magento ecosystem will run to, not from.

The biggest question seems to be – should encrypted Magento extensions be allowed? Let’s check the story behind it, pros, cons, and in-betweens and try to come up with a solution, or figure out what Magento will decide.

So, what’s the whole fuss about?

If you’re not that familiar with Magento Connect (really? so what are you doing here exactly?), it started out as a Magento “app store”, or a place where anyone involved with Magento implementations can find extensions that will give them that extra piece of functionality needed on top of already a robust system that is Magento.

The issue with it, as it was with Android Market as opposed to App Store, was that developers were able to publish any type of extension without any scrutiny, quality or security check – and you can imagine the “diversity” we had there.

Extensions extravaganza

Working directly with merchants as a Solution Partner, you can imagine the things we came across and the stories our developers can share – dozens (in some instances hundreds) of extensions installed, completely messed up installations, websites that we had no idea how they’re still holding up.

With that said, we have used a number of high quality extensions and recommended a lot to our clients as there are plenty of great extension development companies out there who also offer top level support.

Open source, you say? We don’t think so.

Now, this is all good for “standard”, OSS extensions – but, there are some players in the Magento ecosystem who decided to go an extra mile with protecting their IP and have decided to encrypt their extensions – there are several ways to go about this, and one widely used, and such that most discussions are in fact about, is by using ionCube.

What this encryption does is protect certain files within a software (in this case a Magento extension) from being accessed and, ultimately, modified.

And that, my friends, is what open source community doesn’t like :)

Imagine a developer whose job and passion is solving problems. Now imagine that developer running into an ionCubed extension on a client’s site. Now imagine the frustration if that particular extension is causing an issue with the site and he can’t do anything about it.

Now imagine, on top of all that, a merchant who opted to go with Magento because it was open source and because the usual sales pitch is that you are free to modify it in any way and that you have a vast community of quality partners who can help you out.

But hey, there’s always support, right?

Yes and no. Granted, there are some companies that provide excellent support around their encrypted extensions and they can work together with solution partners and merchants. And there are those who can’t or simply don’t. And then everyone is stuck.

It started with a tweet…

The most recent debate was started by a tweet from Tanya Soroka, one of Magento’s product managers. And Magento should know by now that once you ask for opinion from a very vocal community, that’s exactly what you’re going to get.

tanya-soroka-tweet-encryption

As you can imagine, the discussion(s) quickly took a lot of twists and turns and it’s kind of difficult to keep track of everything that’s being said. So, I tried to sum it up.

Pros, cons and in-betweens

The majority of the recent controversy stems from two main “philosophies”, at least how I see them:

  1. Magento is open source, and should promote open source across its entire ecosystem (because it’s also a way to ensure no-one gets stuck with any particular vendor, beating the entire open source concept)
  2. Magento is a business and it should do what it deems necessary to ensure the majority of their stakeholders are happy, and basically let the market decide (and since the ecosystem around it has grown well beyond development companies, can’t we all just get along?)

Now, these two don’t need to be in a complete mismatch, but there are strong feelings from developers, solution partners, and many industry partners as well that encryption simply doesn’t play to anyone’s best interest (other than those encrypting the extensions, of course). The feelings around this option are in my mind stronger as they take into account the whole idea of the open source and the community, and bring it to a higher level.

Those opposing such an opposing attitude from an open source community do raise a valid point when they say that a community around Magento used to be (and should be) a little more inclusive. They say we shouldn’t force someone out of play just because they decided to protect their IP in a certain way. And they argue that ultimately the merchants will decide and move away from such providers who do not give them the proper support. This position is more business- and not community-driven.

Should this discussion really be about Unirgy?

Many people have mentioned Unirgy as a shiny example of good support and encryption that only covers a handful of files. And we’ve been in touch with them on several occasions and can confirm they are professional, responsive and willing to assist.

And I believe this is where most problems arise – everyone mostly mentions Unirgy – what about others? Are they the only ones who provide encryption AND good support? If so, we really do have a problem.

How shall Magento proceed?

First off, it’s great that they are including the community about a lot of new decisions they are making, especially around Magento 2. And this can, of course, be a double-edged sword with any business.

The product leadership should have a clear vision and strategy once they do take into account all different opinions, and I hope, actually I do believe the current team calling the shots around Magento product understands this.

Let’s face it, if they hadn’t asked this question and simply continued allowing all types of extensions, I don’t think there would have been so much fuss about this – yes, the developers and many solution partners, even merchants, would continue to be frustrated, but now this frustration can only get amplified is this policy remains unchanged.

There is already an “alternate” path many have suggested, and that would be that Magento allows encrypted extensions to be listed on Magento Connect, but ONLY after they have been

  • submitted unencrypted
  • scrutinized and approved by Magento
  • encrypted by Magento themselves

And since this option is advocated by some Magento people themselves, this is probably the way they will proceed – I would only ask for regular checkups of the support level and quality of support such authors are providing. They should be placed under additional scrutiny since they are not playing by the open source rules and this should be very clear to everyone.

ben-marks-tweet-encryption

Our “verdict”

As for our two cents, we don’t prefer those in-between solutions that try to make everybody happy as they usually end up making everybody feel the opposite.

We would prefer Magento takes this up as an opportunity to strengthen its position as an eCommerce open source leader and stand as an advocate for a true open source ecosystem. 

Everyone can still offer their solutions via other channels, but if Magento Connect is looking to be a creative platform for developers and something as close to a safe haven for merchants as possible, we see it happening only with OSS written all over it.

After all, Magento isn’t using any encryption for their product themselves and they’ve created a business model around it.

So, what’s your take? Feel free to share and let some steam off before the decision is made :)

The post Should encrypted Magento extensions be allowed on the new Magento Connect? appeared first on Inchoo.

]]>
http://inchoo.net/magento/encrypted-magento-extensions/feed/ 21
How to diagnose and fix blackhat SEO branded keywords hijacking? http://inchoo.net/online-marketing/how-to-diagnose-and-fix-blackhat-seo-branded-keywords-hijacking/ http://inchoo.net/online-marketing/how-to-diagnose-and-fix-blackhat-seo-branded-keywords-hijacking/#comments Fri, 22 May 2015 14:10:30 +0000 http://inchoo.net/?p=24338 Blackhat SEO branded keywords hijacking. Say whaaaat?! Oh, yes! Read about blackhat cloacking technique used mainly by scammers where your branded keywords are used to drive traffic to scam ecommerce sites.

The post How to diagnose and fix blackhat SEO branded keywords hijacking? appeared first on Inchoo.

]]>
Blackhat SEO branded keywords hijacking. Say whaaaat?! Oh, yes! Read about blackhat cloacking technique used mainly by scammers where your branded keywords are used to drive traffic to scam ecommerce sites. 

It all started with a large number of new external links pointing to one of our client’s websites and we’ll call them a “victim” from now on. 38 000+ links pointing to just one page, and there were plenty of others links pointing to other pages as well.  All in all – more than 500 000 new external links.

500 000+ backlinks

GWT incoming links

Links look fishy at first sight:

  • all links come from a domain of a topic completely  unrelated [bike parts/tools] to victim’s website [fashion retailer]
  • there’s a layer of redirection in the game
  • links redirect to a COMPETITOR’s website [in this case – a scam website with products related to victim’s website]
  • GET parameter resembles URL path structure of victim’s website
  • source domain is most likely hacked and being used as a host [infected host]

 

In this particular case, even Google is aware of the fact that the infected host is hacked:

Hacked infected host

 

It’s time for a bit of HTTP analysis… 

One URL is picked and analyzed:

capture-2015-05-22_008

As Google/GWT see it, it should have a link back to a valid victim’s URLhttp://www.example.com/brand-name.html

But instead, it redirects to a scam website [don’t buy anything from this SCAM site, you’ll see later why ;-)]:

http://www.stoneislandsshop.com/?keyword=http%3A%2F%2Fwww%2Esuperbiketool%2Ecom%2Fstois%2Fstois2015042107%2Easp%3Fmens%2Fadidas-black-stan-smith-trainer%2Ehtml

Of course, there are no backlinks to victim’s site on scam site. But how did Google got confused to properly index infected host’s URL and list that URL as a backlink?!

Let’s analyze redirects, or better say “redirects” step by step:

scam redirects

  1. [POST redirect] a link on infected host redirects using POST method to intermediate host [POST is important, as it stops Google from passing through]
  2. [CLOAKING] intermediate host [server side PHP script] then checks whether user-agent has “googlebot” – if so, it sets cookie. As page is rendered – it then redirects a client to a scam website using jQuery redirect [client side Javascript]  or to a Google is user-agent is “googlebot”
    <script src="js/jquery.1.4.4.js" type="text/javascript"></script><script>// <![CDATA[
    if(cuslocked){window.location.href="http://www.google.com/";}else{window.location.href="http://www.stoneislandsshop.com/?keyword=";}
    // ]]></script>
  3. “cuslocked” – cookie that signals whether agent is googlebot or not
  4. [REDIRECT to a SCAM site] HTTP code 200, again – probably to avoid being detected as 30x redirect(s)
  5. SCAM webiste is loaded

As you can see, no 301 or 302 redirects.

Now we can create a list of all parties involved in this scheme and connect the dots:

  •  Victim[this might be you! 😉]
  •  Infected host [superbiketool.com] [some random, poor hacked website]
  •  Intermediate host [www.2015fashionnews.pw] [another poor random hacked website, or incautious scammer’s as in this case – explained later]
  •  Scam site [www.stoneislandsshop.com]

 

WHY this particular cloaking technique and how does scammer benefit from it?

  • Scammer has set up a topic ecommerce website [brand fashion store that looks like a real one].
  • Scammer knows that there are stores that are known for having that particular brand [victim], and that there are users performing a brand keyword queries  on Google
  • Scammer scrapes and recreates the whole victim website [with cloaking]
  • Scammer creates redirects that point to scam sites [making sure that Google is not redirected]

 

Wanna know how it looks in practice?

  • Victim site, but that might be you as well] is well known for selling products of Stone Island fashion brand (among others)
  • Scammer has set up a SCAM site that mimics a popular brand name store [stoneislandsshop.com]
  • Scammer has found/hacked some website[s] that is/are used as a cloaking host[s] site that will be indexed by googlebot [http://www.superbiketool.com/stois/stois2015042107.asp?*]
  • Victim site is scraped and content is recreated on a cloaking website [with all the branded keywords]
  • Googlebot indexes cloaked website [Googlebot can index content, as it’s not redirected as other user-agents]
  • User performs branded query [related to a victim] on a Google and get scam website high in SERP [due to the fact that indexed content is really related to a victim/brand]

Example:

Google: “adidas black stan smith victim’s brand name

Since query is performed including victim’s branded keyword “brand name”, scammer’s website is positioned very high in SERP.

 

How to protect and fix:

 

The post How to diagnose and fix blackhat SEO branded keywords hijacking? appeared first on Inchoo.

]]>
http://inchoo.net/online-marketing/how-to-diagnose-and-fix-blackhat-seo-branded-keywords-hijacking/feed/ 2
Magento Imagine Commerce 2015 – expect the unexpected http://inchoo.net/ecommerce/magento-imagine-commerce-2015-expect-the-unexpected/ http://inchoo.net/ecommerce/magento-imagine-commerce-2015-expect-the-unexpected/#comments Tue, 14 Apr 2015 12:58:09 +0000 http://inchoo.net/?p=24228 It’s that time of the year again – everyone in the world of Magento are getting ready for the biggest event evolving around world’s favorite eCommerce platform – Imagine Commerce 2015. What can we expect this year and what are some of the hidden gems of the Imagine experience? The biggest Magento Imagine event ever, in...

The post Magento Imagine Commerce 2015 – expect the unexpected appeared first on Inchoo.

]]>
It’s that time of the year again – everyone in the world of Magento are getting ready for the biggest event evolving around world’s favorite eCommerce platformImagine Commerce 2015.

What can we expect this year and what are some of the hidden gems of the Imagine experience?

The biggest Magento Imagine event ever, in one of the largest hotels in the world, with Magento 2 in almost full swing and the growing uncertainty around the future of Ebay Enterprise, this Imagine is poised to be the event where we can expect the unexpected :)

Some quick facts:

  • Imagine 2015 will see 2400+ attendees from 40+ countries
  • There is only one person on the General Session Speakers list with Magento next to his name (Head of Product Management Paul Boisvert)
  • Magento 2 is featured heavily in the agenda with one full day of sessions
  • People are still on the fence with what’s the proper hashtag for the event – #MagentoImagine (my favorite) or #ImagineCommerce
  • Wynn is the 6th largest hotel in the world

Community at its strongest

You know how it usually gets, in face of uncertainty you grow stronger and pull together, and the community around Magento is no different – we’ll see many unofficial talks and get-togethers during the conference where we’ll try to find new ways of sharing best practices and, where possible, working together to better support our clients.

If you follow any of the official hashtags on Twitter, you can get insights into what’s cooking behind the scenes and how the community is prepping up for some unofficial talks and meetups which are, to me, an essential part of any Imagine conference.

PreImagine and MCSS Barcamp

These two unofficial events on the outskirts of Imagine are something that can set the tone (PreImagine on Sunday) for the entire conference and then nicely wrap it up (MCSS Barcamp on Wednesday). Both of these are very informal, and since we’re playing a part in both (Inchoo is sponsoring PreImagine and I’m speaking at MCSS Barcamp), it’s certainly worth mentioning them :)

PreImagine, organized by our dear friends from Interactiv4 is (un)fortunately sold out, and for MCSS Barcamp everyone’s invited, so make sure to drop by the Nucleus Community Room on Wednesday at 1:30PM.

NOTE: Unfortunately, the MCSS Barcamp got cancelled but we’ll all have plenty of time to catch up and discuss these topics during the conference.

New products in the Magento ecosystem – the return of SaaS, or not just quite?

Recently Magento announced its Small Business program which is essentially a list of companies that have bundled up Magento Community Edition with some additional services to create SaaS or SaaS-alike solutions – something that Magento Go was originally planned to become, but it never actually took off before it was officially shut down earlier this year – hey, even go.magento.com now redirects to smallbusiness.magento.com

This is a new way that community around Magento is evolving – we see many more closer relationships between solution, industry and hosting partners emerging, which is certainly a good thing. Whether this particular route will prove to be a good one for those involved, it remains to be seen.

At Imagine we will see these new solutions and partnerships taking the stage and there will certainly be some positive buzz around it – I look forward to learning more details about several of those in particular.

What I’m looking forward to most:

  • Catching up with the #RealMagento folks – there’s so many people who I got a chance to meet in person only once or twice – and a smile, a handshake (and sometimes a hug) work wonders in actual human interaction, even when you are active on Twitter :)
  • Breakout sessions – this time around there’s so many of them that it’s going to be difficult deciding between some very interesting topics

Team Inchoo

This year we have a party of two Inchooers attending – Tomas Novoselic, one of our team leaders and among our most experienced Magento backend developers, and myself so you’ll be able to talk both code and business with us :)

You’ll see us at #PreImagine (Inchoo is sponsoring the event), at Partner Summit and during the breakout sessions where we’ll cover mostly a healthy mix of business and technical topics.

If you want to catch up, the easiest way will probably be to approach us directly when you see us passing by or try tweet us up at @tomas_novoselic and @aronstanic.

See you soon in Vegas! Viva #RealMagento!

The post Magento Imagine Commerce 2015 – expect the unexpected appeared first on Inchoo.

]]>
http://inchoo.net/ecommerce/magento-imagine-commerce-2015-expect-the-unexpected/feed/ 0
State of Magento Solution Specialist Certification – March 2015 http://inchoo.net/ecommerce/state-magento-solution-specialist-certification-march-2015/ http://inchoo.net/ecommerce/state-magento-solution-specialist-certification-march-2015/#comments Tue, 31 Mar 2015 09:22:31 +0000 http://inchoo.net/?p=24175 Every three months we’re coming up with the latest numbers on the Magento Certified Solution Specialist certification. As we’re closing in to a full year since Magento rolled out its only business-oriented certification program, it’s good to check out some trends and see if the same countries and solution partners still hold the lead or have the tables...

The post State of Magento Solution Specialist Certification – March 2015 appeared first on Inchoo.

]]>
Every three months we’re coming up with the latest numbers on the Magento Certified Solution Specialist certification.

As we’re closing in to a full year since Magento rolled out its only business-oriented certification program, it’s good to check out some trends and see if the same countries and solution partners still hold the lead or have the tables turned. We bring you the third State of Magento Solution Specialist Certification report by Inchoo.

The latest figures show that some trends have continued since my last report, with Europe further strengthening its global leadership (UK, Sweden and Netherlands helping the most with their growth ) and certificates popping up everywhere, with the likes of Mauritius being introduced to the list of countries where you can find a Magento Certified Solution Specialist.

Asia grows strongest, USA struggling to keep up

This time we can compare how number of certificates has grown over time – Europe has continued to almost double its total numbers between each of the reports, totaling at 158 and USA showing a bit stronger growth than in the previous period (50% compared to 35.5% from the last report).

Worldwide, however, these certificates are growing at a much higher rate of 87% so USA is in danger of falling behind rather soon if these trends continue.

But all of this fades compared to Asia’s huge 350% growth (it jumped from 4 MCSS in November 2014 to 18 in March 2015).

At the moment of drafting this report there have been 267 certified solution specialists out there (well, those that have been publicly listed to be more accurate) coming from the total of 39 countries.

mcss-continents-2015-03-inchoo

Bragging rights for countries, cities and solution partners

Top countries list hasn’t changed much, USA is still in a comfortable lead in front of a number of EU countries, however its global share has dropped further, to 23% (63 of 267) – as a quick reminder, in my first report in July 2014 USA held 43% of all the certificates worldwode (31 out of 74).

COUNTRIES

  1. USA – 63
  2. Germany – 29
  3. UK – 23
  4. Sweden – 21
  5. Netherlands – 16
  6. Croatia – 15

mcss-countries-2015-03-inchoo

As for the cities across the globe where you can find a fellow MCSS, our hometown of Osijek still tops the charts with 15 total certificates – but it’s getting crowded at the top as Stockholm is quickly closing in on us. Looks like those Vaimo guys really started taking these certifications seriously (plus they’re in slightly larger markets).

TOP CITIES

  1. Osijek, Croatia – 15
  2. Stockholm, Sweden – 14
  3. Groningen, Netherlands – 10
  4. Springfield, USA – 9
  5. London, UK – 9

Among solution partners, Vaimo has taken the charts by storm – they hold a total of 20 MCSS badges in several of their offices and that’s by far the most out of all solution partners, so hats off to our Scandinavian friends:

SOLUTION PARTNERS

  1. Vaimo, Sweden – 20
  2. Inchoo, Croatia – 11
  3. Classy Llama, USA – 9
  4. MediaCT, Netherlands – 6
  5. Gorilla, USA – 5

A quick shout-out should also go to Rackspace as they (as Hosting partners of Magento) have 4 of their team members certified as solution specialists – this is a great proof of their commitment to Magento as a platform where most wouldn’t expect this extra effort.

Cream of the crop

In my previous reports I made sure to mention all those fellow Magento community members who have made an effort to prove just how versatile they are by collecting all four available Magento certification badges (MCD, MCD+, MCFD and MCSS), and since the last report this list has more than doubled (from 7 to 18 people).

Here they are, listed alphabetically:

  • Adriano Aguiar (Brazil)
  • James Anelay (UK)
  • Jitze Bakker (Netherlands)
  • Jacques Bodin-Hullin (France)
  • Kris Brown (USA)
  • Ashoka de Wit (Netherlands)
  • Bartosz Gorski (Poland)
  • Phillip Jackson (USA)
  • Vladimir Kerkhoff (Netherlands)
  • Francis Kim (Australia)
  • Chris Manger (USA)
  • Derrick Nyomo (USA)
  • Vladislav Osmianskij (Lithuania)
  • Rutger Rademaker (Netherlands)
  • Martijn Riemersma (Netherlands)
  • Bas Rozema (Netherlands)
  • Tobias Zander (Germany)
  • Anthony Van Zandycke (Belgium)

Take a bow, gentlemen – you deserve it!

Some interesting facts & figures

Even though not much detail can be taken from the official certification directory, there are some interesting facts I wanted to share since I already crunched all those numbers, so here goes:

  • Ladies hold only 8.5% of all MCSS badges
  • Most popular name (9) for an MCSS is Christian (or its equivalent – Chris, Christoph and similar)
  • For 174 people (65% of everyone who got certified) this is their only Magento certificate

Waiting for some official data

Ever since we started preparing these reports, we haven’t seen any official data from Magento about solution specialist certification, so I’m actually very interested to see what numbers they will present at this year’s Imagine conference.

Are you coming?


Disclaimer:

The data for this article is taken from the official public Magento certification directory as well as some of the partners listings and the “heavy crunching” required for this article was completed on March 27, 2015 – if you got certified and had your profile published in the meantime, you may want to wait for this post to be updated :)

There’s also a possibility that not all people who got certified made their profiles publicly searchable, so the actual number of certified developers is probably a bit higher.

The post State of Magento Solution Specialist Certification – March 2015 appeared first on Inchoo.

]]>
http://inchoo.net/ecommerce/state-magento-solution-specialist-certification-march-2015/feed/ 5
Large number of input variables in Magento http://inchoo.net/magento/large-number-input-variables-magento/ http://inchoo.net/magento/large-number-input-variables-magento/#comments Wed, 18 Feb 2015 12:26:36 +0000 http://inchoo.net/?p=23991 UPDATE: The issue described in the following text has been taken care of in Magento EE 1.14 / CE 1.9 Recently, a pretty strange issue occurred on one of our projects. Our client reported that when trying to assign products to a category, only a thousand of products got saved while the other ones were...

The post Large number of input variables in Magento appeared first on Inchoo.

]]>
UPDATE: The issue described in the following text has been taken care of in Magento EE 1.14 / CE 1.9

Recently, a pretty strange issue occurred on one of our projects. Our client reported that when trying to assign products to a category, only a thousand of products got saved while the other ones were ignored. To deal with this issue, I decided to jump to the category save action and check for any problems with the code that may cause this behaviour.

Here’s how Magento assigns products to a category:

class Mage_Adminhtml_Catalog_CategoryController extends Mage_Adminhtml_Controller_Action
{
     public function saveAction()
     {
		                  . . .
 
          if (isset($data['category_products']) && !$category->getProductsReadonly()) {
                $products = array();
                parse_str($data['category_products'], $products);
                $category->setPostedProducts($products);
          }
                 . . .
    }
}

 It takes a string generated by a serializer, passes it to the parse_str function which makes an array out of it and assigns the resulting array to a category. Nothing wrong with it, right?

Since examining the code didn’t give me any answers I decided to check the error log. Here’s what I found in it:

Warning: parse_str(): Input variables exceeded 1000. To increase the limit change max_input_vars in php.ini.

As you can see, the problem is being caused by php’s configuration parameter max_input_vars which limits the number of input variables that may be accepted. It was introduced in PHP version 5.3.9. in order to deal with denial of service attacks which use hash collisions. Parse_str function seems to take that parameter into account when generating array from string. Resulting array’s length is equal to the value of max_input_vars parameter which is by default set to 1000. This is why only first thousand products got saved and the rest were not.

To deal with this issue we can do two things. Increase the value of max_input_vars parameter or try to solve the problem programmatically.

  • Increase the max_input_vars value

Max_input_vars value can be changed in two ways, through .htaccess or php.ini. To change it through .htaccess add this line to your .htaccess file:

php_value max_input_vars 2000

and adjust the value to your needs.

Depending on your hosting  you may not have privileges to edit php.ini file. In that case just contact your host to increase the value for you. Otherwise, if you are allowed to edit php.ini or are working under local development environment, open it and locate the following line:

;max_input_vars = 1000

If this line is commented out, uncomment it and adjust the value to you needs, for example:

max_input_vars = 2000.


In order for the changes to take effect you need to restart the apache.

  • Programmatic solution

 Changing max_input_vars value is fast and simple solution which will probably solve your problems for quite some time. But if you add lots of products on a daily basis  and your category product count raises quickly, than you would always need to keep an eye on max_input_vars and adjust it’s value every now and then. To avoid this we can come up with the programmatic solution which will work no matter how many products we try to assign to one category. You can find one solution below:

require_once 'Mage/Adminhtml/controllers/Catalog/CategoryController.php';
class Inchoo_Smile_Adminhtml_Catalog_CategoryController extends Mage_Adminhtml_Catalog_CategoryController
{
    public function saveAction()
    {
                             ...
 
         if (isset($data['category_products']) && !$category->getProductsReadonly()) {
              $products = array();
              $exploded = explode('&', $data['category_products']);
 
              foreach ($exploded as $row) {
                   $temp = array();
                   parse_str($row, $temp);
                   list($key, $value) = each($temp);
                   if(!empty($key)) {
                        $products[$key] = $value;
                   }
              }
 
              $category->setPostedProducts($products);
         }
 
                            ...
    }
}

Instead of overriding the category controller we could also solve this problem with a more elegant solution. In the save action of Mage_Adminhtml_Catalog_CategoryController, right after product data is assigned to a category, Magento dispatches an event catalog_category_prepare_save. Using the observer we can hook to that event and change category products data like so:

class Inchoo_Smile_Model_Observer
{
    // event: catalog_category_prepare_save
    public function assignCategoryProducts($observer)
    {
        $category = $observer->getCategory();
        $request = $observer->getRequest();
 
        $products = array();
        $exploded = explode('&', $request->getParam('category_products'));
 
        foreach ($exploded as $row) {
            $temp = array();
            parse_str($row, $temp);
            list($key, $value) = each($temp);
 
            if(!empty($key)) {
                $products[$key] = $value;
            }
        }                
 
        $category->setPostedProducts($products);
    }
 
}

As you can see I didn’t go through the process of module creation or file rewrites since there are lots of articles out there covering the subject. If you decide to go with the one of the programmatic solutions make sure you create your own module and do the coding there. Editing core files directly is never a good practice, besides Magento Boogieman will get you if you do! :)

Happy coding!

 

 

The post Large number of input variables in Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/large-number-input-variables-magento/feed/ 4
Creating EAV based model(s) in Magento http://inchoo.net/magento/creating-an-eav-based-models-in-magento/ http://inchoo.net/magento/creating-an-eav-based-models-in-magento/#comments Thu, 12 Feb 2015 11:00:14 +0000 http://inchoo.net/?p=11755 Magento EAV (Entity Attribute Value) data model is used to get flexibility for your data, but it brings more complexity than relation table model. If you need data model that will have flexible attributes which can be dynamically added, for example from Magento admin panel, then EAV is the best solution for you. If using EAV,...

The post Creating EAV based model(s) in Magento appeared first on Inchoo.

]]>
Magento EAV (Entity Attribute Value) data model is used to get flexibility for your data, but it brings more complexity than relation table model. If you need data model that will have flexible attributes which can be dynamically added, for example from Magento admin panel, then EAV is the best solution for you. If using EAV, you don’t need to change table structure for every new attribute like you do on flat tables (creating new colums).

Bad things which EAV flexibility brings are slower Queries and more complex table structures. Let’s talk more about performance, EAV system is slower than using flat table for resource model, because it uses a lot of mysql joins (depend on attributes number) and, as we know, Query with join data from other tables is always slower than select Query from one table. This problem can be solved with two solutions. For Enterprise Magento enable full page cache and page will be cached, so with or without EAV there won’t be any difference, only first load(slower for EAV) and after that all is cached. Second solution is to make flat table and indexer for creating table from EAV entity attributes.

After short brief about good and bad things with EAV let’s see how to create custom EAV model in example. For demonstration purposes let’s create Blog module with EAV resource model for posts, and focus will be on creating model, resource model and collection for post with installation. Admin part for adding new posts and custom attributes won’t be part of this article because they’re not required for our EAV model to work. I will demonstrate how to use EAV resurce model and Collection to get post(s) on frontend.

First we need to create our Blog module by adding module declaration in config, for this we’ll create Inchoo_Blog.xml in app\etc\modules\:
app\etc\modules\Inchoo_Blog.xml:

<?xml version="1.0"?>
<!--
Inchoo Blog Module
Author: Zoran Šalamun(zoran.salamun@inchoo.net)
https://inchoo.net/
-->
<config>
    <modules>
        <Inchoo_Blog>
            <active>true</active>
            <codePool>local</codePool>
        </Inchoo_Blog>
    </modules>
</config>

After registering our module, next step is to create folder structure for Module (Block, Helper, controllers, etc, Model and sql folders in module root folder – app\code\local\Inchoo\Blog\), and inside etc folder config.xml for module configuration:

<?xml version="1.0"?>
<!--
    Blog - Module for testing custom EAV tables
    Author: Zoran Šalamun(zoran.salamun@inchoo.net)
-->
<config>
    <modules>
        <Inchoo_Blog>
            <version>1.0.0.0</version>
        </Inchoo_Blog>
    </modules>
    <global>
        <models>
            <inchoo_blog>
                <class>Inchoo_Blog_Model</class>
            </inchoo_blog>
        </models>
        <helpers>
            <inchoo_blog>
                <class>Inchoo_Blog_Helper</class>
            </inchoo_blog>
        </helpers>
        <blocks>
            <inchoo_blog>
                <class>Inchoo_Blog_Block</class>
            </inchoo_blog>
        </blocks>
    </global>
</config>xxx

In config file we add common options by declaring module version, models, helpers, blocks. After adding them, we add other config options needed for our module to work with entity tables. In config file we have declared model class so Magento can know what model to use with Mage::getModel.

For Blog posts we need to create post model, post resource model and post collection. First we’ll create post model in app/code/local/Inchoo/Blog/Model/ with file name Post.php:

class Inchoo_Blog_Model_Post extends Mage_Core_Model_Abstract
{
 
}

Note that Inchoo_Blog_Model_Post class extends Mage_Core_Model_Abstract, same as every other Model, but EAV magic starts in resource model. In post model class we need to define our resource model in _construct():

protected function _construct()
    {
        $this->_init('inchoo_blog/post');
    }

inchoo_blog/post is resource model that need to be declared in our config.xml and we’ll add it now in models node:

<models>
            <inchoo_blog>
                <class>Inchoo_Blog_Model</class>
                <resourceModel>inchoo_blog_resource</resourceModel>
            </inchoo_blog>
            <inchoo_blog_resource>
                <class>Inchoo_Blog_Model_Resource</class>
            </inchoo_blog_resource>
        </models>

inchoo_blog is model node name and post is file name for resource model. Magento creates path to resource model joining inchoo_blog resource class (Inchoo_Blog_Model_Resource) with resource model name (Post). After Magento join this from configuration we’ll get post resource class name (and path): Inchoo_Blog_Model_Resource_Post.

Now it’s time to create our resource model, so let’s create Post.php file in app/code/local/Inchoo/Blog/Model/Resource/ folder:

class Inchoo_Blog_Model_Resource_Post extends Mage_Eav_Model_Entity_Abstract
{
 
}

As you can see, our resurce model Inchoo_Blog_Model_Resource_Post extends Mage_Eav_Model_Entity_Abstract and this is how Magento handle EAV in resource model. To make this work with Entity tables we need to set options and default attributes, so to set options we’ll add this code:

public function __construct()
    {
        $resource = Mage::getSingleton('core/resource');
        $this->setType('inchoo_blog_post');
        $this->setConnection(
            $resource->getConnection('blog_read'),
            $resource->getConnection('blog_write')
        );
    }

As you can see, we need to set resource and connection. For connection read and write we need to add in our module config file nodes where we define read and write connections:

<resources>
            <blog_write>
                <connection>
                    <use>core_write</use>
                </connection>
            </blog_write>
            <blog_read>
                <connection>
                    <use>core_read</use>
                </connection>
            </blog_read>
        </resources>

$this->setType (‘inchoo_blog_post’) in our Post resource model init defines what entity type resource should be used. We’ll define this later in our module install script. Also in resource model we need to specify default attributes in _getDefaultAttributes() method:

protected function _getDefaultAttributes()
    {
        return array(
            'entity_type_id',
            'attribute_set_id',
            'created_at',
            'updated_at',
            'increment_id',
            'store_id',
            'website_id'
        );
    }

These are default attributes that will be loaded, but we’ll get back on them later. They’re not required to set because Mage_Eav_Model_Entity_Abstract already set them. You can do it only if you want to change default attributes.

After creating our resource model we need to create collection class, and we need to create it in app/code/local/Inchoo/Blog/Model/Resource/Post folder with file called Collection.php and that class has to extend EAV collection class abstract:

class Inchoo_Blog_Model_Resource_Post_Collection extends Mage_Eav_Model_Entity_Collection_Abstract
{
 
    protected function _construct()
    {
        $this->_init('inchoo_blog/post');
    }
 
}

We have extended our collection class Inchoo_Blog_Model_Resource_Post_Collection with Mage_Eav_Model_Entity_Collection_Abstract and this gives us options for our resource model to work with EAV tables. As you can see, you need to set resource model like on any other collection ($this->_init(‘inchoo_blog/post’)).

After setting our model, resource model and collection we have created all functionalities that make this work with EAV. But, one more important step is required to make this actually work – create entity tables and add entity type. We’ll do this with our install script, first we need to set this in our config file:

<resources>
            <inchoo_blog_setup>
                <setup>
                    <module>Inchoo_Blog</module>
                    <class>Inchoo_Blog_Model_Resource_Setup</class>
                </setup>
                <connection>
                    <use>core_setup</use>
                </connection>
            </inchoo_blog_setup>
        </resources>

Now we need to create two things, as you can see in configuration, we need Inchoo_Blog_Model_Resource_Setup class for our setup and setup file for version 1.0.0.0 like our version of module is. Setup class is needed for specifying what attributes model can save in entity tables, and we’ll use this class in our setup script. Let’s create our setup class in app/code/local/Inchoo/Blog/Model/Resource/:

<?php
 
class Inchoo_Blog_Model_Resource_Setup extends Mage_Eav_Model_Entity_Setup
{
    /*
     * Setup attributes for inchoo_blog_post entity type
     * -this attributes will be saved in db if you set them
     */
    public function getDefaultEntities()
    {
        $entities = array(
            'inchoo_blog_post' => array(
                'entity_model' => 'inchoo_blog/post',
                'attribute_model' => '',
                'table' => 'inchoo_blog/post_entity',
                'attributes' => array(
                    'title' => array(
                        'type' => 'varchar',
                        'backend' => '',
                        'frontend' => '',
                        'label' => 'Title',
                        'input' => 'text',
                        'class' => '',
                        'source' => '',
                        'global' => 0,
                        'visible' => true,
                        'required' => true,
                        'user_defined' => true,
                        'default' => '',
                        'searchable' => false,
                        'filterable' => false,
                        'comparable' => false,
                        'visible_on_front' => true,
                        'unique' => false,
                    ),
                    'author' => array(
                        'type' => 'varchar',
                        'backend' => '',
                        'frontend' => '',
                        'label' => 'Author',
                        'input' => 'text',
                        'class' => '',
                        'source' => '',
                        'global' => 0,
                        'visible' => true,
                        'required' => true,
                        'user_defined' => true,
                        'default' => '',
                        'searchable' => false,
                        'filterable' => false,
                        'comparable' => false,
                        'visible_on_front' => false,
                        'unique' => false,
                    ),
                ),
            )
        );
        return $entities;
    }
}

You can see that our setup class extends Mage_Eav_Model_Entity_Setup and we’ve defined our entities and attributes. For entity is used inchoo_blog_post and that’s our entity type. This needs to be declared in our setup script and we’ve set it also in our post resource model. This is important step because we must set our attributes for entity model so our resource model can save them in entity tables.

For example, if our blog should have an author, we need to specify author attribute in getDefaultEntities method under our inchoo_blog_post entity attributes. If you don’t set attribute and use save on post model, Magento won’t know where to save data and your attribute won’t be saved. Good thing with attributes is that they’re only saved in tables, if you set them, but if you don’t set (for example author) on post and save, it will not save this attribute. This approach saves db space in comparison with flat table, as we know, flat table will have all attributes as columns and that columns have NULL value. And that is the biggest advantage of using EAV, especially if you have a lot attributes which are in most cases set for some blog posts.

One more important advantage in using EAV is to have attributes that will work for different languages(stores), you can see example of this in category/product EAV. When comparing flat table vs EAV, EAV advantages are that flat for some attributes can have mostly NULL values, and if you use EAV all attributes that have not been set won’t be saved anywhere – they will be saved only if you set them.

Lastly, for our post model to work, we need to make setup script for creating tables and to add entity type with attributes. If you check setup for catalog/product or customer, you can see that every table for EAV is created separately by installer. This approach is good if you want some additional columns in main entity table, if you don’t need that than create EntityTables, this method is best to use because it will automatically create all EAV tables (note that in 1.6 and 1.7 users reported bugs with this functionality). For our EAV to work we need main entity table (_entity at end of table name) and 6 additional tables for every data type to be saved. For example, every time you create a new blog post a new entity will be added to main entity table, and attributes to entity specific type tables (for example varchar type attribute to table that ends with _entity_varchar) if you’ve set attribute. Every attribute is new entry in specific attribute type table, so if you have set 5 attributes there will be 5 entries times stores(if you have 3 stores, it will be 15 entries – 5 attributes x 3 stores) in entity tables.

We have defined setup script with defining setup class and now it’s time to define our tables. To do that, we only need to define main entity table name, but for demonstration we’ll define all entity tables to demonstrate how to create all entity tables by creating them separately. In configuration file under inchoo_blog_resources node we’ll add tables configuration:

<inchoo_blog_resource>
                <class>Inchoo_Blog_Model_Resource</class>
                <entities>
                    <post_entity>
                        <table>inchoo_blog_post_entity</table>
                    </post_entity>
                    <post_entity_datetime>
                        <table>inchoo_blog_post_entity_datetime</table>
                    </post_entity_datetime>
                    <post_entity_decimal>
                        <table>inchoo_blog_post_entity_decimal</table>
                    </post_entity_decimal>
                    <post_entity_int>
                        <table>inchoo_blog_post_entity_int</table>
                    </post_entity_int>
                    <post_entity_text>
                        <table>inchoo_blog_post_entity_text</table>
                    </post_entity_text>
                    <post_entity_varchar>
                        <table>inchoo_blog_post_entity_varchar</table>
                    </post_entity_varchar>
                    <post_entity_char>
                        <table>inchoo_blog_post_entity_char</table>
                    </post_entity_char>
                </entities>
            </inchoo_blog_resource>

For creating tables with createEntityTables we only need post_entity definition and to create all entity tables in install script with createTable, we need all other table definitions.

Now we’ll create install script, for our version (1.0.0.0), name for install file will be install-1.0.0.0.php. First, we’ll create install script that uses createEntityTables:

$installer = $this;
$installer->startSetup();
 
/*
 * Create all entity tables
 */
$installer->createEntityTables(
    $this->getTable('inchoo_blog/post_entity')
);
 
/*
 * Add Entity type
 */
$installer->addEntityType('inchoo_blog_post',Array(
    'entity_model'          =>'inchoo_blog/post',
    'attribute_model'       =>'',
    'table'                 =>'inchoo_blog/post_entity',
    'increment_model'       =>'',
    'increment_per_store'   =>'0'
));
 
$installer->installEntities();
 
$installer->endSetup();

So, we have set installer, $this refers to our setup class (we have defined that in config file). After setting the installer we have created tables with createEntityTables. This method has created following tables:

inchoo_blog_post_entity
inchoo_blog_post_entity_datetime
inchoo_blog_post_entity_decimal
inchoo_blog_post_entity_int
inchoo_blog_post_entity_text
inchoo_blog_post_entity_varchar
inchoo_blog_post_entity_char

inchoo_blog_post_entity is main entity table, and 6 other tables are data type specific for attributes.

After creating tables we added new entity type with addEntityType, so let’s explain params:

entity_model – our model which will be used for entity (must have resource that extends Mage_Eav_Model_Entity_Abstract)
attribute_model – if you want to use other attribute model, remember that attribute models are not limited to 6 standard models and you can create your own
table – our main entity table
increment_model – model responsible for generating increment IDs
increment_per_store – use increment ID per store(1)

Last thing in install script is to install our entities with installEntities(), which we have defined in setup class (getDefaultEntities). Entity type is added to eav_entity_type table and this is Magento core table for entity types.

I want to show you code for install script to create all entity tables manually:

<?php
 
$installer = $this;
$installer->startSetup();
 
/**
 * Create table 'inchoo_testeav/entity'
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity'))
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
        'unsigned'  => true,
     ), 'Entity Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_set_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Set Id')
    ->addColumn('increment_id', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array(
        'nullable'  => false,
        'default'   => '',
    ), 'Increment Id')
    ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Store Id')
    ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(
        'nullable'  => false,
    ), 'Created At')
    ->addColumn('updated_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(
        'nullable'  => false,
    ), 'Updated At')
    ->addColumn('is_active', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '1',
    ), 'Defines Is Entity Active')
    ->addIndex($this->getIdxName($baseTableName, array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($this->getIdxName($baseTableName, array('store_id')),
        array('store_id'))
    ->addForeignKey($this->getFkName($baseTableName, 'entity_type_id', 'eav/entity_type', 'entity_type_id'),
        'entity_type_id', $this->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($this->getFkName($baseTableName, 'store_id', 'core/store', 'store_id'),
        'store_id', $this->getTable('core/store'), 'store_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Post Entity Main Table');
$installer->getConnection()->createTable($table);
 
/*
 * Datetime entity table for blog post
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity_datetime'))
    ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
    ), 'Value Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Id')
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Id')
    ->addColumn('value', Varien_Db_Ddl_Table::TYPE_DATETIME, null, array(
        'nullable'  => false,
        'default' => $installer->getConnection()->getSuggestedZeroDate()
    ), 'Value')
    ->addIndex(
        $installer->getIdxName(
            'inchoo_blog_post_entity_datetime',
            array('entity_id', 'attribute_id'),
            Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
        ),
        array('entity_id', 'attribute_id'),
        array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->addIndex($installer->getIdxName('inchoo_blog_post_entity_datetime', array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($installer->getIdxName('inchoo_blog_post_entity_datetime', array('attribute_id')),
        array('attribute_id'))
    ->addIndex($installer->getIdxName('inchoo_blog_post_entity_datetime', array('entity_id')),
        array('entity_id'))
    ->addIndex($installer->getIdxName('inchoo_blog_post_entity_datetime', array('entity_id', 'attribute_id', 'value')),
        array('entity_id', 'attribute_id', 'value'))
    ->addForeignKey(
        $installer->getFkName('inchoo_blog_post_entity_datetime', 'attribute_id', 'eav/attribute', 'attribute_id'),
        'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey(
        $installer->getFkName('inchoo_blog_post_entity_datetime', 'entity_id', 'inchoo_blog/post_entity_datetime', 'entity_id'),
        'entity_id', $installer->getTable('inchoo_blog/post_entity_datetime'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey(
        $installer->getFkName(
            'inchoo_blog_post_entity_datetime',
            'entity_type_id',
            'eav/entity_type',
            'entity_type_id'
        ),
        'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Blog Post Entity Datetime');
$installer->getConnection()->createTable($table);
 
/*
 * Decimal entity table for blog post
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity_decimal'))
    ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
    ), 'Value Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Id')
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Id')
    ->addColumn('value', Varien_Db_Ddl_Table::TYPE_DECIMAL, '12,4', array(
        'nullable'  => false,
        'default'   => '0.0000',
    ), 'Value')
    ->addIndex(
        $installer->getIdxName(
            'inchoo_blog/post_entity_decimal',
            array('entity_id', 'attribute_id'),
            Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
        ),
        array('entity_id', 'attribute_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_decimal', array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_decimal', array('attribute_id')),
        array('attribute_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_decimal', array('entity_id')),
        array('entity_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_decimal', array('entity_id', 'attribute_id', 'value')),
        array('entity_id', 'attribute_id', 'value'))
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_decimal', 'attribute_id', 'eav/attribute', 'attribute_id'),
        'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_decimal', 'entity_id', 'customer/entity', 'entity_id'),
        'entity_id', $installer->getTable('inchoo_blog/post_entity'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey(
        $installer->getFkName('inchoo_blog/post_entity_decimal', 'entity_type_id', 'eav/entity_type', 'entity_type_id'),
        'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Blog Post Entity Decimal');
$installer->getConnection()->createTable($table);
 
/*
 * Integer entity table for blog post
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity_int'))
    ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
    ), 'Value Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Id')
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Id')
    ->addColumn('value', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'nullable'  => false,
        'default'   => '0',
    ), 'Value')
    ->addIndex(
        $installer->getIdxName(
            'inchoo_blog/post_entity_int',
            array('entity_id', 'attribute_id'),
            Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
        ),
        array('entity_id', 'attribute_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_int', array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_int', array('attribute_id')),
        array('attribute_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_int', array('entity_id')),
        array('entity_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_int', array('entity_id', 'attribute_id', 'value')),
        array('entity_id', 'attribute_id', 'value'))
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_int', 'attribute_id', 'eav/attribute', 'attribute_id'),
        'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_int', 'entity_id', 'customer/entity', 'entity_id'),
        'entity_id', $installer->getTable('inchoo_blog/post_entity'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_int', 'entity_type_id', 'eav/entity_type', 'entity_type_id'),
        'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Blog Post Entity Int');
$installer->getConnection()->createTable($table);
 
/*
 * Text entity table for blog post
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity_text'))
    ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
    ), 'Value Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Id')
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Id')
    ->addColumn('value', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array(
        'nullable'  => false,
    ), 'Value')
    ->addIndex(
        $installer->getIdxName(
            'inchoo_blog/post_entity_text',
            array('entity_id', 'attribute_id'),
            Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
        ),
        array('entity_id', 'attribute_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_text', array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_text', array('attribute_id')),
        array('attribute_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_text', array('entity_id')),
        array('entity_id'))
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_text', 'attribute_id', 'eav/attribute', 'attribute_id'),
        'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_text', 'entity_id', 'customer/entity', 'entity_id'),
        'entity_id', $installer->getTable('inchoo_blog/post_entity'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey(
        $installer->getFkName('inchoo_blog/post_entity_text', 'entity_type_id', 'eav/entity_type', 'entity_type_id'),
        'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Blog Post Entity Text');
$installer->getConnection()->createTable($table);
 
/*
 * Varchar entity table for blog post
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity_varchar'))
    ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
    ), 'Value Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Id')
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Id')
    ->addColumn('value', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array(
    ), 'Value')
    ->addIndex(
        $installer->getIdxName(
            'inchoo_blog/post_entity_varchar',
            array('entity_id', 'attribute_id'),
            Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
        ),
        array('entity_id', 'attribute_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_varchar', array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_varchar', array('attribute_id')),
        array('attribute_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_varchar', array('entity_id')),
        array('entity_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_varchar', array('entity_id', 'attribute_id', 'value')),
        array('entity_id', 'attribute_id', 'value'))
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_varchar', 'attribute_id', 'eav/attribute', 'attribute_id'),
        'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_varchar', 'entity_id', 'customer/entity', 'entity_id'),
        'entity_id', $installer->getTable('inchoo_blog/post_entity'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey(
        $installer->getFkName('inchoo_blog/post_entity_varchar', 'entity_type_id', 'eav/entity_type', 'entity_type_id'),
        'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Blog Post Entity Varchar');
$installer->getConnection()->createTable($table);
 
/*
 * Add Entity type
 */
$installer->addEntityType('inchoo_blog_post',Array(
    'entity_model'          =>'inchoo_blog/post',
    'attribute_model'       =>'',
    'table'                 =>'inchoo_blog/post_entity',
    'increment_model'       =>'',
    'increment_per_store'   =>'0'
));
 
$installer->installEntities();
 
$installer->endSetup();

You can see that we removed createEntityTables and defined (also created) all tables manually. This approach can give us some additional columns specific for our needs, and for blog we can create title and content columns. As we know, every blog post needs to have title and content, so there is no need to use attributes for them.

Testing our code

For testing purposes we’ll create a route definition and index controller. Let’s define everything in config.xml

<frontend>
        <routers>
            <inchoo_blog>
                <use>standard</use>
                <args>
                    <module>Inchoo_Blog</module>
                    <frontName>blog</frontName>
                </args>
            </inchoo_blog>
        </routers>
    </frontend>

As you can see, defined in frontName we’ll use /blog/ for our blog, and for test we’ll use index controller and index action. Url for testing will be http:://ourmagento.com/blog/ or http:://ourmagento.com/blog/index/index/ (if we only write /blog/ magento will know that index is controller and index is action if not defined otherwise).

For testing we’ll now create index controller with index action:

class Inchoo_Blog_IndexController extends Mage_Core_Controller_Front_Action
{
    public function indexAction()
    {
        /*
         * Create new blog post with author and title
         * -this will create new row in inchoo_blog_post_entity table and
         *  two entries for title and author attributes will be saved in inchoo_blog_post_entity_varchar table
         */
        $post = Mage::getModel('inchoo_blog/post');
        $post->setTitle('Test title');
        $post->setAuthor('Zoran Šalamun');
        $post->save();
 
        /*
         * Try to create post with book type attribute
         * -book type attribute will not be saved because book type attribute is not defined for our entity type
         * -on new row will be added in inchoo_blog_post_entity
         */
        $post = Mage::getModel('inchoo_blog/post');
        $post->setBookType('Test title');
        $post->save();
 
        /*
         * Getting posts collection
         * -also load collection
         * -this will load all post entries but without attributes
         * -loaded data is only from inchoo_blog_post_entity table
         */
        $posts = Mage::getModel('inchoo_blog/post')->getCollection();
        $posts->load();
        var_dump($posts);
 
        /*
         * Getting post collection
         * -load all posts
         * -set attributs to be in collection data
         */
        $posts = Mage::getModel('inchoo_blog/post')->getCollection()
                ->addAttributeToSelect('title')
                ->addAttributeToSelect('author');
        $posts->load();
        var_dump($posts);
 
        /*
         * Load signle post
         * -loading single post will get all attributes that we have set for post
         */
        $post = Mage::getModel('inchoo_blog/post')->load(1);
        var_dump($post);
    }
}

I hope this will help you understand basics for creating EAV models. Also, you can download test module which we’ve used for demonstration here or you can find it on GitHub.

The post Creating EAV based model(s) in Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/creating-an-eav-based-models-in-magento/feed/ 7
State of Magento Solution Specialist Certification – November 2014 http://inchoo.net/ecommerce/state-magento-solution-specialist-certification-november-2014/ http://inchoo.net/ecommerce/state-magento-solution-specialist-certification-november-2014/#comments Thu, 20 Nov 2014 12:03:54 +0000 http://inchoo.net/?p=23713 How many Magento Certified Solution Specialists are there? Which country, city, solution partner hold the most badges and what are some of the trends that can be seen throughout the first 6 months of Magento’s latest official certification program? Check it out in the second State of Magento Solution Specialist Certification report by Inchoo. Many things have changed in...

The post State of Magento Solution Specialist Certification – November 2014 appeared first on Inchoo.

]]>
How many Magento Certified Solution Specialists are there? Which country, city, solution partner hold the most badges and what are some of the trends that can be seen throughout the first 6 months of Magento’s latest official certification program? Check it out in the second State of Magento Solution Specialist Certification report by Inchoo.

Many things have changed in the three and a half months after I published the first report of this type. Back then Europe was in a slim lead over North America while USA accounted for almost half of all solution specialists worldwide.

Well, some things have clearly changed since July, and here are some of the stats and trends to keep an eye on.

Europe leads the way in growth, USA – not so much

Since the last report Europe has more than doubled the number of MCSS badges with the total of 82 (compared to 38 back in July) while only 11 new certificates came from USA (the total of 42 – a 35.5% increase compared to July’s 31).

We’ve also welcomed first certified solution specialists from Africa (one in South Africa) and Oceania (4 in Australia an 3 in New Zealand). At the moment of drafting this report there are 143 certified solution specialists out there (well, those that are publicly listed to be more accurate) coming from 32 countries worldwide.

mcss-continents-2014-11So, Europe grows stronger and a number of new countries are popping up on the MCSS map – which is understandable as Magento clearly is becoming more and more of a global eCommerce solution, and that’s a great thing, right? Right.

Top 5 lists – countries, cities, solution partners

This is even more apparent if we take a look at the certificates by country chart – USA tops the charts with 42 but it now accounts for “only” 29% of the world total (143) – back in July USA held a whopping 43% of the global certificates (31 out of 74).

And what do you know? Check out who made the Top 5 – Croatia is among the likes of USA, UK, Germany and Netherlands – hey, if there only were an option to translate this to GDP figures :)

COUNTRIES

  1. USA – 42
  2. Germany – 15
  3. UK – 14
  4. Croatia – 13
  5. Netherlands – 11

mcss-countries-2014-11

If you’re thinking of relocating to a place where you can pick the brains of certified solution specialists over a cup of coffee, these are the places to consider (yes, there are in fact 6 on the list as Stockholm, London and NYC all have 5 badges to show):

CITIES

  1. Osijek, Croatia – 13
  2. Springfield, USA – 9
  3. Groningen, Netherlands – 6
  4. Stockholm, Sweden – 5
  5. London, UK – 5
  6. New York, USA – 5

Now, it’s only reasonable to assume that Magento Solution Partners would have most certificates, so this Top 5 list correlates nicely to an extent with the cities list (the tiebreaker here is the total number of certificates – devs included):

SOLUTION PARTNERS

  1. Inchoo, Croatia – 11
  2. Classy Llama, USA – 9
  3. Vaimo, Sweden – 4
  4. Gorilla, USA – 4
  5. Corra, USA – 4

Gotta catch ’em all

There are 7 people on this list that deserve to be mentioned as they made a special effort to collect all four available Magento certification badges (MCD, MCD+, MCFD and MCSS), so here’s a quick shout-out to these seven amazing individuals – congratulations, guys!

  • Adriano Aguiar (Brazil)
  • Jitze Bakker (Netherlands)
  • Jacques Bodin-Hullin (France)
  • Kris Brown (USA)
  • Phillip Jackson (USA)
  • Vladimir Kerkhoff (Netherlands)
  • Chris Manger (USA)

So, what do we know 6 months in?

It’s been full six months since solution specialist certification rolled out and we were happy to work our way through the training materials right away.

Honestly, preparations for the exam were really useful for everyone involved as they brought us back (especially some of our senior developers) to the basics and allowed us to step back a bit and take a look at Magento as a whole – something that somehow gets lost in a bunch of custom projects you’re involved at all times.

Is this a useful investment? In our book – certainly. However, it depends on what your starting position is – for most of us who have been around Magento for 5-6 years, the preparations and the actual exam are nothing to fear and most of us passed it more or less easily. So, it really doesn’t require that much time to prepare. And let’s be honest, we’ve all spent much more than $260 on some things of questionable usefulness.

Will it (or does it) lead to more, bigger, better clients for companies who get their people certified? Time will tell, but in our experience it is a good extra selling point as more and more clients will be in need of a real solution partner rather than “just” a development company, so positioning yourselves as someone who can not only deliver great code but be there as a true partner for a new or established eCommerce business is something that will be essential for companies like ours.

What do you think?

Have you already gotten your certificate? If not, what’s stopping you? If yes, what benefits to your organization do you see?

Disclaimer:
The data for this article is taken from the official public Magento certification directory as well as some of the partners listings and the “heavy crunching” required for this article was completed on November 19, 2014 – if you got certified and had your profile published in the meantime, you may want to wait for this post to be updated :)

There’s also a possibility that not all people who got certified made their profiles publicly searchable, so the actual number of certified developers is probably a bit higher.

The post State of Magento Solution Specialist Certification – November 2014 appeared first on Inchoo.

]]>
http://inchoo.net/ecommerce/state-magento-solution-specialist-certification-november-2014/feed/ 4
6 ways to prepare your online storefront for holiday season http://inchoo.net/ecommerce/6-ways-prepare-online-storefront-holiday-season/ http://inchoo.net/ecommerce/6-ways-prepare-online-storefront-holiday-season/#comments Thu, 30 Oct 2014 16:55:13 +0000 http://inchoo.net/?p=23621 Holiday season is just around the corner, consumers are eager to start shopping, there is no doubt — it’s very important time for any retailer in the business. There are numerous marketing strategies to prepare your online store for all that online rush, but hey — let’s not forget that sometimes a pleasant holiday atmosphere...

The post 6 ways to prepare your online storefront for holiday season appeared first on Inchoo.

]]>
Holiday season is just around the corner, consumers are eager to start shopping, there is no doubt — it’s very important time for any retailer in the business. There are numerous marketing strategies to prepare your online store for all that online rush, but hey — let’s not forget that sometimes a pleasant holiday atmosphere can play a quite important role.

In this article I’ll present 6 possible ways to prepare your Magento storefront for the upcoming holiday season.

1. Redesign only certain parts of your store

One of the most widely used approaches is to enhance certain visual aspects of the store for the upcoming Holiday. For example… it’s Christmas time! Ho Ho Ho! First thing that instantly comes to my mind is a picture of our old friend Santa with his reindeers flying around and delivering presents. Using our logo facelift to identify with the holidays will spontaneously attract customers more. Ideas are countless, but placing a good looking Santa cap to your logo can really do the job.

2. Lift up specific category

Sometimes a simple logo facelift just isn’t good enough. Imagine this — you’re preparing your store for Halloween and you have a full set of items ready to be placed under Halloween category. Why not to try lift up the visuals for the whole category? I’m pretty sure that customers will feel “the holiday fever” way more while browsing, which may ultimately result in improved conversions.

Luckily Magento comes with really powerful built in theme inheritance system to help us accomplish our goal. Let’s quickly inspect how we can set things up for good.

Once in administration /Catalog/Category options, there are useful updates under the category Custom design tab. Besides usual global layout updates, mechanism allows us to override default theme with our new/desired one but only for this specific category. System provides settings for theme “active-from-to” period and even to apply theme updates for all child product pages as well.

Category specific theme

Let’s see how this may work in one specific example.

Default theme

Figure 1. Category with default visual identity applied.

Halloween

Figure 2. Desired outcome with just a few simple theme/style overrides.

It all depends how far you may want to go. The more effort you put in the design lift, the result will provide better visual appearance as a regard.

3. Let the interface do the talking

In some occasions you’ll find yourself considering to lift the visuals in general for the whole store. For example — you want to transform your store into one nice Christmas ambient unity, from header to footer, from page to page, from home page to the store checkout etc. Magento comes with an answer to this one too! :)

Once inside Magento Administration under System/Design there is ability to set up complete design change for a scheduled period of time. It’s quite similar to the mechanism inside category options but refers to a complete store instead.

Custom design

If that’s the result you’re looking for, certainly go for it! :)

4. Prepare newsletter emails

Don’t forget to lift up design for the newsletters. Be sure to add a little of that holiday magic to the emails too. :)

5. Add interesting effects to your store

If all of that simply wasn’t enough, we have one last advice. Add a little bit of interactive effects to your store. In fact, let it snow. :)

We have developed Magento extension just for that. http://inchoo.net/magento/snowflakes-magento-extension/

Enjoy!

6. Posibilities are countless, use your holiday imagination.

Happy Holidays! Ho, Ho, Ho! :)

The post 6 ways to prepare your online storefront for holiday season appeared first on Inchoo.

]]>
http://inchoo.net/ecommerce/6-ways-prepare-online-storefront-holiday-season/feed/ 3
Frontend performance: why should you care? http://inchoo.net/ecommerce/magento-frontend-optimization/ http://inchoo.net/ecommerce/magento-frontend-optimization/#comments Thu, 23 Oct 2014 10:47:54 +0000 http://inchoo.net/?p=23574 Speed is the most important feature of any online store. If it is too slow, people will just not use it. It may have great products, low prices, the most beautiful interface, but if it takes forever to load, people will just go away. In the offline world, we often have to wait. Line in...

The post Frontend performance: why should you care? appeared first on Inchoo.

]]>
Speed is the most important feature of any online store. If it is too slow, people will just not use it. It may have great products, low prices, the most beautiful interface, but if it takes forever to load, people will just go away.

In the offline world, we often have to wait. Line in the bank, security control at the airport, checkout line at the grocery store… pick your favorite. “I had such a great time waiting in the line for 3 hours” – said no one, ever…  People hate waiting.

On the other hand, in the online world, we have ability to serve thousands of customers in the same time. Immediate service. And that’s exactly what people expect. They expect websites to load in 2 seconds or less. Almost half of customers will abandon the website if it takes more than 3 seconds to load, and most of them will never come back. (Source: Kissmetrics)

Bad performance = Bad business

It is obvious that loading time has direct impact on customer satisfaction, conversions, and in the bottom line, on sales. It is proven that 1 second delay in loading time results in 7% less conversions. Amazon discovered that 100ms of latency costs them 1% less revenue. On the other hand, statistics show that websites are getting slower every year. It is almost like they are losing money on purpose. Why is that?

slow

Graphic credit: radware.com

Websites are getting fatter

Websites are getting more complex with every year. We also have bigger screens and bigger images, and that is the main reason why web pages are getting “fatter”. Namely, around 60% of page weight consists of images.

chart

It is a Mobile-first world

I already stated that screens are getting bigger. Well, kind of… They are getting smaller at the same time. Smartphone and tablet usage is climbing sky-high. We can now say that 55% of online traffic these days come from mobile. And mobile users expect sites to load as fast or even faster than on their desktops. And that’s hard to achieve due network constraints.

Although the mobile networks are getting faster, web browsing experience on 4G did not dramatically change compared to 3G. As it appears, bandwidth is not the biggest constraining factor – it’s the latency. Simply put, latency is time needed for a request to go from the browser to the server and back. Latency is important on wired connections, so you can imagine how critical it is on mobile networks.

The mobile web is a whole different game, and not one for the better. If you are lucky, your radio is on, and depending on your network, quality of signal, and time of day, then just traversing your way to the internet backbone can take anywhere from 50 to 200ms+. From there, add backbone time and multiply by two: we are looking at 100-1000ms RTT range on mobile.

 

What to do?

So far we stated:

  1. Speed is important
  2. Users expect 2 seconds
  3. Mobile traffic is dominant
  4. Mobile web is slow

We have a problem here… So, what can we do to avoid this pittfall of bad performance? Follow these rules, and you’ll be on the right track…

Speed is a feature, treat it as a part of UX

Speed is directly responsible for conversion rates. It is your competitive advantage. A feature of your store… And it is equally or even more important than your fancy newsletter subscribe form or one-step checkout. If your website is slow, no one will use it. Period.

Treat performance as a part of UX design, plan it in advance and make sure you secure a budget for it.

Speed is the most important feature. If your application is slow, people won’t use it. I see this more with mainstream users than I do with power users. I think that power users sometimes have a bit of sympathetic eye to the challenges of building really fast web apps, and maybe they’re willing to live with it, but when I look at my wife and kids, they’re my mainstream view of the world. If something is slow, they’re just gone.

Fred Wilson: “The 10 Golden Principles of Successful Web Apps”

Mobile first, content out workflow

Always start with the content. Structure it to create focus and hierarchy. Putting your content on a small screen, where your screen real estate is limited, will make you think twice what is most important and what deserves to be on the screen. It will give you an opportunity to reevaluate what content or functionality is necessary, and not just for the mobile version.
Performance will also benefit from mobile first approach. Think of it as building a house. You start with the foundation, and build on top of it.

Start with a baseline mobile experience and enhance with a media query cascade that starts from small screens and adapts design as screens get bigger.

mobile-first

Set a performance budget

We are not talking about money here. Performance budget is a clever idea introduced by Tim Kadlec.

A performance budget is just what it sounds like: you set a “budget” on your page and do not allow the page to exceed that.

Your “budget” may be number of requests, page weight, loading time…  It is important to declare your goals early in the planning phase. If you set a goal to have a homepage under 500Kb and then desing a content slider with 5 full screen images, it is going to be impossible to achieve your goals.

Once you achieved your goals, you should stick to them. Next time you want to add something to the page, you need to make sure it doesn’t exceed the budget. If it is does, there are three options:

  • Optimize an existing feature or asset on the page.
  • Remove an existing feature or asset from the page.
  • Don’t add the new feature or asset.

Optimize!

This is the part you need to be religious about it. Squeeze the last Kb out of your website. The main goals of optimization are:

  • Reduce http requests
  • Reduce file sizes
  • Reduce latency
  • Optimize critical rendering path

There are numerous techniques to achive this goals, and we will cover them in our future blog posts. Stay tuned!

The post Frontend performance: why should you care? appeared first on Inchoo.

]]>
http://inchoo.net/ecommerce/magento-frontend-optimization/feed/ 6
Book Review – Mastering Magento Theme Design by Andrea Sacca http://inchoo.net/magento/book-review-mastering-magento-theme-design/ http://inchoo.net/magento/book-review-mastering-magento-theme-design/#comments Thu, 09 Oct 2014 10:50:27 +0000 http://inchoo.net/?p=23466 It is widely assumed that knowledge is power and that it has to be built upon every day. It takes a lot of time to put a person’s knowledge into a book. For the past few months I was looking for a new book in order to expand on my frontend knowledge. Due to lack...

The post Book Review – Mastering Magento Theme Design by Andrea Sacca appeared first on Inchoo.

]]>
It is widely assumed that knowledge is power and that it has to be built upon every day. It takes a lot of time to put a person’s knowledge into a book. For the past few months I was looking for a new book in order to expand on my frontend knowledge.

Due to lack of free time I usually read only particular sections of some book or I surf the Internet if I need a specific piece of information.

Long story short, just at that time Andrea Sacca published his new book which covers one of my favorite topics – Creating responsive Magento theme. As one of the frontend developers at Inchoo, I had the opportunity to receive one of first copies of that book (and a copy for entire Inchoo). To return the favor to the author, we were asked to write a review of the book.

First impression

The cover page of the book shows something that I personally don’t like very much: “Responsive Magento themes using Bootstrap.” Nevertheless, I decided to give the author a chance.

The book is very useful for developers who have just started Magento career, such as myself. I have found some very practical information that will hopefully help me in my future career. I believe that many young developers will be able to find good tricks in the book. In addition to this, the book can also be a helpful reminder for developers who already have some experience in Magento development.

Book evaluation

As already mentioned, this book is focused on Bootstrap integration in Magento. To be honest, this approach doesn’t appeal to me in particular. It is adorable that frameworks will speed up development process but adding one more framework in the mix is not a clean and nice solution. Although it is nowadays all about speed and flexibility, we need to develop responsive, light and agile web stores. In such cases each part of the puzzle should be taken into consideration in order to make the users satisfied. And adding some frontend framework just seems as unnecessary added weight on already heavy Magento.

The book obviously has not changed my mind and I still believe that custom solutions are a better option, especially the ones based on SASS or LESS preprocessors which can speed your development process and provide good flexibility. If you have time to put a few mixings together, it can really help you keep your code on a “diet”. It is known that frameworks are not just CSS but if you need something else specific, you can always borrow a good solution from frameworks or internet without adding all the extra unnecessary code.

Moreover, if we take a look at new Magento 2 frontend ideas and solutions, it is clearly seen that new Magento frontend developers do not implement any frontend framework; instead they decide to go with custom solution established on LESS preprocessor.

Conclusion

To sum up, this book is very useful for fellow frontend developers who are starting to get their grips on Magento.

It contains several very useful chapters, and I’ll highlight three of them:

  • Adding Incredible Effects to Our Theme
  • Creating a Magento Widget
  • Creating a Theme Admin Panel

I would certainly recommend this book as an integral part of the learning process for junior frontend developer audience, but would also advise everyone to expand on this knowledge and try their own approaches when creating amazing responsive websites.

If you are interested, you can get your copy of this book here.

Regards and good luck with your responsive Magento projects!

The post Book Review – Mastering Magento Theme Design by Andrea Sacca appeared first on Inchoo.

]]>
http://inchoo.net/magento/book-review-mastering-magento-theme-design/feed/ 8
Magento without an IDE? Say hello to Sublime! http://inchoo.net/dev-talk/programming-magento-without-ide-say-hello-sublime/ http://inchoo.net/dev-talk/programming-magento-without-ide-say-hello-sublime/#comments Tue, 30 Sep 2014 09:45:00 +0000 http://inchoo.net/?p=23350 We are all aware of the Magento codebase size and its complexity. That is one of the reasons most people use full-fledged IDEs for Magento programming. Most answers regarding the “what IDE should I be using for Magento?” or “what is the best Magento development environment?” include big boys like Eclipse, NetBeans and PhpStorm. Since...

The post Magento without an IDE? Say hello to Sublime! appeared first on Inchoo.

]]>
We are all aware of the Magento codebase size and its complexity. That is one of the reasons most people use full-fledged IDEs for Magento programming. Most answers regarding the “what IDE should I be using for Magento?” or “what is the best Magento development environment?” include big boys like Eclipse, NetBeans and PhpStorm. Since you’ve already read the title, you may be wondering: “what can a text editor like Sublime Text offer me for my Magento development?”. Vanilla installation? Not much, but with the help of a few plugins, well… Keep reading and you just may be in for a treat.

Package manager

If you’ve ever used Sublime text you have probably stumbled upon its package manager. It is a great tool that lets you choose from hundreds of available plugins and install them in no time. This is the foundation for sublime customization, and you should install this immediately.

After you’ve installed it, you have access to hundreds of awesome plugins. But which ones should you choose? Which ones are good for Magento/PHP/general development? Here are the ones I use for my day-to-day work.

CTags

So, you’re accustomed to the “click to go to definition” functionality that most IDEs give you. Unfortunately Sublime text does not have this feature. But CTags is here to help. What it does is it generates an index file of language objects (eg. method names, class names etc.) and allows for them to be easily located. After you’ve installed it from the package manager (and read the installation instructions on github), you should build your tags. The way to do that is through “Find > CTags > Rebuild Tags” menu item, but I prefer a key bind. To add a key bind add this to your sublime-keymap file (Preferences > Key Bindings User):

[
  { "keys": ["alt+shift+c"], "command": "rebuild_tags" },
  { "keys": ["alt+shift+v"], "command": "navigate_to_definition"}
]

Now you can build your tags by pressing alt+shift+c, or any other key combination you want. After some time (usually ~30-40 seconds for Magento projects), CTags will build your tags and you can start to harness their power. How? Just place your text cursor on the class or method name, press the key combination for ‘navigate to definition’ command and the class will be automatically opened. As for the methods, if their name is unique, they will also be automatically opened, but if there are more methods with the same name, they will be shown in a list so you can choose where to go. CTags is a great package to have in order to improve your coding speed.

SublimeLinter

Don’t you hate it when you code for some time and refresh the browser only to find you have at least 5 syntax errors (missing semicolons or the infamous Paamayim Nekudotayim error)? “This would not happen if I was using Eclipse.”, you might say. Don’t give up yet, there is a cure for this problem too, and it’s called SublimeLinter. It supports many languages out of the box, including PHP so it is a great tool in our toolbelt. In order to make it work, you have to add a path to PHP installation in its configuration. In my case it was this:

"sublimelinter_executable_map":
{
  "php":"/usr/bin/php"
}

Try to make an error now, and sublime will greet you with a red error mark and the explanation of the error in the status bar.

sublime_2

XMLLinter

Magento uses a lot of XML. Before I found out about this plugin I used to spend a lot of time debugging even the silliest xml syntax errors in Sublime text.  It was really a pain. But fortunately we have a plugin for that too, XMLLinter. Missing the closing tag, misspelling tag name and other errors are a thing of the past, since we are now able to detect XML errors instantly.

So far so good.

PHP snippets

Sublime text already has a lot of PHP code snippets out of the box, but few more always come in handy: Additional PHP Snippets and PHP Completions Kit are good enough. One thing I should like to point is that I had some trouble with triggering the snippets in .phtml files. The solution was to change the “auto_complete_selector” default configuration value, ie. place this

"auto_complete_selector": "source, text"

into your ‘Preferences.sublime-settings’ file (Preferences > Settings – User), and it should work.
You want an “if” statement in your .phtml? No problem. Start typing.

sublime_4

Press enter and – poof:

sublime_5

XDebug

If you use a debugger while developing, you should look into packages that add support for xDebug. Possibly this or maybe this one.

Sublime-magento

This is a plugin that I have been contributing to, not really an original naming – sublime-magento, but it has some really nice features like:

  • inserting the proper class name on a simple key press (so you don’t have to write it yourself)
  • opening file path within quotes (especially useful for template paths in XML files)
  • creating a module (insert module name and it will be created with all the necessary files and configuration)
  • additional code snippets

 

With these packages in your arsenal, you are good to go. You have the power to tackle even the hardest Magento-related tasks. But let’s not wrap this up yet. We are developers, and we like to have a clean and neat code. Also, we like our development environment to look good (if not awesome), so here are few more plugins you should really look into because they provide for a much better programming experience in Sublime text.

AdvancedNewFile

AdvancedNewFile is a pretty neat plugin that allows you to easily create files within your project. No need to touch your mouse, just place a key bind. I have it like this:

{ "keys": ["shift+alt+n"], "command": "advanced_new_file_new"}

SidebarEnhancements

Out of the box sublime does not come with a lot of options in context menu of right click. In order to fix that, you should use SidebarEnhancements. It provides enhancements to the operations on sidebar of Files and Folders, and it’s really cool. Officially supported only for Sublime text 3, but there are some workarounds to make it work in ST2 also.

GoToDocumentation

If you are not really familiar with PHP standard library, or simply have trouble remembering the order of the parameters in a function call, you should consider using GoToDocumentation plugin. Place your cursor over the method name, press the designated key, and documentation page should open up in your browser, be it PHP, HTML, CSS or any other supported language.

Trailing Spaces

I really hate trailing spaces in code, and so should you.  With this plugin you will be able to detect and delete them in a matter of seconds. CAUTION: other developers in your team probably don’t care about this so you may start hating them too.

sublime_6

Code with trailing spaces

Git/Github packages

If you use git without a command line, you could use the Git and Github Tools plugins. Just search for them in package manager.

Syntax Highlight and themes

Even though the default syntax highlighting in Sublime is pretty good (monokai theme), you can always find something that suits you better. I really like the Phix color scheme and the SODA Dark theme.

There are lots of other tips and tricks for Sublime text and it would be really great to write about them too, but let’s leave that for some other time. You may have also noticed that these plugins (at least the ones from the beginning) were mostly backend related. Sublime has some nice plugins for frontend too like Emmet or jQuery. I recommend that you go through the list of packages in Package Manager and try some of them. It is really easy to add them, and remove, too.

NOTE: all plugins were tested and working on ST2, but they should work without problems in ST3 also.

The post Magento without an IDE? Say hello to Sublime! appeared first on Inchoo.

]]>
http://inchoo.net/dev-talk/programming-magento-without-ide-say-hello-sublime/feed/ 3
Magento Grid Serializer for Ajax Grids http://inchoo.net/magento/magento-grid-serializer-ajax-grids/ http://inchoo.net/magento/magento-grid-serializer-ajax-grids/#comments Mon, 22 Sep 2014 09:00:51 +0000 http://inchoo.net/?p=23268 In Magento selectable grids, each time after a grid operation is performed (whether it’s filtering the results, pagination or something else), the selected values get lost. In other words, Magento by default keeps only values selected after the grid is initialized in the beginning and loses them each time the grid gets reloaded by Ajax...

The post Magento Grid Serializer for Ajax Grids appeared first on Inchoo.

]]>
In Magento selectable grids, each time after a grid operation is performed (whether it’s filtering the results, pagination or something else), the selected values get lost. In other words, Magento by default keeps only values selected after the grid is initialized in the beginning and loses them each time the grid gets reloaded by Ajax call. Fortunately, Magento has a built-in library called Grid Serializer to solve this issue.

To make it simple, Grid Serializer is nothing more than a container made of two parts: a grid and a hidden input field that are distinctly separated. Hidden input is used for storing serialized selected id’s and is populated with Javascript each time a row is selected. As it’s separated from the grid, the hidden input content is preserved each time the grid is reloaded and is used for auto-selecting the rows each time the grid is reloaded.

Before we start with the code, as this is a bit more advanced grid, it is assumed that you’ve already mastered the basics of Magento grids since only differences will be shown how to create ajax-like grids.

Structure

It is already said that Grid Serializer splits the grid in two parts where only the grid gets reloaded, leaving the rest of the page intact as opposite to default action where the entire tab is reloaded. In order to make that happen, layout and controller has to distinguish those two parts. As it can be seen on the image below, we have to separate the entire tab content, which initially includes the a hidden input type and the grid itself, and the grid that will be replaced.

For this we’ll need to have two actions defined in both layout file and controller: customersTab and customersGrid (or the tab and grid content).

Admin Ajax Grid

Visible in the code below, the tab content defined by the <adminhtml_ajaxgrid_customerstab> handle includes both the grid content and the hidden input type blocks, while the grid-only content defined by the <adminhtml_ajaxgrid_customersgrid> handle is a stripped version of the tab content, containing only the grid block.

<?xml version="1.0"?>
<layout>
    <adminhtml_ajaxgrid_edit>
        <reference name="left">
            <block type="inchoo/adminhtml_edit_tabs" name="ajaxgrid.edit.tabs" />
        </reference>
        <reference name="content">
            <block type="inchoo/adminhtml_edit" name="ajaxgrid.index" />
        </reference>
    </adminhtml_ajaxgrid_edit>
 
    <adminhtml_ajaxgrid_customerstab>
        <block type="core/text_list" name="root" output="toHtml">
            <block type="inchoo/adminhtml_edit_tab_customers" name="inchoo.tab.customers"/>
            <block type="adminhtml/widget_grid_serializer" name="inchoo.serializer.customers">
                <action method="initSerializerBlock">
                    <grid_block_name>inchoo.tab.customers</grid_block_name>
                    <data_callback>getSelectedCustomers</data_callback>
                    <hidden_input_name>customer_ids</hidden_input_name>
                    <reload_param_name>customers</reload_param_name>
                </action>
            </block>
        </block>
    </adminhtml_ajaxgrid_customerstab>
 
    <adminhtml_ajaxgrid_customersgrid>
        <block type="core/text_list" name="root" output="toHtml">
            <block type="inchoo/adminhtml_edit_tab_customers" name="inchoo.tab.customers"/>
        </block>
    </adminhtml_ajaxgrid_customersgrid>
</layout>

In order to make Grid Serializer work, all those things have to be linked somehow, which is why the hidden input field has a few parameters left to be defined.

grid_block_name: name of the grid block
data_callback: method in the grid block that returns selected id’s on grid initialization
hidden_input_name: name of the hidden input type
reload_param_name: name of the parameter used in URL

Having the layout defined, controller has to add some actions to those handles. The tab action (customersTabAction) is supposed to collect the id’s, which is usually a collection that returns the id’s as array, to load the block defined in the layout and to set the selected id’s.

The grid action (customersGridAction) does the similar thing to the tab action, again, but the difference is that the selected id’s are read from the URL parameter which is defined in the layout (reload_param_name).

In the save action selected id’s are taken from the hidden input field from the parameter name defined in the layout (hidden_input_name). Grid Serializer comes with a handy method called decodeGridSerializedInput() which decodes a string into an array.

class Inchoo_Module_Adminhtml_AjaxgridController
extends Mage_Adminhtml_Controller_Action
{
    public function editAction()
    {
        $this->loadLayout()
            ->_title($this->__('Customer Grid'))
            ->renderLayout();
    }
    public function customersTabAction()
    {
        // used for selecting customers on tab load
        $saved_customer_ids = array(); // your load logic here
 
        $this->loadLayout()
            ->getLayout()
            ->getBlock('inchoo.tab.customers')
            ->setSelectedCustomers($saved_customer_ids);
 
        $this->renderLayout();
    }
    public function customersGridAction()
    {
        $this->loadLayout()
            ->getLayout()
            ->getBlock('inchoo.tab.customers')
            ->setSelectedCustomers($this->getRequest()->getPost('customers', null));
 
        $this->renderLayout();
    }
    public function saveAction()
    {
        if ($customersIds = $this->getRequest()->getParam('customer_ids', null)) {
            $customersIds = Mage::helper('adminhtml/js')->decodeGridSerializedInput($customersIds);
        }
 
        // your save logic here
    }
}

Blocks

Having the structure of the tab defined, there are only a few things left to change in the blocks. Let’s start with the Tab block first.

In the _beforeToHtml() method, where the tabs are usually defined, several parameters have to be changed. The first thing would be to omit the content parameter, that is usually set by default, and to replace it with the “url” parameter. That parameter has to define an url for the tab content. The other thing would be to add a class parameter with the value “ajax”.

protected function _beforeToHtml()
{
    $this->addTab('customers', array(
        'label'     => $this->__('Customers'),
        'title'     => $this->__('Customers'),
        'url'       => $this->getUrl('*/*/customerstab', array('_current' => true)),
        'class'     => 'ajax'
    ));
    return parent::_beforeToHtml();
}

The last file that has to be changed is the grid block itself. One thing would be to use a variable containing the selected id’s, which is set by the controller (getSelectedCustomers() in this example). The other thing would be to define an URL of the grid content, which is why a getGridUrl() method is defined. Make sure to have setUseAjax(true) variable defined as well.

class Inchoo_Module_Block_Adminhtml_Edit_Tab_Customers
extends Mage_Adminhtml_Block_Widget_Grid
{
    public function __construct()
    {
        parent::__construct();
        $this->setSaveParametersInSession(false);
        $this->setUseAjax(true);
        $this->setId('inchoo_customers');
    }
    protected function _prepareCollection()
    {
        $collection = Mage::getResourceModel('customer/customer_collection')
            ->addNameToSelect();
 
        $this->setCollection($collection);
        return parent::_prepareCollection();
    }
    protected function _prepareColumns()
    {
        $this->addColumn('selected_customers', array(
            'header'    => $this->__('Popular'),
            'type'      => 'checkbox',
            'index'     => 'entity_id',
            'align'     => 'center',
            'field_name'=> 'selected_customers',
            'values'    => $this->getSelectedCustomers(),
        ));
 
        $this->addColumn('name', array(
            'header'    => $this->__('Name'),
            'index'     => 'name',
            'align'     => 'left',
        ));
 
        return parent::_prepareColumns();
    }
    public function getGridUrl()
    {
        return $this->getUrl('*/*/customersGrid', array('_current' => true));
    }
}

The result

Not a lot of changes are needed to create ajax-like grids in Magento by using Grid Serializer while a lot of things can have benefit of that. With this library selected id’s are preserved on grid reloads which is why pages can be changed, filters applied, sorting changed without losing the selection.

Additionally, the grid won’t be populated and collections won’t be run until the tab is selected. Having that in mind, it has to be extra careful when saving id’s since it has to distinguish whether none of the id’s are selected and when the tab is not loaded at all.

Ajax Admin Grid

There’s a small glitch that I have found when working on this. When viewing a grid on smaller screens where the grid can be scrolled up and down, each time a row is selected the page jumps to the top. It’s a bit irritating, but looking around in Magento I have found a few of the pages that use Grid Serializer natively and that share the same problem (some of the pages have custom solutions), but that’s really a tiny thing compared to what the library offers.

The post Magento Grid Serializer for Ajax Grids appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-grid-serializer-ajax-grids/feed/ 3
How We Built Sheeel iOS Application http://inchoo.net/dev-talk/ios-development/how-we-built-sheeel-ios-application/ http://inchoo.net/dev-talk/ios-development/how-we-built-sheeel-ios-application/#comments Thu, 18 Sep 2014 06:36:04 +0000 http://inchoo.net/?p=23294 A while ago, we released the first version of iOS application for Middle Eastern favorite, award-winning daily deal site – Sheeel.com Our goal was simple – to create natural environment for iOS users to receive notifications about new deals and breeze through the process of exploring and shopping. Even though Sheeel.com features an adaptive site...

The post How We Built Sheeel iOS Application appeared first on Inchoo.

]]>
A while ago, we released the first version of iOS application for Middle Eastern favorite, award-winning daily deal site – Sheeel.com

Our goal was simple – to create natural environment for iOS users to receive notifications about new deals and breeze through the process of exploring and shopping.

Even though Sheeel.com features an adaptive site which performs superb on a variety of devices, iOS handcrafted experience will help users stay in touch with notification system that helps them be the first to get their hands on the great deals from Sheeel.

With the launch of both iPhone and iPad versions you can download for free on iTunes, we are extending Sheeel’s already established identity as an innovative commerce company in the Middle East market.

For those of you unfamiliar with the project, Sheeel.com is a daily deal site based in Kuwait, operating in a number of Middle East countries, and you can learn more about our previous work with them in our portfolio.

Why iOS?

iOS powered devices are taking over mobile market in Middle East and to accommodate that growing market and fit users needs, developing an application for this platform was the next logical step in reaching out to ever growing base of new customers and the evolution of Sheeel brand.

How We Built The Application

Since Sheeel’s primary focus is on user needs and ease of access to everyone regardless of the platform and device, the app itself had to be designed and developed with same practice in mind – to allow every iOS user to quickly get notifications for new deals and browse through them without unnecessary clutter.

We researched, wireframed and iterated numerous versions of design. The process involved researching elements and UI patterns that are already in use by iOS applications, how users perceive and react to them and finally applying what we learned to create a great user experience for Sheeel users.

sketching

The main challenge to deal with was building an application that can serve both left-to-right and right-to-left layouts in order to fulfil the market needs while maintaining user flow and providing straightforward instructions and calls to actions. Having an experienced team at Inchoo who already worked on projects in the same market made the entire process a whole lot easier.

Repeating steps of research, wireframing and design while presenting and conducting tests with people who were not originally involved with the project resulted in where we are today. It took quite a few number of iterations, experimenting and ideas to get the things where we wanted them and where our test subjects felt comfortable while using the application itself and breezing through the process of exploring the deals and finally making a purchase.

prototyping

Prototyping the app’s design was a huge part of testing; it helped us to quickly iterate and resolve common problems while using the application and observing how people expected it to behave, which led to achieving the natural user flow in the final iteration. Ideas for features where piling up quickly and gave us insights into what we could include in the future, but we focused on keeping the core functionality of Sheeel as well as its simplicity, rather than spending too much time around nifty features in our user tests. Less is more.

Having color scheme and Sheeel’s strong identity already defined and widely recognized in the region through main website Sheeel.com, we faced quite a challenge blending it with iOS 7 design guidelines and best practices in mind, and the team tackled this with passion. Since we had the features and functionality sealed and tested in our early prototypes that gave us clear and precise answers to usability and behavioral questions, numerous design iterations, experiments and mashups were being thrown out until we iterated the right one that suited the needs of market and it’s users and represented Sheeel’s identity to the fullest.

screens

Mobile development and cooperation with Plava Tvornica

Our partners from Plava Tvornica (Blue Factory) were in charge of the mobile development aspect of the project. They are a Croatian company who have years of experience and excel at building mobile apps. Our design and ideas combined with the team of highly experienced professionals from Blue Factory resulted in a clear and concise workflow which allowed us to build and iterate prototypes quickly and efficiently to create a great user experience.

Conclusion

Nonetheless, versatility of the team involved, their knowledge and vast experience with previous projects in the same market pushed us in the right direction while building the application and adapting features and functionalities for iOS 7 powered devices to the ultimate satisfaction of its end users.

With the launch of the iPhone and iPad applications we have opened up a completely new frontier for Sheeel’s business to grow and attract new customers and serve existing ones using the iOS platform as their everyday tool of choice to get things done quickly and efficiently.

The post How We Built Sheeel iOS Application appeared first on Inchoo.

]]>
http://inchoo.net/dev-talk/ios-development/how-we-built-sheeel-ios-application/feed/ 4
UI/UX guidelines for different types of customers http://inchoo.net/ecommerce/ui-ux-guidelines-different-types-customers/ http://inchoo.net/ecommerce/ui-ux-guidelines-different-types-customers/#comments Tue, 02 Sep 2014 07:15:00 +0000 http://inchoo.net/?p=22749 There are plenty of things to consider when designing an eCommerce site, but the most important is to design with the customer in mind. Different shopper types have different goals and shopping strategies, and being aware of these helps us make decisions that improve store usability. While analysing some of our recent projects, and taking...

The post UI/UX guidelines for different types of customers appeared first on Inchoo.

]]>
There are plenty of things to consider when designing an eCommerce site, but the most important is to design with the customer in mind. Different shopper types have different goals and shopping strategies, and being aware of these helps us make decisions that improve store usability. While analysing some of our recent projects, and taking into account a number of articles around this topic, we decided to break down online customers into five main types.

The Bargain Hunter

customer-type-bargain-hunter What motivates the bargain hunter?

  • Coupons
  • Promotions
  • Discount codes
  • Sales
  • Free Shipping

Brand loyalty: Low Purchase probability: Medium Description: The Bargain Hunter will spend a lot of time to find the best deal. Usually they will start their search on some of the comparison shopping sites. They are less loyal to a brand and are just looking for the lowest price. Those with stronger brand loyalty will connect with your store via social media waiting for a coupon codes and sales. They often buy products on sale which they don‘t really require, as a excuse they would say that they are actually saving money in the long run by getting everything they need during the sale. UI/UX guidelines for Bargain Hunters:

  1. Products on sale must be clearly indicated – bargain hunters want to locate special deals as soon as they land on a page. Use labels, icons and other visual elements to make these items stand out. If possible, create On Sale category so they can see all sale-priced items listed on one page.
  2. List prices, discounts and savings – Cross out retail price and show discounted one, including amount saved.
  3. Make newsletter forms easy to find and appear in multiple places on your site – give them a clear value proposition for why they should join your email list (exclusive discounts).
  4. Easy coupon redemption – prominently display a coupon field on the cart or checkout page. Don’t forget to explain how to get codes (modal or tooltip) to avoid the risk of losing customers who don‘t have a code and will leave the site in search of a discount.
  5. Define a discount based on a specific criteria – e.g. free shipping for customers who spend at least $90.

Power Shopper (product focused)

customer-type-power-shopper What motivates the power shopper?

  • specific need (a replacement for something they have or something completely new)
  • necessity

Brand loyalty: Low / Medium Purchase probability: Medium Description: Power shopper has a specific product on mind and they want to find their desired product in as few steps as possible. If they don’t find it, they will leave right away. UI/UX guidelines for Power Shoppers:

  1. Place your search bar in a prominent place and keep it consistently placed throughout the rest of the site – Power shoppers will probably use search option to find a product so be sure to make it easily visible. If possible use search bar with auto-complete option and display results with prominent sort and filter options.
  2. Clear navigation – keep your main navigation well-structured and simple. With well-planned content and a natural organization structure Power shoppers will find their desired products in a few clicks.
  3. Use clear product descriptions, recognizable names and large images so they can confirm it’s the right product – They don’t want to waste time looking around, they will take a look and if it match their criteria they’ll buy it.
  4. Provide a convenient and quick checkout – power shoppers values their time so if they found what they were looking for, they want to checkout as quickly as possible.

The Researcher

customer-type-researcher What motivates the researcher?

  • specific goal but not a specific product or brand
  • to gather as much information as possible

Brand loyalty: Low / Medium Purchase probability: Medium Description: The researcher is browsing with the intention of collecting enough information about products and prices. Search can last for a few days or a few months. They will probably visit multiple sites, so provide them information they need to keep them on your site. UI/UX guidelines for Researches:

  1. detailed product description – if you provide them with enough helpful data you will become their source of information and therefore turn them into buyers on your site. If they are left with unanswered questions, they will try to find the answers on other sites.
  2. provide related or similar products links- researchers don’t have a specific product or brand on mind so show them all options you have available.
  3. ratings and reviews – they rely on ratings and reviews when deciding on a product, so motivate your customers to write reviews and make them prominent on a product page.
  4. if you have many similar products provide easy product comparison – buying is preceded by a decision and the researcher is undecided, he is comparing the products until he makes a final decision so make it easier for him to make a final decision by comparing products.
  5. use a persistent shopping cart – more than 20% of shoppers confess using the shopping cart as a wish list to save products for future consideration. Allow them to think about a purchase for a few days and save the information for their next visit.

The Wandering Shopper

customer-type-wandering What motivates the wandering shopper?

  • no specific need or desire

Brand loyalty: Low/Medium Purchase probability: Low Description: Wandering shoppers don’t have a specific product or goal in mind, so it is a challenge to convert them into buyers. They study products with no intentions to purchase anything. Usually this is the largest segment in terms of traffic but they make up the smallest percentage of revenue. Anyhow, this is not a reason to ignore them but we should minimize the efforts to attract these shoppers. UI/UX guidelines for Wandering Shoppers:

  1. Make your site more visually appealing – they have time to socialize and if they are passionate about your site or specific products they will very likely communicate to others on social networks and blogs. They are often the source of good marketing.
  2. Keep them engaged – wandering shoppers in a retail store would pace up and down every aisle. They act similar in online store, if they like your site they will study the items, rate them, write reviews etc. Clear navigation, cross-sell, up-sell and vivid images will keep them engaged.

The Loyal Shopper

customer-type-loyal What motivates the loyal shopper?

  • good experiences with the store
  • quality

Brand loyalty: High Purchase probability: Medium/High Description: This segment usually make up more than 50% of sales. Price is secondary for loyal shoppers, they value quality over price. UI/UX guidelines for Loyal Shoppers:

  1. Encourage them to repeat orders – Allow them to save shopping carts and create lists of frequent purchases.
  2. Reward loyalty – customers like feeling appreciated so thank them for being loyal and encourage them to keep coming back. Whether it’s a discount or reward points they will appreciate the effort.

How to please everyone?

When you know all types of shoppers, their shopping strategies and needs, you should put yourself in their position and see if you have all key approaches to satisfy their varied needs. Share of shopper types can vary greatly from one store to another, but almost each store has these main 5 types of customers. Knowing their behavior patterns will help us identify their problems and two main factors for successful store is understanding who your customers are and solve their problems before they arise.

The post UI/UX guidelines for different types of customers appeared first on Inchoo.

]]>
http://inchoo.net/ecommerce/ui-ux-guidelines-different-types-customers/feed/ 0
Adding Magento products to Google Merchant Center http://inchoo.net/magento/adding-magento-products-to-google-base/ http://inchoo.net/magento/adding-magento-products-to-google-base/#comments Tue, 19 Aug 2014 07:00:57 +0000 http://inchoo.net/?p=3764 This post describes how to add Magento products to Google Merchant Center (formerly known as Google Base) using a product feed and explains some of the issues you might encounter. This post used to be about adding Magento products to Google Base, but since Google swapped Google Base for Google Merchant Center, I revamped this...

The post Adding Magento products to Google Merchant Center appeared first on Inchoo.

]]>
This post describes how to add Magento products to Google Merchant Center (formerly known as Google Base) using a product feed and explains some of the issues you might encounter. This post used to be about adding Magento products to Google Base, but since Google swapped Google Base for Google Merchant Center, I revamped this article with up to date data.

Setting up the Google Merchant Center account

Head over to Google Merchant Center page to create a Google Merchant account. Important note: While you’re able to add additional users to one Google Merchant Center account through Users tab in Settings, every Google account can be associated with only one Google Merchant Center Account.

Generating a Magento product feed for Google Shopping Ads

While there are several solutions in form of extensions out there, so far we’ve only used our custom in-house developed code to generate the product feed.

If you can’t have the in-house solution, there is an official extension by Magento called Google Content API for shopping, but that extension seem to have a lot of issue judging by reviews. I’d probably try some of the other solutions.

If you’ll create a custom product feed generation solution, here are the full feed specifications that you should go through.

EDIT: Good mates left some nice options for feed generation in the comments bellow this post so make sure to check those out.

Test the new product feed

Once you’ve generated your product feed, it’s time to upload it into the Google Merchant Center and test it. Within GMC, go to the data feeds tab.

There are two different upload buttons: “New Data Feed” and “New Test Data Feed”. Use the test feed to test if your feed is generated correctly.

Add and schedule your product feed to GMC

Once you tested the feed and solved any issues, go to the data feeds tab once again and add your feed using a “New Data Feed” button.

You can create a schedule for Google Merchant Center to automatically fetch your data feed at certain time of the day every day, week or month. In most cases I’d recommend generating and fetching the feed daily.

Common reasons for Google Merchant Center to disapprove your products

The most common reason for Google to disapprove your products are your images.

For apparel products images must be at least 250x250px. Max image size for any category is 4MB.

If your products (apparel) come in several colors or even sizes, Google requires a different image for each variation.

Your images may not have a watermark on them.

Your images must be crawl-able by Google, make sure you didn’t block them by robots.txt disallow or if they are on CDN make sure your CDN is not blocking them with CDN’s robots.txt disallow.

Apparel products must have a color. It can’t be a pattern (you can’t put “chevron pattern” under color attribute). You must use the “dominant” color instead, and you may add additional colors. Also color can’t be #ffffff, it needs to be the actual name of the color “white” or “black” or “red” etc.

Your price might have changed between the time you generated the feed and the time someone reviewed the products in your feed. If price is different in the feed compared to the price on the live store, your products will get disapproved.

Product reviews in Google shopping ads

Google recently started including product reviews into their shopping ads. This is only available in the USA, but when it comes to features such as this one we can expect it to roll out into UK and some other countries soon as well.

To be able to display your product reviews (and star rating) within your Google shopping ads, you need to either use one of the authorized 3rd party review systems (fore full list scroll down in this article) or create a product review feed.

In any case, you’ll need to fill out this form.

If you use your own product review system, as I mentioned, you’ll need to create a review feed. There are two types of feed supported, a full feed and an incremental feed. Click here for feed specifications and here for a sample feed.

Google Trusted Stores Feeds

Besides accepting product feeds for your shopping ads, Google Merchant Center is also a place where you submit your shipment and cancellation feeds for Google trusted stores program.

If you don’t know what this program is, it’s Google’s amazing trust seal that boosts the trust of your customers into your store since Google has your shipment and order cancellation data and asks your customers for feedback. Within GTS, Google also offers your customers up to $1,000 purchase protection.

Unfortunately, for now Google Trusted Stores program is only available in the US.

EDIT: Google Trusted Stores program is now available in the UK and it’s called Google Certified Shops program. We already have one of our clients accepted into the program!

The post Adding Magento products to Google Merchant Center appeared first on Inchoo.

]]>
http://inchoo.net/magento/adding-magento-products-to-google-base/feed/ 126
Custom shipping method in Magento http://inchoo.net/magento/custom-shipping-method-in-magento/ http://inchoo.net/magento/custom-shipping-method-in-magento/#comments Thu, 07 Aug 2014 11:30:39 +0000 http://inchoo.net/?p=2669 In this article, I will demonstrate how to write custom shipping in Magento, or to be more precise, two of them: standard shipping and express shipping, which is only available if none of your cart items exceeds specified weight threshold. Lets start by explaining how Magento handles shipping, and what would be needed to achieve...

The post Custom shipping method in Magento appeared first on Inchoo.

]]>
In this article, I will demonstrate how to write custom shipping in Magento, or to be more precise, two of them: standard shipping and express shipping, which is only available if none of your cart items exceeds specified weight threshold. Lets start by explaining how Magento handles shipping, and what would be needed to achieve our goal.


When looking for available shipping methods, Magento first gathers all available carriers. A “Carrier” represents a shipping carrier, just like in real world (ex. FedEx). Each carrier is represented with the class that extends Mage_Shipping_Model_Carrier_Abstract.

After list of carriers has been received, shipping information(implemented as Mage_Shipping_Model_Rate_Request) is sent to carrier in order to retrieve all available rates provided by given carrier, represented as Mage_Shipping_Model_Rate_Result.

This process happens in Mage_Shipping_Model_Shipping::collectRates() as seen in code below:

...
$carriers = Mage::getStoreConfig('carriers', $storeId);
 
foreach ($carriers as $carrierCode => $carrierConfig) {
    $this->collectCarrierRates($carrierCode, $request);
}
...

Function collectCarrierRates() is responsible for checking carrier availability (is carrier enabled in admin, is it available for requested country, etc.), and eventually triggers collectRates() function of your class, which we will implement later.

And that is general outline of what is going on behind the scenes. We are now ready to write some code that will fit nicely into everything explained above. First thing you will need to do, is create new module which depends on Mage_Shipping. Besides standard module configuration, you will have following inside your config.xml:

<config>
    ...
    <default>
        ...
        <carriers>
            <inchoo_shipping>
                <active>1</active>
                <model>inchoo_shipping/carrier</model>
                <title>Inchoo Shipping Carrier</title>
                <sort_order>10</sort_order>
                <sallowspecific>0</sallowspecific>
                <express_max_weight>1</express_max_weight>
            </inchoo_shipping>
        </carriers>
        ...
    </default>
    ...
</config>

Entries active, sallowspecific and express_max_items are config entries which will be used and explained later. We will start with model entry. You can see that our carrier will be represented with Inchoo_Shipping_Model_Carrier, so lets implement that class. As previously said, carrier needs to extend Mage_Shipping_Model_Carrier_Abstract and implement Mage_Shipping_Model_Carrier_Interface in order to ensure Magento can work with it. We will start by doing just that:

class Inchoo_Shipping_Model_Carrier
    extends Mage_Shipping_Model_Carrier_Abstract
    implements Mage_Shipping_Model_Carrier_Interface
{
    protected $_code = 'inchoo_shipping';

Next, our interface requires us to implement getAllowedMethods() which returns array of key-value pairs of all available methods, so let’s do that:

public function getAllowedMethods()
{
    return array(
        'standard'    =>  'Standard delivery',
        'express'     =>  'Express delivery',
    );
}

Finally, we said that rates are collected by calling collectRates(). This function takes shipping information as parameter, and returns all available rates. It is also responsible for determining which rate is available for given request:

public function collectRates(Mage_Shipping_Model_Rate_Request $request)
{
	/** @var Mage_Shipping_Model_Rate_Result $result */
	$result = Mage::getModel('shipping/rate_result');
 
	/** @var Inchoo_Shipping_Helper_Data $expressMaxProducts */
	$expressMaxWeight = Mage::helper('inchoo_shipping')->getExpressMaxWeight();
 
	$expressAvailable = true;
	foreach ($request->getAllItems() as $item) {
	    if ($item->getWeight() > $expressMaxWeight) {
		$expressAvailable = false;
	    }
	}
 
	if ($expressAvailable) {
	    $result->append($this->_getExpressRate());
	}
	$result->append($this->_getStandardRate());
 
	return $result;
}

As you can see, code is pretty straight forward: Weight of all products in cart are compared to value stored in config, which determines availability of our ‘express‘ rate. Our ‘standard‘ rate is always available. Each rate is added by passing Mage_Shipping_Model_Rate_Result_Method object to append() of our result object. And we get those rate objects by calling _getExpressRate() and _getStandardRate(), which are implemented as following:

protected function _getStandardRate()
{
    /** @var Mage_Shipping_Model_Rate_Result_Method $rate */
    $rate = Mage::getModel('shipping/rate_result_method');
 
    $rate->setCarrier($this->_code);
    $rate->setCarrierTitle($this->getConfigData('title'));
    $rate->setMethod('large');
    $rate->setMethodTitle('Standard delivery');
    $rate->setPrice(1.23);
    $rate->setCost(0);
 
    return $rate;
}

And that’s all that our class needs in order to work. We will finish up by adding admin configuration through system.xml. Here is an shortened version:

<config>
    <sections>
        <carriers>
            <groups>
                <inchoo_shipping translate="label">
                    ...
                    <fields>
                        <active translate="label">
                            ...
                        </active>
                        <title translate="label">
                            ...
                        </title>
                        <sallowspecific translate="label">
                            ...
                            <frontend_type>select</frontend_type>
                            <frontend_class>shipping-applicable-country</frontend_class>
                            <source_model>adminhtml/system_config_source_shipping_allspecificcountries</source_model>
                            ...
                        </sallowspecific>
                        <specificcountry translate="label">
                            ...
                            <frontend_type>multiselect</frontend_type>
                            <source_model>adminhtml/system_config_source_country</source_model>
                            ...
                        </specificcountry>
                        <express_max_weight translate="label">
                            ...
                        </express_max_weight>
                    </fields>
                </inchoo_shipping>
            </groups>
        </carriers>
    </sections>
</config>

What is important to note here, is that active, title, sallowspecific and specificcountry are handled automatically by Magento, so besides adding this to admin, you aren’t required to do anything else. With others being self explanatory, there are only two options being interesting here. First one, sallowspecific, tells Magento should carrier be available for all countries, or only for once that are specified in specificcountry.

And that is all work required for our shipping method to appear on checkout step. This module has been written as an example for Magento CE 1.9.0.1 and can be downloaded here.

Note: This is a revamp of an article originally written in July 2009.

The post Custom shipping method in Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/custom-shipping-method-in-magento/feed/ 29
State of Magento Solution Specialist Certification – July 2014 http://inchoo.net/ecommerce/state-of-magento-solution-specialist-certification-july-2014/ http://inchoo.net/ecommerce/state-of-magento-solution-specialist-certification-july-2014/#comments Thu, 31 Jul 2014 07:23:31 +0000 http://inchoo.net/?p=22827 We are now over two and a half months deep into Magento Solution Specialist certification that rolled out during this year’s Imagine eCommerce conference. So, it was about time to crunch some numbers – we bring you the first “State of Magento Solution Specialist Certification” report. Who leads the charts with Solution Specialist certification? Which continent, country, city...

The post State of Magento Solution Specialist Certification – July 2014 appeared first on Inchoo.

]]>
We are now over two and a half months deep into Magento Solution Specialist certification that rolled out during this year’s Imagine eCommerce conference. So, it was about time to crunch some numbers – we bring you the first “State of Magento Solution Specialist Certification” report.

Who leads the charts with Solution Specialist certification? Which continent, country, city and solution partner have the most people certified?

These and some other interesting stats can be drawn from the current list of certified solution specialists available from the official Magento certification directory.

Let’s start with some basics – there are 74 Magento Certified Solution Specialists worldwide at the time of writing this article (end of July 2014).

Global leaders – continents, countries, cities, partners

I’m happy to report that Europe actually leads the way in front of North America with 38:32 and only a handful of Solution Specialists coming from other continents (Central and South America with 3 and Asia with 1).

mcss-continent-2014-07

However, USA does lead the charts in the total number of MCSS per country with the total of 31, followed by Germany with 11, Netherlands and UK with 7 apiece and yours truly – Croatia with 5.

mcss-country-2014-07

 

Our hometown of Osijek can take the global bragging rights on city level for the time being with 5 MCSS badges shining right here in Croatia with some “smaller places” like Groningen (Netherlands), Springfield (MO, USA) and New York following suit with 4 each.

And if you were wondering how come some of these cities take the reins, here’s a quick explanation – our Classy Llama friends come from Springfield, Missouri and Inchoo, as you probably already know, is based in Osijek, Croatia. Our two companies are currently tied for the lead with 4 Solution Specialists each.

Finally certified

Interestingly enough, this is the only certificate for the majority of those who attained it, 45 people didn’t have any other Magento certificate (myself included) prior to this one – and it makes perfect sense, as Magento clearly announced this to be the first non-technical certification.That meant that many of us who were around the platform and know it reasonably well simply didn’t have the opportunity to verify this – now we were quick to jump on the bandwagon.

Who’s your MVD (Most Versatile Developer)?

There are some all-rounders in the mix, what you might call your Most Versatile Developers – I found 5 (five) people on the listing that have passed all currently available Magento certification exams – Frontend, Backend, Backend Plus and Solution Specialist achieving a Magento Certification Grand Slam – talk about wearing multiple hats, right?

And I’d say these folks deserve to be mentioned here (I’d leave it to you to find their contact details, though):

  • Adriano Aguiar
  • Jitze Bakker
  • Kris Brown
  • Phillip Jackson
  • Vladimir Kerkhoff

Eric(k)s and the ladies

And if we dig a little deeper, there are two people named Eric and one Erik on the list making this name the most common one in the Solution Specialists pool – not sure what you want to do with this particular piece of information, but here it is.

Women also play a part, albeit theirs is more of a supporting role as there’s the total of 7 Solution Specialist ladies out there (slightly below 10%) – and all of them come from USA – so what’s up with that, Europe?!

mcss-ladies-gentlemen-2014-07

 

And there you go – State of Magento Solution Specialist certification at the end of July 2014 – I plan to prepare updates and show some more interesting data in the months to come, so stay tuned.

Disclaimer:
This data is taken from the official public Magento certification directory and the “heavy crunching” required for this article was completed on July 29, 2014 – if you got certified and published in the meantime, you may want to wait for this post to be updated :)

There is also a possibility that not all people who got certified made their profiles public, so the actual number of certified developers may be a bit higher.

The post State of Magento Solution Specialist Certification – July 2014 appeared first on Inchoo.

]]>
http://inchoo.net/ecommerce/state-of-magento-solution-specialist-certification-july-2014/feed/ 5