Pages

Tuesday, September 18, 2012

10 Tips for E-commerce on Drupal


I wrote this article for a general web development blog, but it ended up not being published. It constitutes a simple checklist of tips and reminders for people building e-commerce websites on Drupal that I hope can still be useful published here.
  • Know your tools.
    

Drupal is notorious for its abundance of modules written to address thousands of minor and major features. E-commerce on Drupal is no exception, and the tool you use depends on your business needs and timeline. Sites launching in the near future will be using Drupal 6 and have two e-commerce systems to choose from:
    

Ubercart - This is a full fledged e-commerce system designed to "just work" out of the box. It offers the standard shopping cart features, integration with several payment and shipping quote services, and the ability to automate your order workflow without writing any code. Additional features can be added by dozens of related contributed modules, and with over 18,000 live sites and hundreds of users and contributors, you're bound to find support for the functionality you need.
    

e-Commerce - The most recent version is a trimmed down e-commerce API that defines the components you'll use to build the e-commerce functionality you need. The pool of contributors and users is relatively small compared to Ubercart, so you should feel comfortable doing some heavy lifting on your own and possible Drupal module development if you go this route.
    

For future sites targeting the upcoming Drupal 7, users should keep an eye on the Drupal Commerce project. Spearheaded by the former project lead of Ubercart, the project is joining the attention to detail of the e-Commerce API and the attention to usability of Ubercart with the latest and greatest features of Drupal 7. Drupal Commerce got some stage time at DrupalCon San Francisco and the CMS Expo and has attracted the attention of some of the top contributors to Drupal 7 with expertise in core API development, usability, and security.

  • Don't hack your modules.


    Developers coming to Drupal from other e-commerce systems will be tempted to change a few lines of code here and there to quickly address any string changes, problems, or business needs. This often results in the edited modules not being updated as bugs are fixed and the module maintainers create new releases. This becomes a serious problem when security fixes aren't applied to live sites handling customer contact and payment information.


    There are several books on Drupal module development and security you can consult to find out the established best practices for adding and adjusting features on your Drupal site. With your customers' information on the line, you should take the time to learn how to manage and add to your site in such a way that you're always able to run the latest, most secure code.

Recommended reading:
  • Secure checkout and login pages with the Secure Pages module.
    

Maintained by the man behind the e-Commerce module, Secure Pages is a simple module that lets you protect any or all paths on your website using some simple pattern matching. Once you have an SSL certificate installed, you can browse to the module's administration form to configure which pages ought to be secured on your site. Depending on which module you're using to add e-commerce functionality to your site, you will need to secure different paths. Consult the pertinent module's documentation and issue tracker for more information if you aren't sure what to secure.

  • Mind your WYSIWYGs.
    

There are many ways to add WYSIWYG functionality to a Drupal site, and almost all of them have the propensity to interfere with textareas on settings forms that aren't meant to contain HTML. If you must use a WYSIWYG editor on your site, be sure to disable it on the settings forms that govern your store's contact and payment settings.


    In fact, the best thing you can do is alter your editor's settings so the editor is turned off by default and then selectively enable it on the forms you know you want WYSIWYG support. This process is going to vary by module, so there's no one size fits all approach.

  • Show your products off in style with a customized theme.
    

Drupal themes are not generally designed with marketing and product sales in mind. In true democratic fashion, those important Add to Cart buttons and Checkout forms won't receive any more attention than a Login button or a Contact form. However, Drupal's theme layer is incredibly good at letting you create custom templates and CSS to target specific pieces of your Drupal site. Spend some time learning to customize your theme to draw more attention to those buttons that turn into dollars when clicked.


    Those who lack the designer's eye can take heart. Several Drupal theme shops create themes specifically targeting e-commerce on Drupal using Ubercart. Top Notch Themes has also contributed a free theme targeting Ubercart sites called Acquia Prosper and distributes a free e-book with tips for configuring themes and contributed modules to enhance your store and increase conversions.

  • Minimize "noise" on the checkout pages.


    When your customers end up on the checkout form, you're either sending them clear signals on how to complete the sale or distracting them unnecessary noise on the page. Drupal makes it easy to trim down that noise so your customers can focus on giving you money. Drupal's core block system lets you easily hide all the blocks and widgets in the non-content regions of your site using the same URL pattern matching system as the Secure Pages module. Hide as many blocks as possible on the shopping cart and checkout pages so your customers can't help but fill in their shipping and payment information and click those checkout buttons you've just styled.

  • Use Views to build custom product catalog and upsell pages.
    

