Magento – Inchoo http://inchoo.net Magento Design and Magento Development Professionals - Inchoo Fri, 16 Feb 2018 12:42:59 +0000 en-US hourly 1 https://wordpress.org/?v=4.9.1 How we developed a new Morris 4×4 Center store – case study http://inchoo.net/ecommerce/morris-4x4-center-case-study/ http://inchoo.net/ecommerce/morris-4x4-center-case-study/#respond Mon, 29 Jan 2018 08:14:10 +0000 http://inchoo.net/?p=31503 In 2017, we had a challenging task in front of us. It all started with an inquiry from a potential new client, Morris 4×4 Center, that was looking for a partner to help them address and solve website stability issues. From that, it developed into a whole new project with the deadline of delivering a...

The post How we developed a new Morris 4×4 Center store – case study appeared first on Inchoo.

]]>
In 2017, we had a challenging task in front of us. It all started with an inquiry from a potential new client, Morris 4×4 Center, that was looking for a partner to help them address and solve website stability issues.

From that, it developed into a whole new project with the deadline of delivering a new site in under 2 months! Backend and frontend developers, design team, eCommerce consultants – we were all in it!

Let me tell you more about this project.

About Morris 4×4 Center

Morris 4×4 Center is a leading eCommerce destination for Jeep and 4×4 enthusiasts looking to outfit and enhance their vehicles. It provides more than 40,000 products across top brands, with passionate experts and a commitment to great customer experience.

Having fulfilled over a million orders in just over 25 years, Morris 4×4 Center’s passionate experts, superior customer service team, and new customer-centric initiatives are poised to better serve and help Jeep and off-road outdoor enthusiast fulfil their dream of a great driving experience.

New design Morris 4x4 CenterNew design
Old design Morris 4x4 CenterOld design

What was the challenge in all of this?

Morris 4×4 Center is well known among their targeted audience, and the business itself has outgrown the platform capabilities and could not rapidly implement changes to scale the business further.

Much of the problems stemmed from an outdated eCommerce platform. We proved our expertise by solving technical issues, which then expanded our cooperation through conducting a complete technical audit for their digital store. Detailed report on the findings and actionable set of recommendations we provided set a clear path towards speeding up and improving the store’s stability.

The technical audit was the pebble that started the avalanche. After our initial engagement, Inchoo was invited to bid on an eCommerce replatform project and we were chosen as the development partner for this larger project.

While it was well below zero in Inchoo’s hometown of Osijek, our team flew to Morris 4×4 Center’s headquarters, just outside Miami, Florida, and sealed the deal that we’ll develop a new store in less than 2 months. And we did just that!

How did the development process look like?

Planning, as the initial phase, was crucial in meeting the project deadline. The digital store was originally a hybrid between a static site and Magento, that made things even more challenging. We migrated it to Magento Enterprise 1.14.2. that brought stability, architectural control, and almost painless connection to external systems (such as ERP, Middleware systems and PIM).

Wireframing got us to the point where we identified the key elements to enhance visually and keep an eye on when developing them from the technical side. Clean design was crucial to achieve in displaying complex information architecture in the menu, and we developed it in collaboration with Prototyp. Special attention was devoted to imagery and media center for how-to videos, which helps in keeping Morris 4×4 Center as a recognizable brand in their industry.
 
Part Finder module brought an even better user flow for visitors to reach specific car model and its parts. Based on brand/model/year type of selection, it leads to results page that then offers numerous specific attributes by which to filter further.

 
Diagrams are an icing on the cake for finding the exact name and product for replacement car parts the user is looking for. Functioning as a click on picture where customer clicks on number and gets the exact name of associated products on shop that match the results. Useful feature for this kind of business, where even the mechanics sometimes don’t know the exact name of the particular Jeep part.

What are the results?

Morris 4×4 center is now not only a product destination, but also a content destination for off-road enthusiasts. Media focused responsive design with great information architecture is now more intuitive to use.

Loyalty program and promotion options give a great value not only to customers but also to store owners. With implemented tracking options, it is now easier to analyze site’s usage, test numerous promotional options and to make the most of gained information for remarketing purposes.

If you’re curious to learn what we did for some of our other clients, visit our portfolio page!

The post How we developed a new Morris 4×4 Center store – case study appeared first on Inchoo.

]]>
http://inchoo.net/ecommerce/morris-4x4-center-case-study/feed/ 0
How to delete spam customer accounts from Magento http://inchoo.net/magento/delete-spam-customer-accounts-magento/ http://inchoo.net/magento/delete-spam-customer-accounts-magento/#comments Thu, 21 Dec 2017 11:02:29 +0000 http://inchoo.net/?p=31236 We all love spam bots, don’t we? They really help us improve our sites. There was a situation with one of our clients being hit by a spam bot that generated dozens of customer accounts daily. Let us show you how to get rid of them and protect the site against future attacks. How the...

The post How to delete spam customer accounts from Magento appeared first on Inchoo.

]]>
We all love spam bots, don’t we? They really help us improve our sites. There was a situation with one of our clients being hit by a spam bot that generated dozens of customer accounts daily. Let us show you how to get rid of them and protect the site against future attacks.

How the problem started

One would ask:  “Why there wasn’t any validation on register form?”. Well, things were functioning smoothly for a couple of years, so there wasn’t need for one. It was happening for a few weeks until it was recognized. So, we found ourselves in situation where spam customer accounts had to be recognized, deleted and prevented from registering again.

Detailed examination

With detailed examination of customer grid, it was immediately clear this wasn’t going to be easy. There were many accounts with different names and email addresses. At the time, nearly 30000 accounts were registered. Going through the list and delete them manually was not an option. It would take too much time to open each account, examine it and decide whether it’s a spam account or a real customer. It had to be done with the script.

Recognizing possible patterns

There is no simple way of properly recognizing spam customer accounts. In order to delete them programmatically, you have to be sure you’re not going to delete a real customer. It would be very unpleasant situation for a customer to be deleted from the website. Not only the customer would be unhappy, but all connections to his/her orders would be lost.

 

So, establishing a way to recognize only spam customer accounts would be in a few steps.

1. Go through a reasonable number of spam accounts and write down most repeating similarities between them.

In this case, those would be following:

– One or two capital letters at the end of a firstname or lastname, rest of the letters are small

– all email domains ending with *.ru, *.xyz, *.ua, *.top

– Numbers in firstname or lastname

– identical first and lastname

 

Account examples:

onlinecreditufedor@qip.ru, FedorKr FedorKroM

moiseevayeq1957@mail.ru, MichaelWoxeF MichaelWoxeFLP

abellayssard@homail.top, Assingnits AssingnitsDV

meme123@ccxpnthu2.pw, Ronaldtrek RonaldtrekWI

lesha.gorodnitsyn@mail.ru, VladimirCrOp VladimirCrOpAN

maksim.sakevich@yandex.ua, DouglasPhem DouglasPhemDU

ahtd95782@gmail.com, WalterDer WalterDerXV

mretsan@mail.ru, Simfum Алексей

kuch@vitalityspace.com, MartinRoot MartinRootWK

georgina14@dlfiles.ru, zirehohamew79 Taylor

ra.um@mail.ru, Somfum Димас

gfhherejft@mail.ru, FrankieDok Bartek

srhcgiarc@007game.ru, top2017bloomingme Beson

teod.or78@mail.ru, GlennZek Vlad Stahov

admin_3@iphone-ipad-mac.xyz, Xewrtyuoipye XewrtyuoipyeBP

abcd2775y38@nod03.ru, myregobahev87 Alejandro

akilaanka@qip.ru, aseoprodwig aseoprodwig

polysten@i.ua, CharlesSCARK CharlesSCARKJE

 

Magento code is as follows:

$customers = Mage::getModel('customer/customer')
    ->getCollection()
    ->addAttributeToSelect('*')
    ->addAttributeToFilter(
        array(
            array('attribute' => 'email', 'like' => '%.ru'),
            array('attribute' => 'lastname', 'regexp' => '[a-z][A-Z]{2}'),
            array('attribute' => 'firstname', 'regexp' => '[0-9]'),
            array('attribute' => 'lastname', 'regexp' => '[0-9]')
        )
);

 

2. Try to load addresses for each account

First step should do the trick. But, to be sure that no real data will be lost from Magento, this additional step will be applied. This particular spam bot was unable to register and login to the site. It only created number of accounts. So, all of those accounts didn’t have any address associated. If there is any account with address, it shall be skipped from deleting.

foreach ($customers as $customer) {
    $customerAddresses = $customer->getAddresses();
        if ($customerAddresses) {
            continue;
        }
}

 

3. Check if there are any orders for each account.

If there is an order associated to a customer account it shall also be skipped from deleting. This is most probably the real account.

foreach ($customers as $customer) {
    $customerOrders = Mage::getModel('sales/order')
        ->getCollection()
        ->addAttributeToFilter('customer_id', $customer->getId())
        ->load();
 
        if ($customerOrders->count()) {
            continue;
        }
}

In this particular case, filters had to be very carefully set because there are real customers on the site whose names are written in capital letters. They also don’t have any address registered, therefore not having any orders either.

Before deleting customer account, it is nice to have it written in a log file. Just in case.

After all checks have been made, spam customer accounts can be deleted simply by calling $customer->delete() function in a loop.

Prevention