Views is the mother of all contributed modules for Drupal. It lets you filter through all the content on your website and display it to the user in tables, lists, and grids. Ubercart and e-Commerce both provide Views integration for products so you can easily create custom product catalogs, popular product displays, and related product blocks. Using Drupal's core taxonomy system, you can associate products with one another and add Views to the shopping cart page to upsell similar products to your customers as they shop.


    In the land of Drupal, Views is a source of raw power that you should tap into to pump up your product presentation.

  • Keep your customers in the loop.
    

As you add new products and features to your website, you can keep your customers in the loop by taking advantage of custom RSS feeds and newsletters. Drupal provides basic feeds for items posted to your site's front page, blogs, and categories, and you can create any number of additional custom feeds using the Views module. Promote your feeds to your customers so they can be the first to hear about your latest promotions and newest products.


    Several contributed modules also add newsletter functionality to your sites. You can use theSimplenews module to run a newsletter straight from your Drupal site offering simple subscription management features for your customers and administrators. Alternatively, you can use modules like MailChimp to integrate your Drupal site with external services offering advanced newsletter features.

  • Engage your customers with product reviews and ratings.
    

When someone places an order on your site, keep them engaged by offering them the chance to review and rate the products they've purchased. This data will help future customers make informed purchases and will help you target your marketing and promotions based on hot items.
    

Enabling product reviews is as easy as turning on Drupal's core comment system for your product pages. Adding ratings to these comments is a simple matter of installing and configuring the Fivestar module. The comments and ratings your customers enter will then be exposed to Views so you can select and sort products for display based on recent reviews and average ratings.

  • Help your customers spread the word!
    

On the social web, your satisfied customers will be the best promoters of your products and services. Help them tell their friends just how great you are by taking advantage of the many Social network integration and recommendation tools. Contributed modules exist offering simple integration with Facebook Connect and Twitter, and others like Gigya Socialize and ShareThiswill integrate your site with multiple services at once. You can also enable your customers to directly recommend your products to their friends via e-mail with the Forward module.

Tips for posting to the Drupal forums