Most of the spam bots will be filtered out by activating Magento’s built in captcha for register form. It can be easily activated in administration under Settings->Customer->Customer Configuration->CAPTCHA. There are several options, as well as forms to be activated on.

As a custom solution and probably the best protection available, a Google’s reCAPTCHA can be implemented on register form. No bots shall pass then.

Conclusion

There are number of different spam bots out there, so there is no simple and certain way of deleting accounts from the website once they are registered. They must be examined manually and pattern shall be defined accordingly. There is no need to cover all of them. It’s impossible. After majority has been deleted, rest of the spam accounts are not so difficult to delete manually.

The post How to delete spam customer accounts from Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/delete-spam-customer-accounts-magento/feed/ 3
Why you should go to MageTestFest if you are a Magento developer http://inchoo.net/life-at-inchoo/go-magetestfest-magento-developer/ http://inchoo.net/life-at-inchoo/go-magetestfest-magento-developer/#respond Fri, 03 Nov 2017 13:50:31 +0000 http://inchoo.net/?p=31014 When a conference is advertised as “Magento. Software Testing. Party.” and Yireo stands behinds its organization, there is really not much left to say in order to convince you to attend MageTestFest that’s happening November 15th – 18th 2017! There are no doubts – you must clear your schedule and head off to this international...

The post Why you should go to MageTestFest if you are a Magento developer appeared first on Inchoo.

]]>
When a conference is advertised as “Magento. Software Testing. Party.” and Yireo stands behinds its organization, there is really not much left to say in order to convince you to attend MageTestFest that’s happening November 15th – 18th 2017!

There are no doubts – you must clear your schedule and head off to this international developer oriented event that has one single focus: TESTING! Vital for clean coding, testing is a topic that can always break the unbearable silence among developers that are just meeting each other.

But let’s be serious for a moment here. Testing should be a fundamental part of your work if you are a developer. Guys behind MageTestFest saw the need for full-depth conference about testing, where everybody is talking about the same topic, which results in extreme focus, learning and fun. And also for future cleaner code, fewer screw-ups and happier customers.

Who are the speakers?

Only proven experts on the subject! Sebastian Bergmann, Mathias Verraes, Vinai Koop, Fabian Schmengler, James Cowie, Tom Erskine, Igor Minailo and Jisse Reitsma will blow you away with knowledge they are willing to share in order for you to become an even better developer!

 

Four exciting days are ahead of us all – 1 conference day, 2 workshops (PHPUNIT, DDD) and 1 contribution day/hackathon.

Meet the Inchooers, they’ll have the goodies!

Inchoo troops will also be there! Look for our green banner where Stjepan Udovicic and Luka Rajcevic will meet you with open hands (that will be filled with Inchoo swag)! T-shirts, notebooks, stickers, be sure to grab yours while you can! 😉


Go to MageTestFest, invest in a future with fewer screw-ups!

The post Why you should go to MageTestFest if you are a Magento developer appeared first on Inchoo.

]]>
http://inchoo.net/life-at-inchoo/go-magetestfest-magento-developer/feed/ 0
Keeping an eye on small things in eCommerce projects http://inchoo.net/ecommerce/keeping-eye-small-things-ecommerce-projects/ http://inchoo.net/ecommerce/keeping-eye-small-things-ecommerce-projects/#comments Tue, 31 Oct 2017 12:23:02 +0000 http://inchoo.net/?p=30978 When managing projects, one usually focuses on big things: biggest costs, biggest features, biggest risks, etc. The same is with building an eCommerce site – the biggest, most important things are, well, most important. But large topics are not the be-all and end-all of the project. In this post, we will illustrate how tiny issues...

The post Keeping an eye on small things in eCommerce projects appeared first on Inchoo.

]]>
When managing projects, one usually focuses on big things: biggest costs, biggest features, biggest risks, etc. The same is with building an eCommerce site – the biggest, most important things are, well, most important.

But large topics are not the be-all and end-all of the project.

In this post, we will illustrate how tiny issues can have an outsized influence on the project. Through analysis of several examples from our experience, we will try to understand how small perturbations shape the course of the project.

So let’s start with something totally relevant, let’s start with – Napoleon.

We all know the story. He conquered Europe, and then turned his sights on Russia. His conquest started with an army of 680 000 men, and in a short time marched into Moscow. Russians evacuate Moscow and burn down three quarters of it! Napoleon, short on supplies retreats from the empty city. During the retreat, he loses most of his army. Out of 600 000 men, only 30 000 survive, and only 1 000 are fit for service.

A great story of brilliant defensive strategy.

Or is it?

In 2001, workers digging trenches for telephone cables in Vilnius, Lithuania found over 2 000 skeletons, stacked three on top of each other, arranged in v-shape. Analysis revealed that those were soldiers from Napoleon’s time, and with their remains, the complete story of Napoleon’s defeat was revealed:

Napoleon starts his attack on Russia in June 1812 from Germany. Things turn downhill in Poland. The summer is unusually hot and 20 thousand horses die of thirst. This stretches and strains the supply lines and the resources are scarce. The hygiene is bad and lice are becoming a huge problem – they are visibly crawling on men and the whole army is infested. Lice carry typhus, and in a month 80 000 soldiers die or are incapacitated because of the disease.

In August Napoleon conquers Smolensk, but another 105 000 men are lost to typhus. In the next two weeks, typhus claims another 60 000 men. In September, a week after the battle of Borodino, Napoleon enters Moscow with 90 000 men. The city is deserted and burned. Napoleon requests enforcements, but out of 15 000 men sent to him, 10 000 die from typhus. The winter is coming, and supplies have run out. Napoleon decides to retreat.

In December they come to Vilnius, with only 20 000 men fit for service. Fearing the coup, Napoleon urgently leaves for Paris, and general Murat organizes the retreat.

And now it is clear: a military genius of Kutuzov, and the coldest Russian winter and all the Russian cannons heard pounding in overture to Tchaikovsky’s 1812 weren’t enough to defeat the great general. It took the smallest creature of them all, unassuming, ordinary lice to bring destruction to Grande Armée.

With that in mind, what are the small, seemingly insignificant things that have an outsized influence on our projects?

Let’s walk through few examples of the small challenges or hiccups that proved to be big in the end (whether we acted proactively to resolve them and prevent the excrement hitting the fan, or we learned the hard way from addressing them too late in the game).

We will start with technical topics, and move slowly into organizational and pure project management issues.

Comment? What comment?
– When comments in code are bad

You know how developers often comment out pieces of code while working, in order to speed up a process or go around specific bugs in Magento (what bugs in Magento, you ask?!?!)

Well, it happened probably too many times that a piece of code that was commented out during development made it through to production that way, causing havoc on live site. Why is this commented out? What does it do? What did it use to do?

Lesson learned: Establish proper git branching model – And stick to it for dear life!

Leave commented out code out of committed code. Instead, keep it in git-diff – this may be subject to interpretation and depends on the project at hand, but this is a good rule of thumb. For controlling different versions of our code, we should use – version control software.

Attack of the Robots
– Ignore crawlers at your own peril!

Magento site works the same with or without the robots.txt file. The file itself does not affect the functioning of the store. The interesting thing about it is that it is one of few items that must be different between staging and live site (on staging we want to exclude everything from crawling, and on live site we are targeting specific files and folders – though usually it is a large number of them).

What happens is that after deployment, because of gitignore developer inadvertently deletes robots.txt file without noticing. Actually, nobody notices – the site works ok and everybody is celebrating new feature release.

Nobody notices, until we got a call from the client that the site is down. Next call is with the hosting provider – they will yell how they are bombarded with requests and how Google crashes our site. All because of one simple small txt file that does not affect our code or the user.

Lesson learned: Establish post-deploy check procedure – And execute it every time!

There are a lot of small tasks that need to be done and are easily forgotten during deploy. Having a checklist that we can rely on releases our mental energy to focus on executing the task at hand.

I-Track, U-Track, DDoS-Track
– Newsletters gone wild!

Newsletters are a great way to inform customers about new promotions and discounts. One large client had a big subscriber database and was sending a newsletter to them without problem for years. The URL in the newsletter was a hefty complex query, but as Magento has full-page caching, it was not a problem for the server.

At one point marketing came to a very reasonable idea to track which subscribers open the newsletter, so they could analyze and segment the campaigns. The tracking was implemented by adding a unique ID parameter to each newsletter URL, so when the user opens the newsletter, the hit is registered.

What that meant is that each user was served a unique page, Magento’s full-page cache was no longer used, and the query was executed for each hit. With a huge amount of subscribers opening their newsletter at the same time and thus executing the enormous query, the site went down. The client DDoS-ed themselves.

Lesson learned: Establish a Change Request Procedure – Make it simple, clear and stick to it!

Change is a fact for any project, that is why we have to be prepared for change. By establishing a Change Request Procedure we will make sure that relevant persons vet the change. For example, should the request be checked by Development, SEO specialist, Marketing? In our example, the solution was to setup caching to ignore the parameter – something a developer would notice in a second.

Extending the non-extendable
– Assumptions are just that!

We have a relatively large client (around 80 000 products) that wanted to track stock for a subset of products, and if a customer tries to order out of stock product, offer them a subscription to notification when the product is in stock again.

The notification is easy to do if the stock management is global, if Magento manages the stock for all products. If the stock is tracked only on a subset of products, as was the case with that client, then Magento will trigger the subscription message for all non-managed products (since their stock is 0)