Please do NOT post test pages. Drupal.org is a production site and you will not be able to delete content once you post it.
Note, that some questions can only be answered when built on an understanding of other core concepts. If you are missing core concepts or looking for a simple answer to a hard question, no amount of short forum posts will provide a good answer.
However, the following best practice tips can make participating in the forums a more pleasurable and productive experience for everyone:
  1. Please see the documentation before posting. #

    If it's obvious your question could easily be answered by browsing or quickly searching the handbook, it will likely be ignored. Likewise, if people provide a link to the information, please read it. If you have further questions after reading, then try and refine your post further.
  2. Do your homework. #

    Be sure to at least try to solve your problem before posting to the forums. Why would anyone take the time to help you if you're unwilling to help yourself?
  3. Don't expect anyone to take more time to answer the question than you did to ask it. #

    Simply stated, if you state a problem in one sentence but the answer would take 2 pages to explain, your post may be ignored, or if you are lucky, merely given a link to the handbook. This is particularly true for questions of the "Hi, I'm brand new to web design how do I make www.best-site-ever.com with drupal?" variety.
  4. Always use context-sensitive forum post titles. #

    Good titles are just as important in forum posting as they are in email. Many newbies believe that the easiest way to get support is to scream "HELP!!!" in all caps in a subject line. However, the Drupal forums and issues are very active. Many Drupal members check the tracker page and scan the titles. They prioritize how they may use their limited time by offering support where they feel they can provide direct assistance. So a post like "Getting error X in installation" or "Help with thinking through corporate Internet site design" is more likely to attract someone who can assist with your problem.
  5. Be specific. #

    Large open ended questions like "how to i use the x module" or "how do i make a community site" are far too generic and time consuming to answer in a forum thread effectively. They will likely simply be ignored or replied to with a request for more details. Why not save yourself, and the community, this wasted effort and ask clear answerable questions in the first place? Instead of "how do i do i use the x module", ask something like "i'm trying to use the x module, i've read the available docs, checked the issue queue, i've already done a, b, and c but I still can't figure out how to do y.".
  6. Provide relevant details. #

    Often, before someone may be able to assist you, they will need to know the Drupal version number, the hosting environment, the specific error generated, and other relevant information. Incomplete support requests just end up requiring a volley of question and answer, lengthening the time it takes to resolve an issue-- if you receive a reply at all. No one should have to become Sherlock Holmes to figure out what you're asking for. You can find a status report in the administration screen, ready to be pasted into a forum topic. Of course you should still provide enough context for the information to be useful.
  7. Describe what you've done so far. #

    Not only will this avoid wasting everyone's time by suggesting things you've tried, it willdemonstrate a willingness and capability to help yourself, and possibly give an indication of your experience level so the answers can be worded appropriately without blinding you with jargon.

    How to ask questions the smart way
  8. Don't repost and/or crosspost the same question. #

    Repeatedly reposting the same question will not benefit you in any way. Besides irritating the community, it will only fragment the support you receive, waste precious volunteer resources answering the same question multiple times, and make it more difficult for other users to find the correct information in the future. If you wish to have a post moved, create an issue in the webmaster's issue queue.
  9. Please don't bump a topic more than once in a day. #

    If you don't receive a response it is acceptable to 'bump' your own post, preferably with additional information, in order to keep it on the active list. However, no more than once in 24 hours and do not get petulant or sarcastic and complain about the community if your problem isn't responded to immediately. Everyone here is avolunteer-- that's a sure fire turn-off and you'll be alienating the very people you're asking to help you.
  10. Ignore flames and rude tones. #

    Drupal is a large international community. Since many members are not native English speakers, realize that a brusque tone may be a result of language differences and should not be immediately interpreted as rude. Regardless, if you feel like another user has flamed you, the best response is to ignore the offense and continue working toward discussing productive solutions.
  11. You get more bees with honey than with vinegar. #

    While it is very easy to become frustrated when grappling with a problem, remember that Drupal community members donate their time in offering support. People are more likely to respond to posts which ask nicely for assistance over those that demand it or complain. Politeness can make a difference.
  12. Don't use excessive punctuation and/or capitals. #

    Most experienced forum users find excessive punctuation and capitals irritating and doing so will likely have exactly the opposite effect of what you're trying to do.
  13. Enable your contact tab. #

    So that people may offer assistance privately by email, be sure to edit your user account and check the Personal contact form box. This feature does not share your email address, but rather forwards the message to you via drupal.org.
  14. If you solve your problem, please follow-up, explain, and prepend '[Solved]' to the subject. #

    Many times people open a question, get several suggestions on troubleshooting, then either disappear or follow up with "It's OK, the problem is solved now!" Please at least be polite enough to let the board know which of the solutions fixed it for you, or if it was something else altogether, or even if it was just a "Doh!" mistake. This is to help others that may encounter the same problem you did. If it exists, provide a link to the documentation or discussion that helped you, so others can find it when searching. It's common that the same problem can be described in many ways, and your search phrases or issue title may be different from the ones that the writer was thinking of when they wrote the guides. You can add a cross-reference from your post to a better one ... if you have found it.
    This also prevents your problem post from being a frustrating dead-end for later seekers, and your problem becomes part of the solution!
  15. A really helpful addition to improving the docs would be a great addition to your question. #

    For example: "I was looking at pages X,Y,Z in the handbook, but couldn't see/understand the answer I expected to find there".
    This would enable the support folk to copy a summary of their forum answers to the FAQ or handbook pages where they will be the most use!
    Terminology and mind-maps differ between the reader and writer, so if you can let us know where you expect to find the answer ... we can copy/link it to there for next time.
    Of course, this does assume you did some of your own research first ;-)
  16. With volunteer support, not everyone gets a response. #

    And finally, if your post has gone unanswered, perhaps no one that has read your post has the solution to your problem. Whining or complaining about it will more than likely not get you an answer and may harm your chances of getting assistance in the future. You might also consider whether the title for the post is specific enough. And if you feel like support response could be better on drupal.org, please donate some of your time, too, to answering support questions. In a volunteer effort, the only way to improve support is for everyone to participate.