We planned to go with Magento 1, so the solution was simple – extend the core to additionally check does the product have managed stock and if not, suppress the message. 30 minutes work.

Then Magento 2 became a thing. And the plan was changed, we’ll go with Magento 2. Because why not. Now, Magento 2 does not allow extending core. In general, you don’t extend a component, you write your own. In this case, we were deep in the core, we had to rework the whole Product page, which landed us with 30h of work. Add few similar customizations, and you have a project deep in red.

Lesson learned: Check your assumptions – Then double check them!

Every project is built on a series of assumptions. If one of them changes or proves wrong (we’re using Magento 2 instead of Magento 1), then the project is in danger (in this case, our estimates are way off). But if we know the assumptions we’re building our project on (dare I say: if we have them written down), then we can always check them and react when they’re changing.
Most important lesson: build a relationship of trust with the client, so you can work together when problems like these arise.

An admin, an admin! My kingdom for an admin!
– Who handles system administration?

We had a client where we took care of everything on Magento side, and everyone thought (ok, we thought) that their hosting company takes care of permissions and overall server-side setup.

However, when we came close to deploying to the live site (there was a specific environment in place where we were allowed to push changes to live in a very specific way), we brought the site down only to realize that our latest deployments failed to go to production due to server-side setup mixup. We were not in a position to fix this ourselves, hosting company wasn’t aware we expected them to handle this, the client didn’t know whose job this would have to be in the first place. A small omission in communication caused a huge issue when the push came to shove.

Lesson learned: Know your scope – In more details than you think you need

Ask questions, a lot of them. Prepare a checklist for the Sales team and on-boarding process. PMs should make sure to have all of this information in place before the team starts working.

Roses are red, violets are blue
– Clash of personalities

When a new project kicks off, you have to establish a good rapport with a client. Sometimes, when the deadlines are tight and you are already stretched, you don’t have enough time to think about personality match with the client. Things can go south quickly if there is a personality or communication style mismatch that you put under the rug hoping it will sort itself out.

We’ve had a scenario in which our lead developer, who acted as the main point of contact, and the client had almost an outing over a specific task where they poorly communicated the feature request. The confusion, combined with poor judgment on how one can/should communicate directly with a client created enough bad blood to force us to remove the lead developer from all communication with the client and get another team member assume this role.

Fortunately, the timing was not off, and we made it to a point where we managed to salvage the relationship and the project.

Lesson learned: Know your client – Adapt your communication to the client

Keep a close eye on the communication, invest time to learn who you are talking to on the other side, who is the client’s representative, what communication style they prefer, how technical are they… so that you can decide what communication tone and frequency would be the best from your end. In PM-speak, do a stakeholder analysis and use it as input for creating communication matrix for the project.

Six is not enough?

If the six examples did not convince you, we could spend hours talking about:

  • Client thinking something is irrelevant or easy and forgetting to tell us until it’s too late. E.g. automatic order processing is “just one button”, or the client doesn’t mention they have multiple stores,
  • The time we did not contract 3rd party support (for integrated systems),
  • That project where we did not include all stakeholders from the client side (e.g. not all departments) which led to last minute changes and budget overruns,
  • When we did not explain our process to the client, so they were late with their deliveries (e.g. logos, transactional emails),
  • The client that was too detailed and wanted to have spit-polished plans – the whole budget was spent only on planning,
  • Or many projects where we did not check if extensions really work with the latest Magento version (e.g. M1 -> M2 upgrade)
  • Or …

Instead of conclusion

“By failing to prepare, you are preparing to fail.”
– Benjamin Franklin

The six examples we covered are ranging from technical to social, but they have one thing in common. The solution was not technical, it was better processes, communication, organization – in short, better Project Management. They are the best illustration that the biggest risk and the biggest opportunity in projects come from good or bad project management. Please share your experiences in comments – what were the small things that made or broke your projects, and how did you deal with them?

Oh, I almost forgot…

Those skeletons in Vilnius? That wasn’t a mass grave, they were not burying the bodies. The ground was too frozen to dig, so they could not dig trenches. They used frozen corpses of their friends to build a breastwork, a shield, barricades to protect them from advancing Russian army.

Don’t let that happen to you because you ignored small things.

The post Keeping an eye on small things in eCommerce projects appeared first on Inchoo.

]]>
http://inchoo.net/ecommerce/keeping-eye-small-things-ecommerce-projects/feed/ 1
Add custom image field for custom options http://inchoo.net/magento/add-custom-image-field-custom-options/ http://inchoo.net/magento/add-custom-image-field-custom-options/#comments Tue, 24 Oct 2017 10:39:05 +0000 http://inchoo.net/?p=30858 We had a request from a client who wanted to display images for custom options. In this article, I’ll explain how to add the image field to the custom option in admin. Create new module Inchoo_ProductCustomOptionsFile app/etc/modules/Inchoo_ProductCustomOptionsFile.xml <?xml version="1.0"?> <config> <modules> <Inchoo_ProductCustomOptionsFile> <active>true</active> <codePool>local</codePool> </Inchoo_ProductCustomOptionsFile> </modules> </config> Configuration file To add your custom field it...

The post Add custom image field for custom options appeared first on Inchoo.

]]>
We had a request from a client who wanted to display images for custom options. In this article, I’ll explain how to add the image field to the custom option in admin.

Create new module Inchoo_ProductCustomOptionsFile

app/etc/modules/Inchoo_ProductCustomOptionsFile.xml

  1. <?xml version="1.0"?>
    <config>
        <modules>
            <Inchoo_ProductCustomOptionsFile>
                <active>true</active>
                <codePool>local</codePool>
            </Inchoo_ProductCustomOptionsFile>
        </modules>
    </config>

Configuration file

To add your custom field it is necessary to rewrite class         Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Type_Select and set your template. To fill custom option image filed with new image’s names it is necessary to rewrite the class Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Option.

/app/code/local/Inchoo/ProductCustomOptionsFile/etc/config.xml

<?xml version="1.0"?>
<config>
	<modules>
		<Inchoo_ProductCustomOptionsFile>
			<version>1.0.0</version>
		</Inchoo_ProductCustomOptionsFile>
	</modules>
	<global>
		<blocks>
			<adminhtml>
				<rewrite>
					<catalog_product_edit_tab_options_type_select>Inchoo_ProductCustomOptionsFile_Block_Adminhtml_Rewrite_Catalog_Product_Edit_Type_Select</catalog_product_edit_tab_options_type_select>
					<catalog_product_edit_tab_options_option>Inchoo_ProductCustomOptionsFile_Block_Adminhtml_Rewrite_Catalog_Product_Edit_Tab_Options_Option</catalog_product_edit_tab_options_option>
				</rewrite>
			</adminhtml>
			<inchoo_file>
				<class>Inchoo_ProductCustomOptionsFile_Block</class>
			</inchoo_file>
		</blocks>
		<resources>
			<inchoo_productcustomoptionsfile_setup>
				<setup>
					<module>Inchoo_ProductCustomOptionsFile</module>
				</setup>
			</inchoo_productcustomoptionsfile_setup>
		</resources>
	</global>
	<admin>
		<routers>
			<adminhtml>
				<args>
					<modules>
						<inchoo_product before="Mage_Adminhtml">Inchoo_ProductCustomOptionsFile_Adminhtml</inchoo_product>
					</modules>
				</args>
			</adminhtml>
		</routers>
	</admin>
</config>

 Blocks

/app/code/local/Inchoo/ProductCustomOptionsFile/Block/Adminhtml/Rewrite/Catalog/Product/Edit/Type/Select.php

We want to use our template instead default one for custom option.

<?php
class Inchoo_ProductCustomOptionsFile_Block_Adminhtml_Rewrite_Catalog_Product_Edit_Type_Select extends Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Type_Select
{
	public function __construct()
	{
		parent::__construct();
		$this->setTemplate('catalog/product/edit/options/type/select-with-file.phtml');
		$this->setCanEditPrice(true);
		$this->setCanReadPrice(true);
	}
 
}

Create a template file and copy content of /app/design/adminhtml/default/default/template/catalog/product/edit/options/type/select.phtml into our new template file /app/design/adminhtml/default/default/template/catalog/product/edit/options/type/select-with-file.phtml

Add code between Inchoo into select-with-file.phtml template

<!---...-->
 
OptionTemplateSelect =
<!---...-->
        '<th class="type-sku"><?php echo Mage::helper('core')->jsQuoteEscape(Mage::helper('catalog')->__('SKU')) ?></th>'+
            // Inchoo
        '<th class="type-title"><?php echo Mage::helper('core')->jsQuoteEscape(Mage::helper('catalog')->__('Image Name')) ?></th>'+
        '<th class="type-title"><?php echo Mage::helper('core')->jsQuoteEscape(Mage::helper('catalog')->__('Upload New Image')) ?></th>'+
            // Inchoo
        '<th class="type-title"><?php echo Mage::helper('core')->jsQuoteEscape(Mage::helper('catalog')->__('Sort Order')) ?></th>'+
<!---...-->
 
OptionTemplateSelectRow =
<!---...-->
        '<td><input type="text" class="input-text" name="product[options][{{id}}][values][{{select_id}}][sku]" value="{{sku}}"></td>'+
            // Inchoo
        '<td><input type="text" class="select-type-image" id="product_option_{{id}}_select_{{select_id}}_image" name="product[options][{{id}}][values][{{select_id}}][image]" value="{{image}}">{{checkboxScopeTitle}}</td>'+
        '<td><input type="file" class="input-text select-type-image" id="image" name="{{id}}-{{select_id}}"></td>'+
            // Inchoo
        '<td><input type="text" class="validate-zero-or-greater input-text" name="product[options][{{id}}][values][{{select_id}}][sort_order]" value="{{sort_order}}"></td>'+
<!---...-->

To pass our new custom option value to template we need to rewrite Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Option class. Create block /app/code/local/Inchoo/ProductCustomOptionsFile/Block/Adminhtml/Rewrite/Catalog/Product/Edit/Tab/Options/Option.php Add code between Inchoo comment. Function getOptionValues() returns all product custom options to javascript object which fills custom options fields.

class Inchoo_ProductCustomOptionsFile_Block_Adminhtml_Rewrite_Catalog_Product_Edit_Tab_Options_Option extends Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Option
{
	public function getOptionValues()
	{
 
// ...
	$i = 0;
	$itemCount = 0;
	foreach ($option->getValues() as $_value) {
	/* @var $_value Mage_Catalog_Model_Product_Option_Value */
		$value['optionValues'][$i] = array(
		'item_count' => max($itemCount, $_value->getOptionTypeId()),
		'option_id' => $_value->getOptionId(),
		'option_type_id' => $_value->getOptionTypeId(),
		'title' => $this->escapeHtml($_value->getTitle()),
		'price' => ($showPrice)
		? $this->getPriceValue($_value->getPrice(), $_value->getPriceType()) : '',
		'price_type' => ($showPrice) ? $_value->getPriceType() : 0,
		'sku' => $this->escapeHtml($_value->getSku()),
		'sort_order' => $_value->getSortOrder(),
		// Inchoo
		'image' => $_value->getImage(),
		// Inchoo
		);
// ...

Now we have our custom fields Image Name and Upload New Images :

Saving images

To save images into database, we need to create setup script and rewrite Mage_Adminhtml_Catalog_ProductController. Then create setup script /app/code/local/Inchoo/ProductCustomOptionsFile/sql/inchoo_productcustomoptionsfile_setup/install-1.0.0.php

<?php
/* @var $installer Mage_Core_Model_Resource_Setup */
 
$installer = $this;
 
$installer->getConnection()
->addColumn($installer->getTable('catalog/product_option_type_value'), 'image', 'VARCHAR(255) NULL');
$installer->endSetup();

Rewrite class Mage_Adminhtml_Catalog_ProductController and add code between Inchoo to save images name into table catalog_product_option_type_value and upload images.

 

<?php
require_once(Mage::getModuleDir('controllers','Mage_Adminhtml').DS.'Catalog'.DS.'ProductController.php');
 
class Inchoo_ProductCustomOptionsFile_Adminhtml_Catalog_ProductController extends Mage_Adminhtml_Catalog_ProductController
{
	/**
	 * Initialize product before saving
	 */
	protected function _initProductSave()
	{
 
// ..
 
		$product->setCanSaveConfigurableAttributes(
			(bool) $this->getRequest()->getPost('affect_configurable_product_attributes')
			&& !$product->getConfigurableReadonly()
		);
// Inchoo
		$skuImageName=trim($product->getSku());
		$imagesFiles=$_FILES;
		$path = Mage::getBaseDir('media') . DS . 'catalog' . DS . 'customoption' .DS. 'images';
 
		foreach ($imagesFiles as $key=>$value)
		{
			$optionsValue = explode('-',$key);
			foreach ($value as $key2=>$value2)
			{
				if($key2=='name' && $value2!="") {
					try {
						$uploader = new Varien_File_Uploader($key);
						$uploader->setAllowedExtensions(array('jpg','jpeg','gif','png','svg'));
						$uploader->setAllowRenameFiles(false);
						$uploader->setFilesDispersion(false);
						$optionTitle = trim($productData['options'][$optionsValue[0]]['title']);
						$optionValueTitle = trim($productData['options'][$optionsValue[0]]['values'][$optionsValue[1]]['title']);
						$imageExtension = pathinfo($value2,PATHINFO_EXTENSION);
						$newImageName =$skuImageName.'_'.$optionTitle.'_'.$optionValueTitle.'.'.$imageExtension;
						$uploader->save($path, $newImageName);
						$productData['options'][$optionsValue[0]]['values'][$optionsValue[1]]['image']=$uploader->getUploadedFileName();
					} catch(Exception $e) {
						Mage::log('Unable to save custom option image. ' . $e->getMessage(), null, null, true);
					}
 
				}
			}
		}
// Inchoo

And that’s it. If you have any questions, feel free to post them in comments.

The post Add custom image field for custom options appeared first on Inchoo.

]]>
http://inchoo.net/magento/add-custom-image-field-custom-options/feed/ 3
Solr and Magento – search by department http://inchoo.net/magento/solr-search-by-department/ http://inchoo.net/magento/solr-search-by-department/#comments Wed, 18 Oct 2017 09:29:47 +0000 http://inchoo.net/?p=30692 There are eCommerce stores which sell a wide variety of products like food, personal care, electronics, and so on. On those stores, visitors want to be able to search by a specific category. This can be achieved by adding a new feature: search by department or category. In this quick tutorial I will explain the...

The post Solr and Magento – search by department appeared first on Inchoo.

]]>
There are eCommerce stores which sell a wide variety of products like food, personal care, electronics, and so on. On those stores, visitors want to be able to search by a specific category. This can be achieved by adding a new feature: search by department or category. In this quick tutorial I will explain the base concept of how to do it using Solr search engine as an example.

Assuming you already use Solr search server on your eCommerce site, first step should be checking if there is a category_ids field in Solr index. Category_ids should exist in Solr index in order to be able to filter by category_id.

To check if the field exists, call this url: http://localhost:8983/solr/collection1/select?q=*%3A*&wt=json&indent=true.

You can see category_ids in the response below:

Field category_ids should be declared in Solr schema.xml as in the following example:

        <field name="category_ids"  type="int"  indexed="true" multiValued="true"/>

Category_ids field is multivalued and indexed, can contain more values, and product can be assigned to more categories.

If the previous conditions are fulfilled, only the Magento side should be modified to send a proper category filter with search phrase. If you use Solarium client, it is pretty easy.

$client = new Solarium\Client($config);
 
// get a select query instance
$query = $client->createSelect();
$query->setQuery('Teflon');
$query->setFields(array('id','name','price'));
 
// create a filterquery by category id 3887
$fq = $query->createFilterQuery('category_ids')->setQuery('category_ids:3887');

If you are interested in finding out more about the Solarium concepts, click here.

Finally, query to Solr should look like this:
http://localhost:8983/solr/collection1/magento_en?q=Teflon&fq=category_ids:3887

q parameter is search terms “Teflon”
fq parameter is filter by category id 3887 (more about Solr common parameters)

The results

This is how a “search by category” on Magento frontend can look like:

With this feature you can improve site search performance and decrease the need for search refinement which, ultimately, has direct impact on your eCommerce conversion rate.

The post Solr and Magento – search by department appeared first on Inchoo.

]]>
http://inchoo.net/magento/solr-search-by-department/feed/ 1
How to keep design library in sync across the team? Welcome Sketch Libraries! http://inchoo.net/magento/design/how-to-keep-design-library-in-sync-across-the-team-welcome-sketch-libraries/ http://inchoo.net/magento/design/how-to-keep-design-library-in-sync-across-the-team-welcome-sketch-libraries/#comments Tue, 17 Oct 2017 10:43:10 +0000 http://inchoo.net/?p=30680 The buzz these days is all about design systems, but design system by itself is not enough to ensure consistency through all designs. When working with design systems, the main challenges are ongoing maintenance and informing everyone about the changes. For a long time, there wasn’t a thorough solution for designers who design in Sketch...

The post How to keep design library in sync across the team? Welcome Sketch Libraries! appeared first on Inchoo.

]]>
The buzz these days is all about design systems, but design system by itself is not enough to ensure consistency through all designs. When working with design systems, the main challenges are ongoing maintenance and informing everyone about the changes.

For a long time, there wasn’t a thorough solution for designers who design in Sketch which would provide easy access to the latest styles and propagate changed assets to team members. Yeah, we had the ability to share symbols via plugins for a while (Craft’s Library), but there were too many problems, and sharing library is too important to rely on a third-party plugin.

Welcome Sketch Libraries

Sketch just made public the Sketch 47, and we finally have a document with symbols which can be used across other documents, so let’s see how to use libraries in Sketch.

1. Create a Sketch document with at least one symbol and save your document in Dropbox, Box, Sync or any other place where your colleagues have access.

2. Press CMD + comma to open Sketch’s Preferences and navigate to the Libraries tab.

3. You’ll notice there is iOS UI Design library included, but we’ll create a new library. Click on the “Add Library…” button and choose your document. Congrats, you’ve just created a single source of truth for everyone in your team.

Team members can now easily add the Library by following the same steps mentioned above and access the symbols in that file from any Sketch file.

Inserting, editing and accepting changes

Using shared library is simple and straightforward. Inserting symbols works just like inserting regular symbols, the only difference is they are not placed in your document. To insert a symbol just find your shared library at the bottom of the list on the Insert menu.

You’ll notice external symbols have slightly different icons from the local symbols to avoid confusion.