Performance tuning tips for Drupal 7 testing.


Opcode Caches

Make sure you are running an opcode cache since loading & parsing all the files take a while.
Non-professional, quick benchmarking by deekayen found varying speed improvements, best to worst as follows:
  1. APC
  2. xcache
  3. eAccelerator
Note: even with extensive testing and trials with varying configurations, ALL the opcode cache options have a history of sporadic, varying, infrequent, and unreproducible failures of all kinds. For this reason, PIFR clients are not recommended to run opcode caching for live, production testing.

Installing APC on Debian/Ubuntu


# sudo apt-get install php5-dev pear
# sudo pecl config-set preferred_state beta
# sudo pecl install APC
# sudo echo "extension=apc.so
apc.shm_segments=1
apc.shm_size=32
apc.ttl=7200
apc.user_ttl=7200
apc.enable_cli=1
apc.stat=1
apc.stat_ctime=1" > /etc/php5/conf.d/apc.ini
# /etc/init.d/apache2 restart

apc.stat_ctime normally off by default, but when turned on "verification with ctime will avoid problems caused by programs such as svn..." Since PIFR is constantly doing CVS checkouts, it falls under the category of "programs such as svn". Reference and added documentation for additional APC variables:
http://us2.php.net/manual/en/apc.configuration.php

Installing xcache on Debian/Ubuntu


# sudo apt-get install php5-xcache
# echo "zend_extension = /usr/lib/php5/20060613+lfs/xcache.so
xcache.shm_scheme = "mmap"
xcache.size = 32M
xcache.count = 1
xcache.slots = 8K
xcache.ttl = 7200
xcache.gc_interval = 7200
xcache.test = Off
xcache.mmap_path = "/dev/zero"
xcache.cacher = On
xcache.stat = On
xcache.optimizer = On
xcache.coverager = Off" > /etc/php5/conf.d/xcache.ini
# /etc/init.d/apache2 restart

xcache.stat is on by default, but this configuration reinforces that setting. The testing process is constantly updating and changing files, so xcache needs to make sure when a file gets patched or CVS updated, that it refreshes the version cached in memory. It only knows to do that by looking at the last modification time through the statistics (stat) on the file's last changes. Some demo configurations around the internet will show that disabling xcache.stat will make xcache run faster because it doesn't check the last modification of each file. Ignore that recommendation for testing.

Installing eAccelerator on Debian/Ubuntu


# cd /opt
# wget http://bart.eaccelerator.net/source/0.9.5.3/eaccelerator-0.9.5.3.tar.bz2
# tar -xvjf eaccelerator-0.9.5.3.tar.bz2
# cd eaccelerator-0.9.5.3
# phpize
# ./configure --with-eaccelerator-shared-memory --with-eaccelerator-sessions
# sudo make
# sudo make install
# echo "extension = eaccelerator.so
eaccelerator.shm_size = 32
eaccelerator.cache_dir = /var/cache/eaccelerator
eaccelerator.enable = 1
eaccelerator.optimizer = 1
eaccelerator.check_mtime = 1
eaccelerator.debug = 0
eaccelerator.filter = ""
eaccelerator.shm_max = 0
eaccelerator.shm_ttl = 0
eaccelerator.shm_prune_period = 0
eaccelerator.shm_only = 1
eaccelerator.compress = 1
eaccelerator.compress_level = 9" > /etc/php5/conf.d/eaccelerator.ini
# /etc/init.d/apache2 restart