Once inserted, there are two options for editing an external symbol. You can unlink it from Library or open it in the Original document.


If you choose “Unlink from Library”, it will detach from the external library and become a local symbol in your current Sketch file.

Making changes in the original document will affect all instances of the symbol across any document which is using this library, but only if those changes are accepted. After making changes, everyone who is using this library will see “Library Update Available” badge on the top-right corner of Sketch.

Maybe Sketch crew should make that badge more prominent because it’s easy to miss. Anyhow, clicking on it will display a dialog box with outdated symbols and an option to selectively update them.

To sum up…

This feature is definitely a game changer for all Sketch users and it will change real-time collaboration permanently. What we would like to see in some of the future updates is an option to include text styles and layer styles in a library.

The post How to keep design library in sync across the team? Welcome Sketch Libraries! appeared first on Inchoo.

]]>
http://inchoo.net/magento/design/how-to-keep-design-library-in-sync-across-the-team-welcome-sketch-libraries/feed/ 1
Case Study: Migration from Magento 1 to Magento 2 for Sloan Express http://inchoo.net/magento-2/case-study-migration-magento-1-magento-2-sloan-express/ http://inchoo.net/magento-2/case-study-migration-magento-1-magento-2-sloan-express/#respond Mon, 02 Oct 2017 11:54:40 +0000 http://inchoo.net/?p=30654 Sloan Express is a family-owned business with deep roots in the agricultural industry that have been serving the needs of farmers worldwide for over 80 years. Located in Central Illinois, Sloan Express is the area leader offering new agricultural parts that are equal to or better than the original equipment part. They sell directly to...

The post Case Study: Migration from Magento 1 to Magento 2 for Sloan Express appeared first on Inchoo.

]]>

Sloan Express is a family-owned business with deep roots in the agricultural industry that have been serving the needs of farmers worldwide for over 80 years. Located in Central Illinois, Sloan Express is the area leader offering new agricultural parts that are equal to or better than the original equipment part. They sell directly to farmers, implement dealers and repair shops.

Sloan Express has been able to address some of the problems that today’s farmers face: parts not stocked locally; shortage of local sources for parts; and most important – TIME. The Sloan Express Customer prompt and direct delivery with no time wasted trying to find a part and then waiting for it to come in.

What were the challenges for us?

Sloanex_new2017
Sloanex_old2016

Recognizing the need to take the business to the next level, Jeff Sloan and the team from Sloan Express approached us looking for Magento professionals that can migrate their existing store from 1.7. Open Source edition to Magento 2.

Magento 2 introduces new methodologies and technologies for delivering enhanced shopping and store experience to the merchants and users. But to be honest, migrating from Magento 1 to Magento 2 is not an easy and trouble-free process. Since it’s not automated, there is plenty of manual work that needs to be done by professionals who understand migration process and your business in order to get a stable and fully functional store.

Sloan Express knew what they wanted for their future. They required a solution that can easily scale up if required and has a modular architecture to ensure faster page load time, faster add-to-cart server response time and faster end-to-end checkout time.

Inchoo at Sloan Express

Inchoo at Sloan Express

Magento 2 Open source (previously known as Community Edition) comes with support for only MySQL search engine, but some projects require better or more adjustable search engine in order to increase sales or conversion rate. For Sloan Express, we’ve implemented SOLR search engine in order to achieve blazing-fast search results that are also highly reliable and fault tolerant. With a near real-time indexing, advanced full-text search capabilities and optimisation for high volume traffic, SOLR has brought a new dimension for the customers using the site.

The client wanted to keep all of their existing features and extensions from Magento 1. As one can imagine, Magento 1 extensions are not compatible with Magento 2, so we implemented new ones and set fundamentals for all future technical implementations and integrations as PIM, ERP and other complex technical systems.

Since Sloan Express had a large number of categories, we had to do a major restructuring of the store’s hierarchy, which, among other, resulted with structured navigation and flow that seems more natural to the end user.

The end result

Sloan Express now has modern and clean responsive design with a completely new look and flow that provides optimal viewing and interaction experience across a wide range of devices. We designed it by having in mind business needs for this special niche and eCommerce trends supported with the gathered information and behavior of visitors on the previous store.

Results Sloanex Inchoo

Implementing several analytic tools, we now have a better understanding of the customer’s journey and how they engage with the brand. That gives us the opportunity to continuously test and improve technical functionalities and user experience in order to increase revenue and reflect the quality that stands behind the name of Sloan Express in agriculture world.

The post Case Study: Migration from Magento 1 to Magento 2 for Sloan Express appeared first on Inchoo.

]]>
http://inchoo.net/magento-2/case-study-migration-magento-1-magento-2-sloan-express/feed/ 0
Programmatically create upsell, cross sell and related products in Magento http://inchoo.net/magento/programmatically-create-upsell-crosssell-related-products-magento/ http://inchoo.net/magento/programmatically-create-upsell-crosssell-related-products-magento/#comments Thu, 21 Sep 2017 08:52:12 +0000 http://inchoo.net/?p=30529 This article will explain how to add upsell, cross sell and related products programmatically to Magento. One of practical examples would be data migration from some other ecommerce system to Magento. You can read a nice article on how to add upsell, cross sell and related products from administration here. It explains what all these...

The post Programmatically create upsell, cross sell and related products in Magento appeared first on Inchoo.

]]>
This article will explain how to add upsell, cross sell and related products programmatically to Magento. One of practical examples would be data migration from some other ecommerce system to Magento. You can read a nice article on how to add upsell, cross sell and related products from administration here. It explains what all these product relations mean and where are they used on the site.

Load existing product data

At the beginning, there is a product that need to be updated with product relations. It needs to be loaded as usual.

$product = Mage::getModel('catalog/product')->load($productId);

This loaded product model will not contain information about already existing upsell, cross sell and related products. If loaded product doesn’t have previous upsell, cross sell or related products set, it can be saved immediately with new data. But, if there is already existing data about these products, it must be loaded first, merged with new data and then saved. There are specific functions for that. Otherwise, it would be overwritten with new data only.

$upSellProducts = $product->getUpSellProducts();
$crossSellProducts = $product->getCrossSellProducts();
$relatedProducts = $product->getRelatedProducts();

These functions load all upsell, cross sell and related product models as an array with numeric keys starting from zero.

 

Prepare existing product data

In order to update product’s upsell, cross sell and related information, they need to be rearranged in array with product ids as keys. This array should also contain information about product position as a subarray. Position parameter determines product’s order position on frontend, usually in sidebar or slider. This parameter can also be set through Magento administration by opening product’s upsell, cross sell or related tab.

foreach ($upSellProducts as $upSellProduct) {
    $upSellProductsArranged[$upSellProduct->getId()] = array('position' => $$upSellProduct->getPosition());
}
 
foreach ($crossSellProducts as $crossSellProduct) {
    $crossSellProductsArranged[$crossSellProduct->getId()] = array('position' => $crossSellProduct->getPosition());
}
 
foreach ($relatedProducts as $relatedProduct) {
    $relatedProductsArranged[$relatedProduct->getId()] = array('position' => $relatedProduct->getPosition());
}

Merge new product data

When migrating products to Magento, products will be created, not updated, so if there is multiple upsell, cross sell or related products, this parameter can be incremented in a loop starting from zero or they can all be set to zero.Merging new upsell, cross sell and related products:

$newUpSellProducts = array($newUpSellProduct1, $newUpSellProduct2);
foreach ($newUpSellProducts as $newUpSellProduct) {
    $upSellProductsArranged[$newUpSellProduct->getId()] = array('position' => '');
}
 
$newCrossSellProducts = array($newCrossSellProduct1, $newCrossSellProduct2);
foreach ($newCrossSellProducts as $newCrossSellProduct) {
    $crossSellProductsArranged[$newCrossSellProduct->getId()] = array('position' => '');
}
 
$newRelatedProducts = array($newRelatedProduct1, $newRelatedProduct2);
foreach ($newRelatedProducts as $newRelatedProduct) {
    $relatedProductsArranged[$newRelatedProduct->getId()] = array('position' => '');
}

When all relations are merged, they should be set as one of product’s _data parameter:

$product->setUpSellLinkData($upSellProductsArranged);
$product->setCrossSellLinkData($crossSellProductsArranged);
$product->setRelatedLinkData($relatedProductsArranged);

Finally the product can be saved:

$product->save();

Database structure

This may seem like a simple thing. All that is needed is to set upsell, cross sell and related product ids in array with their positions and save the product. Backend process is actually complicated. Magento function that handles product relations saving process is saveProductRelations($product). It is located in Mage_Catalog_Model_Product_Link class.

Database structure for product relations is eav structure. Main table, in which most of this information is saved, is “catalog_product_link”. It’s structure is very simple. It consists of 4 columns. “link_id” is increment ID, “product_id” is edited product, “linked_product_id” is ID of the product that is related to edited product, “link_type_id” is relation type ID. 4 is for upsell, 5 for cross sell and 1 for related product. Second table worth mentioning is “catalog_product_link_attribute_int” which saves product’s position parameter mentioned earlier.

 

select * from catalog_product_link;

+---------+------------+-------------------+--------------+
| link_id | product_id | linked_product_id | link_type_id |
+---------+------------+-------------------+--------------+
|     1   |     247    |               640 |            1 |
|     2   |     247    |               642 |            1 |
|     3   |     247    |               647 |            1 |
|     4   |     247    |               641 |            1 |
|     5   |     247    |               652 |            4 |
|     6   |     247    |               651 |            4 |
|     7   |     247    |               651 |            5 |
|     8   |     247    |               652 |            5 |
|     9   |     247    |               652 |            1 |
|    10   |     247    |               651 |            1 |
+---------+------------+-------------------+--------------+

The post Programmatically create upsell, cross sell and related products in Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/programmatically-create-upsell-crosssell-related-products-magento/feed/ 2
Adding gallery tab to a CMS page http://inchoo.net/magento/adding-gallery-tab-cms-page/ http://inchoo.net/magento/adding-gallery-tab-cms-page/#comments Tue, 29 Aug 2017 10:53:32 +0000 http://inchoo.net/?p=29908 In this article I will demonstrate how you can create new tab into admin cms page so you can create image gallery similar as upload images for product. Step 1 Register your module : <config> <modules> <Inchoo_Gallery> <active>1</active> <codePool>local</codePool> </Inchoo_Gallery> </modules> </config> Step 2 Module configuration : <config> <modules> <Inchoo_Gallery> <version>1.0.0</version> </Inchoo_Gallery> </modules> </config> Step...

The post Adding gallery tab to a CMS page appeared first on Inchoo.

]]>
In this article I will demonstrate how you can create new tab into admin cms page so you can create image gallery similar as upload images for product.

Step 1

Register your module :

<config>
	<modules>
		<Inchoo_Gallery>
			<active>1</active>
			<codePool>local</codePool>
		</Inchoo_Gallery>
	</modules>
</config>

Step 2

Module configuration :

<config>
	<modules>
		<Inchoo_Gallery>
			<version>1.0.0</version>
		</Inchoo_Gallery>
	</modules>
</config>

Step 3

We need to store images into database so we need to create a model, collection, resource and setup script.Our model will implement JsonSerializable so we need to implement jsonSerialize method which will return required json data about the model.

Config:

<!--....-->
<global>
 <models>
  <inchoo_gallery>
   <class>Inchoo_Gallery_Model</class>
    <resourceModel>inchoo_gallery_resource</resourceModel>
   </inchoo_gallery>
   <inchoo_gallery_resource>
    <class>Inchoo_Gallery_Model_Resource</class>
    <entities>
     <gallery>
      <table>inchoo_cms_gallery</table>
     </gallery>
    </entities>
    </inchoo_gallery_resource>
     </models>
     <resources>
      <inchoo_gallery_setup>
       <setup>
        <module>Inchoo_Gallery</module>
       </setup>
      </inchoo_gallery_setup>
     </resources>
</global>
<!--....-->

Model:

class Inchoo_Gallery_Model_Gallery extends
Mage_Core_Model_Abstract implements JsonSerializable {
 public function _construct() {
  $this->_init( 'inchoo_gallery/gallery' );
	}
 
 public function jsonSerialize() {
  return [
    'id'               => $this->getId(),
    'file'             => $this->getFile(),
    'label'            => $this->getLabel(),
    'position'         => $this->getPosition(),
    'disabled'         => $this->getIsDisabled(),
    'label_default'    => $this->getLabel(),
    'position_default' => $this->getPosition(),
    'disabled_default' => $this->getIsDisabled(),
    'url'              => Mage::getBaseUrl( Mage_Core_Model_Store::URL_TYPE_WEB ) . 'media/gallery/' . $this->getFile(),
    'removed'          => 0
		];
	}
}

Resource:

class Inchoo_Gallery_Model_Resource_Gallery
extends Mage_Core_Model_Resource_Db_Abstract
{
	public function _construct() {
		$this->_init('inchoo_gallery/gallery','id');
	}
}

Collection:

<?php
class Inchoo_Gallery_Model_Resource_Gallery_Collection
extends Mage_Core_Model_Resource_Db_Collection_Abstract
{
	public function _construct() {
		$this->_init('inchoo_gallery/gallery');
	}
}

Setup script:

<?php
$installer = $this;
$installer->startSetup();
 
$table = $installer->getConnection()
                   ->newTable( $installer->getTable( 'inchoo_gallery/gallery' ) )
                   ->addColumn( 'id', Varien_Db_Ddl_Table::TYPE_INTEGER, null,
	                   array(
		                   'identity' => true,
		                   'unsigned' => true,
		                   'nullable' => false,
		                   'primary'  => true,
	                   ), 'Value id' )
                   ->addColumn( 'cms_page_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null,
	                   array(
		                   'nullable' => false,
	                   ), 'Cms Page id' )
                   ->addColumn( 'position', Varien_Db_Ddl_Table::TYPE_INTEGER, null,
	                   array(
		                   'unsigned' => true,
		                   'nullable' => true,
	                   ), 'Position' )
                   ->addColumn( 'file', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255,
	                   array(
		                   'nullable' => false,
	                   ), 'File Name' )
                   ->addColumn( 'label', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255,
	                   array(
		                   'nullable' => true,
	                   ), 'Label' )
                   ->addColumn( 'is_disabled', Varien_Db_Ddl_Table::TYPE_BOOLEAN, null,
	                   array(
		                   'nullable' => true,
	                   ), 'Is Disabled' );
 
$installer->getConnection()->createTable( $table );
 
$installer->getConnection()->addForeignKey(
	$installer->getFkName(
		'inchoo_gallery/gallery',
		'cms_page_id',
		'cms/page',
		'page_id'
	),
	$installer->getTable( 'inchoo_gallery/gallery' ),
	'cms_page_id',
	$installer->getTable( 'cms/page' ),
	'page_id'
);
$installer->endSetup();

Step 4

Next we need to create our block and template file.

Config:

<!--....-->
 
<global>
<!--....-->
		<blocks>
			<inchoo_gallery>
				<class>Inchoo_Gallery_Block</class>
			</inchoo_gallery>
		</blocks>
</global>
<!--....-->

Create block:

<?php
class Inchoo_Gallery_Block_Adminhtml_Gallery
	extends Mage_Adminhtml_Block_Widget
	implements Mage_Adminhtml_Block_Widget_Tab_Interface {
	protected $_uploaderType = 'uploader/multiple';
 
	public function __construct() {
		parent::__construct();
		$this->setShowGlobalIcon( true );
		Mage_Adminhtml_Block_Template::__construct();
		$this->setTemplate( 'inchoo/gallery.phtml' );
	}
 
	protected function _prepareLayout() {
		$this->setChild( 'uploader',
			$this->getLayout()->createBlock( $this->_uploaderType )
		);
 
		$this->getUploader()->getUploaderConfig()
		     ->setFileParameterName( 'image' )
		     ->setTarget( Mage::getModel( 'adminhtml/url' )->addSessionParam()->getUrl( '*/cms_page/upload' ) );
 
		$browseConfig = $this->getUploader()->getButtonConfig();
		$browseConfig
			->setAttributes( array(
				'accept' => $browseConfig->getMimeTypesByExtensions( 'gif, png, jpeg, jpg' )
			) );
 
		return Mage_Adminhtml_Block_Template::_prepareLayout();
	}
 
	public function getUploader() {
		return $this->getChild( 'uploader' );
	}
 
	public function getUploaderHtml() {
		return $this->getChildHtml( 'uploader' );
	}
 
	public function getHtmlId() {
		return 'media_gallery_content';
	}
 
	public function getGallery() {
		return Mage::getModel( 'inchoo_gallery/gallery' )->getCollection()
		           ->addFieldToFilter( 'cms_page_id', array( 'eq' => $this->getRequest()->getParam( 'page_id' ) ) );
 
	}
 
	public function getImageTypes() {
		return array( 'image' => [ 'label' => 'Base Image', 'field' => 'post[image]' ] );
	}
 
	public function getImageTypesJson() {
		return Mage::helper( 'core' )->jsonEncode( $this->getImageTypes() );
	}
 
	public function getJsObjectName() {
		return 'media_gallery_contentJsObject';
	}
 
	public function getImagesJson() {
 
		$jsonFiles = '';
		$gallery   = $this->getGallery();
		foreach ( $gallery as $images ) {
			$jsonFiles = $jsonFiles . ',' . json_encode( $images );
		}
 
		return '[' . trim( $jsonFiles, ',' ) . ']';
	}
 
	/**
	 * Prepare label for tab
	 *
	 * @return string
	 */
	public function getTabLabel() {
		return Mage::helper( 'cms' )->__( 'Gallery' );
	}
 
	/**
	 * Prepare title for tab
	 *
	 * @return string
	 */
	public function getTabTitle() {
		return Mage::helper( 'cms' )->__( 'Gallery' );
	}
 
	/**
	 * Returns status flag about this tab can be showen or not
	 *
	 * @return true
	 */
	public function canShowTab() {
		return true;
	}
 
	/**
	 * Returns status flag about this tab hidden or not
	 *
	 * @return true
	 */
	public function isHidden() {
		return false;
	}
 
	/**
	 * Check permission for passed action
	 *
	 * @param string $action
	 *
	 * @return bool
	 */
	protected function _isAllowedAction( $action ) {
		return true;
	}
 
}

Create template file :

Create template. This template is based on gallery.phtml which is used in Mage_Adminhtml_Block_Catalog_Product_Helper_Form_Gallery_Content block. I modified this template to fit for our purpose.