eaccelerator.check_mtime is on by default, but this configuration reinforces that setting. The testing process is constantly updating and changing files, so eAccelerator needs to make sure when a file gets patched or CVS updated, that it refreshes the version cached in memory. It only knows to do that by looking at the last modification time through check_mtime. Some demo configurations around the internet will show that disabling eaccelerator.check_mtime will make eAccelerator run faster because it doesn't check the last modification of each file. Ignore that recommendation for testing.

MySQL tuning

The basic MySQL install on a lot of servers is completely inadequate. Debian for one installs a very configuration file based on the mysql-tiny configuration. Read this for details: http://amrmostafa.org/benchmarking-mysql-in-memory-using-tmpfs
These are some things you can do to start tuning your MySQL:
  • phpMyAdmin provides a listing with all the configuration and logging variables with some explanation and how your servers is doing. Go through the list and see which are green (good) and which are red (bad) and read how you can influence result. This should give you a basic understanding of the configuration options.
  • At http://mysqltuner.pl/mysqltuner.pl there's a nifty script that analyses your configuration and recommends configuration changes. I've found it to be pretty helpful at that. You'll need Perl to run it.
  • Make sure you are logging your slow queries and queries without indices and monitor your logs.

Run MySQL in memory

DamZ wrote a modified mysql init.d script for /etc/init.d/mysql on Debian 5 that runs MySQL databases entirely out of tmpfs. It's at
http://drupal.org/files/mysql-tmpfs.txt, attached to http://drupal.org/node/466972.
It allowed the dual quad core machine donated to move from a 50 minute test and huge disk I/O with InnoDB to somewhere under 3 minutes per test. It's live as #32 on PIFR v1 for testing.d.o right now. It is certainly the only way to go.
I have not and won't be trying it on InnoDB anytime soon if anyone wants to omit the step on skip-innodb below and try it on tmpfs.
Brief instructions:
uncomment skip-innodb in /etc/mysql/my.cnf
apt-get install rsync
Backed up /etc/init.d/mysql somewhere.
Drop the script in place.
mkdir /var/lib/.mysql
chown -R mysql:mysql /var/lib/.mysql
/etc/init.d/mysql stop
cd /var/lib/.mysql (to confirm files got copied there)
cd /var/lib/mysql
rm -r *
cd /var/lib
/etc/init.d/mysql start
cd /var/lib/mysql (to verify files moved back in)
df -h (to verify tmpfs is mounted at /var/lib/mysql)

InnoDB tuning

You might be able to optimize INNO for testing operations by creating /etc/mysql/conf.d/innodb.cnf with the following settings:

[mysqld]
innodb_log_file_size = 16M
innodb_log_group_home_dir = /var/lib/mysql
innodb_data_home_dir = /var/lib/mysql
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_files_in_group = 2
innodb_flush_method = O_DIRECT
innodb_log_buffer_size = 1M
innodb_thread_concurrency = 4
# If you have more than 512MB ram, you can increase the innodb_buffer_pool_size
innodb_buffer_pool_size = 128M
innodb_additional_mem_pool_size = 10M
innodb_file_per_table = 1
# If you have raid5 or raid10 with more disks, you can increase innodb_file_io_threads
innodb_file_io_threads = 1
innodb_flush_log_at_trx_commit=0
innodb_max_dirty_pages_pct = 70
# If you run an OurDelta MySQL server, you can enable adaptive checkpointing.
# innodb_adaptive_checkpoint = 1
And also /etc/mysql/conf/tune.cnf with the following settings:

[mysqld]
default-storage-engine = InnoDB
sql_mode = NO_ENGINE_SUBSTITUTION
read_buffer_size = 512K
read_rnd_buffer_size = 512K
query_cache_limit = 128K
query_cache_size = 16M
sort_buffer_size = 16M
bulk_insert_buffer_size = 4M
tmp_table_size = 32M
max_heap_table_size = 32M
key_buffer = 64M
max_allowed_packet = 16M
thread_stack = 256K
thread_cache_size = 8
max-connections = 32
table-cache = 256
thread-concurrency = 2
Because you're changing the size of the innodb transaction logs, you need to make sure these logs are flushed when you next shut MySQL down. Connect to mysql as root and run:

mysql> SET GLOBAL innodb_fast_shutdown=0;
You can now shut down mysql via /etc/init.d/mysql stop and move the existing trasnaction log files out of the way. (Do not delete them yet!)

mv /var/lib/mysql/ib_logfile* /root
Now start the mysql server again via /etc/init.d/mysql start and check that it is happy via tail /var/log/syslog. You should now have ib_logfile0 and ib_logfile1 files in /var/lib/mysql that are both 16MB in size.
If not and MySQL refuses to start, move the /root/ib_logfile* files back to where they came from and change innodb_log_file_size to 5M in innodb.cnf. You can then start mysql and re-try this procedure from the beginning.
Edit /etc/php5/apache2/php.ini and set mysql.allow_pesistent = Off as MySQL is keeping threads ready for clients and is more efficient at doing this than PHP.
Finally, convert the drupal tables to InnoDB format.

mysql_convert_table_format --user=root --password=secretpass --type=InnoDB drupal

Apache tuning

Disable or comment access.log settings. On Debian/Ubuntu, it is
CustomLog /var/log/apache2/access.log combined in /etc/apache2/sites-available/default, or scripted at the command line

perl -pi -e 's/(\s+CustomLog \/var\/log\/apache2\/access\.log combined)$/#\1/g' /etc/apache2/sites-available/default

Running the tests with concurrency

Add the following paramater to pifr.review.inc where it runs php (line 370-380):

--concurrency [number of cores of the server + 1]

Tuesday, September 11, 2012

Drupal 7 Useful Theme Snippets


Theming a Drupal 7 site can be quite tricky at times. Drupal has a powerful engine for themes and tons of different settings in it. Here's a couple of different code snippets to ease your theme development.

Load different page.tpl.php depending on node type

function ThemeName_preprocess_page(&$vars, $hook) {
 if (isset($vars['node'])) {
  $vars['theme_hook_suggestions'][] = 'page_'. $vars['node']->type;
 }
}
This goes in your theme's template.php. Replace ThemeName with your theme's name. What this does, is that it takes the current node type's machine name and appends it to the filename of page.tpl.php. As in, if you have node type called blog, it appends that to the filename so it will look for page-blog.tpl.php. Note that this is only a suggestion, if it can't find that file, it will use the regular page.tpl.php.

Load a block to your page.tpl.php or node.tpl.php

$block = block_load('block', '5');
$output = drupal_render(_block_get_renderable_array(_block_render_blocks(array($block))));
print $output;
With this snippet, you can load a block straight to your theme. The first line loads the block information, in this case it loads a normal block with id of 5. If you want to f.ex. load the search module, you can type:
$block = block_load('search', '0');
And this will the search form.
The second line renders the block and third line prints it to the theme.

Add Javascript file to your theme

Currently there is two ways to do this. More standard way to do it is to add this line to your .info -file in theme.
scripts[] = name_of_script.js
This will automatically load name_of_script.js in your theme in all pages. To add more scripts, just create a new line just like before.
The other way to add it is to write this in your template.php:
function ThemeName_preprocess_html(&$variables) {
 $options = array(
  'group' => JS_THEME,
 );
 drupal_add_js(drupal_get_path('theme', 'ThemeName'). '/name_of_script.js', $options);
}

Target theme's frontpage

Sometimes you don't want to make page-front.tpl.php just for small style changes. Instead, you can try doing this in your page.tpl.php:
if (drupal_is_front_page()) {
 $class = "front";
} else {
 $class = "";
}

print "<body class='".$class."'>";
This adds a class to your body, so now in CSS, you can target stuff like:
#header {
 background: blue;
}

.front #header {
 background: lightBlue;
}