<?php
/* @var $this Inchoo_Gallery_Block_Adminhtml_Gallery */
?>
 
<div class="entry-edit-head">
    <h4 class="icon-head head-edit-form fieldset-legend">Images</h4>
    <div class="form-buttons"></div>
</div>
<div class="fieldset fieldset-wide" id="<?php echo $this->getHtmlId() ?>">
    <div class="hor-scroll">
        <table class="form-list" style="width: 100%;" cellspacing="0">
            <tbody>
            <tr>
                <td class="value" colspan="3" style="width: 100%;">
                    <div id="<?php echo $this->getHtmlId() ?>">
                        <ul class="messages">
                            <li class="notice-msg">
                                <ul>
                                    <li>
										<?php echo Mage::helper( 'catalog' )->__( 'Image type and information need to be specified for each store view.' ); ?>
                                    </li>
                                </ul>
                            </li>
                        </ul>
                        <div class="grid">
                            <table cellspacing="0" class="data border" id="<?php echo $this->getHtmlId() ?>_grid"
                                   width="100%">
                                <col width="1"/>
                                <col/>
                                <col width="70"/>
								<?php foreach ( $this->getImageTypes() as $typeId => $type ): ?>
                                    <col/>
								<?php endforeach; ?>
                                <col width="70"/>
                                <col width="70"/>
                                <thead>
                                <tr class="headings">
                                    <th><?php echo Mage::helper( 'catalog' )->__( 'Image' ) ?></th>
                                    <th><?php echo Mage::helper( 'catalog' )->__( 'Label' ) ?></th>
                                    <th><?php echo Mage::helper( 'catalog' )->__( 'Sort Order' ) ?></th>
									<?php foreach ( $this->getImageTypes() as $typeId => $type ): ?>
                                        <th><?php echo $type['label'] ?></th>
									<?php endforeach; ?>
                                    <th><?php echo Mage::helper( 'catalog' )->__( 'Exclude' ) ?></th>
                                    <th class="last"><?php echo Mage::helper( 'catalog' )->__( 'Remove' ) ?></th>
                                </tr>
                                </thead>
                                <tbody id="<?php echo $this->getHtmlId() ?>_list">
                                <tr id="<?php echo $this->getHtmlId() ?>_template" class="template no-display">
                                    <td class="cell-image">
                                        <div class="place-holder"
                                             onmouseover="<?php echo $this->getJsObjectName(); ?>.loadImage('__file__')">
                                            <span><?php echo Mage::helper( 'catalog' )->__( 'Roll Over for preview' ) ?></span>
                                        </div>
                                        <img src="<?php echo $this->getSkinUrl( 'images/spacer.gif' ) ?>" width="100"
                                             style="display:none;" alt=""/></td>
                                    <td class="cell-label"><input type="text" class="input-text"
                                                                  onkeyup="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"
                                                                  onchange="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"/>
                                    </td>
                                    <td class="cell-position"><input type="text" class="input-text validate-number"
                                                                     onkeyup="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"
                                                                     onchange="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"/>
                                    </td>
									<?php foreach ( $this->getImageTypes() as $typeId => $type ): ?>
                                        <td class="cell-<?php echo $typeId ?> a-center"><input type="radio"
                                                                                               name="<?php echo $type['field'] ?>"
                                                                                               onclick="<?php echo $this->getJsObjectName(); ?>.setProductImages('__file__')"
                                                                                               value="__file__"/></td>
									<?php endforeach; ?>
                                    <td class="cell-disable a-center"><input type="checkbox"
                                                                             onclick="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"/>
                                    </td>
                                    <td class="cell-remove a-center last"><input type="checkbox"
                                                                                 onclick="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"/>
                                    </td>
                                </tr>
                                <tr id="<?php echo $this->getHtmlId() ?>-image-0">
                                    <td class="cell-image"><?php echo Mage::helper( 'catalog' )->__( 'No image' ) ?></td>
                                    <td class="cell-label"><input type="hidden"/>&nbsp;</td>
                                    <td class="cell-position"><input type="hidden"/>&nbsp;</td>
									<?php foreach ( $this->getImageTypes() as $typeId => $type ): ?>
                                        <td class="cell-<?php echo $typeId ?> a-center"><input type="radio"
                                                                                               name="<?php echo $type['field'] ?>"
                                                                                               onclick="<?php echo $this->getJsObjectName(); ?>.setProductImages('no_selection')"
                                                                                               value="no_selection"/>
                                        </td>
									<?php endforeach; ?>
                                    <td class="cell-disable"><input type="hidden"/>&nbsp;</td>
                                    <td class="cell-remove last"><input type="hidden"/>&nbsp;</td>
                                </tr>
                                </tbody>
                                <tfoot>
                                <tr>
                                    <td colspan="100" class="last" style="padding:8px">
										<?php echo Mage::helper( 'catalog' )->__( 'Maximum width and height dimension for upload image is %s.', Mage::getStoreConfig( Mage_Catalog_Helper_Image::XML_NODE_PRODUCT_MAX_DIMENSION ) ); ?>
										<?php echo $this->getUploaderHtml() ?>
                                    </td>
                                </tr>
                                </tfoot>
                            </table>
                        </div>
                    </div>
                    <input type="hidden" id="<?php echo $this->getHtmlId() ?>_save" name="post[media_gallery][images]"
                           value="<?php echo $this->escapeHtml( $this->getImagesJson() ) ?>"/>
                    <input type="hidden" id="<?php echo $this->getHtmlId() ?>_save_image"
                           name="post[media_gallery][values]" value="{}"/>
                    <script type="text/javascript">
                        //<![CDATA[
                        var <?php echo $this->getJsObjectName(); ?> =
                        new Product.Gallery('<?php echo $this->getHtmlId() ?>', <?php echo $this->getImageTypesJson() ?>);
                        //]]>
                    </script>
                </td>
            </tr>
            </tbody>
        </table>
    </div>
</div>

Step 5

To add our block as new gallery tab we need to create new layout file and use existing action handle and reference to block which contain all tabs blocks in cms page.

<layout>
	<adminhtml_cms_page_edit>
		<reference name="cms_page_edit_tabs">
			<block type="inchoo_gallery/adminhtml_gallery" name="cms_page_edit_tab_gallery"/>
			<action method="addTab">
				<name>gallery_section</name>
				<block>cms_page_edit_tab_gallery</block>
			</action>
		</reference>
	</adminhtml_cms_page_edit>
</layout>

Step 6

Finally we need to add logic for saving our gallery into database. We need to override Mage_Adminhtml_Cms_PageController and create our logic for saving and upload the data.

Config:

<config>
<!--....-->
	<admin>
		<routers>
			<adminhtml>
				<args>
					<modules>
						<inchoo_gallery before="Mage_Adminhtml">Inchoo_Gallery_Adminhtml_Rewrite</inchoo_gallery>
					</modules>
				</args>
			</adminhtml>
 
		</routers>
	</admin>
<!--....-->
</config>

Create controller:

<?php
require_once Mage::getModuleDir( 'controllers', 'Mage_Adminhtml' ) . DS . 'Cms' . DS . 'PageController.php';
 
class Inchoo_Gallery_Adminhtml_Rewrite_Cms_PageController extends Mage_Adminhtml_Cms_PageController {
 
	public function saveAction(){
	//Add this code after $model->save
 
				$request    = $this->getRequest()->getPost();
				$formImages = json_decode( $request['post']['media_gallery']['images'] );
 
				foreach ( $formImages as $galleryImage ) {
 
					//if new image is uploaded
					if ( ! isset( $galleryImage->id ) ) {
 
						if ( $galleryImage->removed == 1 ) {
							$filePath = Mage::getBaseDir( 'media' ) . DS . 'gallery' . DS . $galleryImage->file;
							if ( file_exists( $filePath ) ) {
								unlink( $filePath );
							}
 
						} else {
							$galleryModel = Mage::getModel( 'inchoo_gallery/gallery' );
 
							$galleryModel->setCmsPageId( $model->getId() );
							$galleryModel->setFile( $galleryImage->file );
							$galleryModel->setPosition( $galleryImage->position );
							$galleryModel->setLabel( $galleryImage->label );
							$galleryModel->setIsDisabled( $galleryImage->disabled );
 
							$galleryModel->save();
						}
 
					}
 
					if ( isset( $galleryImage->id ) ) {
						if ( $galleryImage->removed == 1 ) {
							$filePath = Mage::getBaseDir( 'media' ) . DS . 'gallery' . DS . $galleryImage->file;
 
							$galleryModel = Mage::getModel( 'inchoo_gallery/gallery' );
							$galleryModel->setId( $galleryImage->id );
							$galleryModel->delete();
 
							if ( file_exists( $filePath ) ) {
								unlink( $filePath );
							}
 
						} else {
 
							$isModified = false;
 
							if ( $galleryImage->label_default != $galleryImage->label ) {
								$isModified = true;
							}
 
							if ( $galleryImage->position_default != $galleryImage->position ) {
								$isModified = true;
							}
 
							if ( $galleryImage->disabled_default != $galleryImage->disabled ) {
								$isModified = true;
							}
 
							if ( $isModified ) {
								$galleryModel = Mage::getModel( 'inchoo_gallery/gallery' );
								$galleryModel->setId( $galleryImage->id );
								$galleryModel->setPosition( $galleryImage->position );
								$galleryModel->setIsDisabled( $galleryImage->disabled );
								$galleryModel->setLabel( $galleryImage->label );
 
								$galleryModel->save();
 
							}
						}
 
					}
 
				}
 
	//other code
       }
	public function uploadAction() {
		try {
			$uploader = new Varien_File_Uploader( 'image' );
			$uploader->setAllowedExtensions( array( 'jpg', 'jpeg', 'gif', 'png' ) );
			$uploader->setAllowRenameFiles( true );
			$uploader->setFilesDispersion( false );
			$path   = Mage::getBaseDir( 'media' ) . DS . 'gallery';
			$result = $uploader->save( $path );
			/**
			 * Workaround for prototype 1.7 methods "isJSON", "evalJSON" on Windows OS
			 */
			$result['tmp_name'] = str_replace( DS, "/", $result['tmp_name'] );
			$result['path']     = str_replace( DS, "/", $result['path'] );
 
			$result['url']    = Mage::getBaseUrl( Mage_Core_Model_Store::URL_TYPE_WEB ) . 'media/gallery/' . $result['name'];
			$result['cookie'] = array(
				'name'     => session_name(),
				'value'    => $this->_getSession()->getSessionId(),
				'lifetime' => $this->_getSession()->getCookieLifetime(),
				'path'     => $this->_getSession()->getCookiePath(),
				'domain'   => $this->_getSession()->getCookieDomain()
			);
 
		} catch ( Exception $e ) {
			$result = array(
				'error'     => $e->getMessage(),
				'errorcode' => $e->getCode()
			);
		}
		$this->getResponse()->setBody( Mage::helper( 'core' )->jsonEncode( $result ) );
	}
 
}

And that’s it! Happy coding!

The post Adding gallery tab to a CMS page appeared first on Inchoo.

]]>
http://inchoo.net/magento/adding-gallery-tab-cms-page/feed/ 5
Restrict website access – require log in http://inchoo.net/magento/restrict-website-access-require-log/ http://inchoo.net/magento/restrict-website-access-require-log/#respond Wed, 23 Aug 2017 07:25:29 +0000 http://inchoo.net/?p=30351 There might be times when you do not want your catalog to be publicly visible, especially if running a B2B shop. Reasons for this are numerous but, from our experience, prices are on top of the list. Unfortunately, when it comes to Magento (at least Open Source edition), this feature is not available out of...

The post Restrict website access – require log in appeared first on Inchoo.

]]>
There might be times when you do not want your catalog to be publicly visible, especially if running a B2B shop. Reasons for this are numerous but, from our experience, prices are on top of the list. Unfortunately, when it comes to Magento (at least Open Source edition), this feature is not available out of the box. Lucky for us, implementation is pretty straightforward. Let’s dig in.

The idea

When a customer tries to access the store (catalog, CMS, checkout, etc.), redirect him to login form (if he is not already logged in).

The plan

When planning this feature, good thing to know about Magento is that every route is composed of 3 parts:

  1. Module name
  2. Controller name
  3. Action name

We can use this piece of information on every request that comes to Magento, in order to see where the request is supposed to go.

This is only half of the information we need. Additionally, we need to know whether customer is already logged in.

Specific combination of conditions above should redirect the customer to login form.

What would be the proper way to check for those conditions? Well, hooking into Magento’s dispatching process (namely, predispatch), we can create an observer that will check what route is requested, as well as whether customer is already logged in.

  1. Create a 3rd party module (e.g. Inchoo_WebsiteRestriction)
  2. Register an observer
  3. Implement logic

The execution

  1. Done
  2. create file in Inchoo/WebsiteRestriction/etc/frontend/events.xml
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
        <event name="controller_action_predispatch">
            <observer name="restrict_website" instance="Inchoo\WebsiteRestriction\Observer\RestrictWebsite" />
        </event>
    </config>
  3. Create observer file at Inchoo/WebsiteRestriction/Observer/RestrictWebsite.php
    <?php
     
    namespace Inchoo\WebsiteRestriction\Observer;
     
    use Magento\Customer\Model\Context;
    use Magento\Framework\Event\Observer;
    use Magento\Framework\Event\ObserverInterface;
    use Magento\Store\Model\StoreManagerInterface;
     
    class RestrictWebsite implements ObserverInterface
    {
     
        /**
         * RestrictWebsite constructor.
         */
        public function __construct(
            \Magento\Framework\Event\ManagerInterface $eventManager,
            \Magento\Framework\App\Response\Http $response,
            \Magento\Framework\UrlFactory $urlFactory,
            \Magento\Framework\App\Http\Context $context,
            \Magento\Framework\App\ActionFlag $actionFlag
        )
        {
            $this->_response = $response;
            $this->_urlFactory = $urlFactory;
            $this->_context = $context;
            $this->_actionFlag = $actionFlag;
        }
     
        /**
         * @param Observer $observer
         * @return void
         */
        public function execute(Observer $observer)
        {
            $allowedRoutes = [
                'customer_account_login',
                'customer_account_loginpost',
                'customer_account_create',
                'customer_account_createpost',
                'customer_account_logoutsuccess',
                'customer_account_confirm',
                'customer_account_confirmation',
                'customer_account_forgotpassword',
                'customer_account_forgotpasswordpost',
                'customer_account_createpassword',
                'customer_account_resetpasswordpost',
                'customer_section_load'
            ];
     
            $request = $observer->getEvent()->getRequest();
            $isCustomerLoggedIn = $this->_context->getValue(Context::CONTEXT_AUTH);
            $actionFullName = strtolower($request->getFullActionName());
     
            if (!$isCustomerLoggedIn && !in_array($actionFullName, $allowedRoutes)) {
                $this->_response->setRedirect($this->_urlFactory->create()->getUrl('customer/account/login'));
            }
     
        }
    }

Following the execute method, we have a list of routes that are white-listed always (e.g. login, logout, create account, reset password, etc.). Reason for this is that customer should be able to access those routes regardless of him being logged in or not (i.e. there is no need to redirect customer to login form, if he is already trying to access that page).

In addition to inspecting the correct route, we need to check whether customer is already logged in. There are at least 2 ways to check this, and one of them is implemented above. The other one would be to use Magento’s customer session object (\Magento\Customer\Model\Session) and its isLoggedIn() method. However, according to this stackexchange question, there are some issues with it, so we’re not using it this time.

The end

To conclude, if a customer is not logged in, he is redirected to login form where he can login/register. Using this, we restrict access to the store’s catalog and CMS pages.

The post Restrict website access – require log in appeared first on Inchoo.

]]>
http://inchoo.net/magento/restrict-website-access-require-log/feed/ 0
How to run a quick security check of your Magento store? http://inchoo.net/magento/quick-security-check-magento-store/ http://inchoo.net/magento/quick-security-check-magento-store/#comments Wed, 12 Jul 2017 09:34:24 +0000 http://inchoo.net/?p=29983 Security of any software system, let alone an eCommerce one, is becoming one of the hottest topics out there. How can you as a store owner do a free and quick security check of your Magento website, even without immediate development assistance? Read on and make sure to stay secure! Magento has a vibrant ecosystem...

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

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

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

What’s up with security of Magento shops?

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

What is Magento doing?

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

What are the most important patches?

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

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

What can you do?

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

What is MageReport and what is it telling me?

Here’s a snippet from their own website:

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

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

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

What to do next?

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

Not good

Good

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

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

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

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

Stay safe!

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

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

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

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

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

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

A few key takeaways from developer’s side:

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

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

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

Klevu works with both Magento 1 and Magento 2.

(for full feature list, check this link)

Installing Klevu

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

Go to:

System > Configuration > Klevu > Search configuration

and start configuration wizard:

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

Klevu Dashboard

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

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

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

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

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

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

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

Klevu Styling

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



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

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

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

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

Klevu data

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

You can add other custom attributes as well:

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

Read next…

Klevu Search results page

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

If you use:

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

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

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

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

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

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

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

Conclusion

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

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

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

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

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

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

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

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

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

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

The case of version 43

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

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

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

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

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

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

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

Sharing is caring

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

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

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

The tools of the trade

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

Folio

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

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

Plant

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

Abstract

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

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

 

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

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

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

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

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

Configuration

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

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

Model

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

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

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

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

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

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

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

Usage in controller

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

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

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

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

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

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

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

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

So, what happened in the meantime?

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

What’s ahead?

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

 

TL;DR;

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

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

See you around!

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

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

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

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

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

Overriding carrier

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

Adding a flag for api

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

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

Like in given example:

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

Storing the response data

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

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

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

Almost done..

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

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

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

Thanks for being with me and happy coding.

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

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

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

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

Beam me up, designer

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

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

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

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

All work, some play

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

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

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

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

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

                         

It’s not all black&white

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

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

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

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

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

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

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

Hi guys,

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

Thanks


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

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

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

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

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

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

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

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

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

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

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

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

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

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

The post Using PostCSS with Sass appeared first on Inchoo.

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

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

What exactly are we talking about?

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

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

Basic Setup

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

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

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

What we see here is a two step process:

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

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

npm install gulp-postcss autoprefixer --save-dev

Choosing plugins

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

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

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

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

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

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

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

In your CSS file you can do something like this:

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

Why should you use PostCSS?

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

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

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

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

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

What’s next

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

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

The post Using PostCSS with Sass appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-frontend/using-postcss-sass/feed/ 1