drupal

Drupal Alters and Overrides: hook_form_alter

One of the first rules most people coming into the world of Drupal learn, hopefully, is to not hack core. I certainly didn't know about this when I first started out and hacked to pieces a lovely Drupal 4.6 site which, amusingly enough, still exists today. Since then, though, I've become more accustomed to the various ways of altering and overriding things in both Drupal core and contrib "the Drupal way".

Because Drupal is built modular with a strong hook API system, it gives us a lot of power in terms of changing things through our own custom modules. You can read more about creating a custom module at http://drupal.org/developing/modules -- there is also a useful module with some good examples you can download from http://drupal.org/project/examples

I'm going to go through a few different ways you can alter and override content in my next series of blog posts. This first one will cover one of the most popular.

hook_form_alter

One of the most common hooks used to override or alter things in Drupal is hook_form_alter. Drupal is driven by forms-- administration settings forms, content creation forms, contact forms, menu forms, user registration forms, etc., that I don't think I've ever worked on a Drupal site where this hook wasn't used.

Let's say, for example, you've got yourself a View with some exposed filters. By default the submit button says "Apply", but you've been asked to change that to "Submit".

My custom module is called demo. If I take a look at the link above from api.drupal.org, it tells me the parameters my function should have:

hook_form_alter(&$form, &$form_state, $form_id)

This is how it would appear in my module:

function demo_form_alter(&$form, &$form_state, $form_id) {

}

I need to find the $form_id in order to make sure I'm altering only that specific form and not the others. I usually do this just by printing out $form_id. For Views exposed filters, they all have the same $form_id which is 'views_exposed_form'. In order to make sure I'm only targetting this specific exposed form, I also use the $form['#id'] value. You can get these values by doing a print_r($form) or dpm($form) in your module. My alter now looks like this:

/**
 * Implementation of hook_form_alter().
 */
function demo_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'views_exposed_form' && $form['#id'] == 'views-exposed-form-content-search-page-1') {
    $form['submit']['#value'] = t('Search');
  }
}

I can do all sorts of things with forms here. I can remove elements, rename them, add new ones. I can also add or override submit and validation functions. Here are a few examples:

/**
 * Implementation of hook_form_alter().
 */
function demo_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'views_exposed_form' && $form['#id'] == 'views-exposed-form-content-search-page-1') {
    // Rename the submit button
    $form['submit']['#value'] = t('Search');
  }
  if ($form_id == 'search_block_form') {
    // Add default "Search" text in the searchbox
    $form['search_block_form']['#default_value'] = t('Search');
  }
  if ($form_id == 'page_node_form') {
    // Remove the "Preview" option
    unset($form['buttons']['preview']);
    // Add my extra validation function
    $form['#validate'][] = 'demo_extra_validate';
  }
}

Sneezing Alligators Come to the Gardens

If you've stumbled your way here, welcome. While I've had the great opportunity to support, use and play with Drupal Gardens the last few months, the number of sites I've built have primary been used for testing. This, here, is my first "real" site, that will soon replace my current blog at http://alligatorsneeze.com

I'm one of those developers that even when I might have the time, there's almost always more pressing matters, other sites to break or fix, or a multitude of other tasks piled upon my to-do list that working on my own personal site and blog is almost always at the very bottom of the list. Because of this, Drupal Gardens is a perfect choice for me. It allows me to quickly put a site up, use the awesome theme builder to more easily customize the look and feel of my site, and I don't have to worry about updating it or keeping it maintained.

Sounds perfect, right?

We'll see. I very much realize it's still in beta, and that Drupal 7 is still in alpha. I suspect I'll continue to run into issues. But I'm having a lot of fun building out this site. I've got my banner working after a couple snags and will be working more on the way the site looks. Being that I have very little design background or sense, we'll see how that plays out...

Optimizing mySQL: Temp Tables on Disk

I wrote this back in June when I was knee deep in attempting to optimize a Drupal 5 website that was receiving a lot of traffic and showing very poor performance. This article is incomplete and I don't really come up with any solid solutions here, but thought this may be of some help nonetheless.

Performance of any site is a concern when you start getting actual traffic. Drupal performance can be even trickier, as it often likes to hog up server resources especially when you have a lot of modules enabled.

Often the issue lies with mySQL. While I like mySQL, configuring for optimal performance doesn't always come easy. One issue that can often occur is a large number of temporary tables being written to disk. What we want is for these tables to be written to memory. A good article which discusses the overhead of mySQL writing to disk can be found here at http://www.mysqlperformanceblog.com/2007/08/16/how-much-overhead-is-caus...

Essentially, it says MEMORY temporary tables can be 10-100 times faster than disk based MyISAM tables . That's quite a difference.

I started two or three weeks ago tweaking our mySQL configuration and performance on a site that was getting 85% or more of it's temp tables written to disk. Today, we're getting between 6-10%. There's still work to do, but the increase has been awesome and very noticeable in the speed of the site.

To check the status of your temp tables, in a mySQL prompt, type show status. This will show you all the variables mySQL has set, including:


| Created_tmp_disk_tables | 141880 |
| Created_tmp_files | 464 |
| Created_tmp_tables | 1304743 |

As you can see here, we still have quite a lot of our temporary tables writing to disk.

You may also want to use something like this script. Just put it on your server and run it with sh tuning-primer.sh and it'll give you some great tips overall, as well as tell you more about your temp tables.

The first method to stop this from happening is to increase your table_cache. A formula often used is:


table_cache = opened table / max_used_connection

Getting this up to a good number is key. Obviously, you can't just keep raising this, though. This value, and most others within mySQL, are going to be limited by your server's RAM.

Other values you need to look at are tmp_table_size, max_heap_table_size, key_buffer, sort_buffer_size, read_buffer_size, myisam_sort_buffer_size.

One problem you may encounter, though, is that blob and text column types are always written to disk. There are a few Drupal patches I ended up testing, benchmarking, and applying in order to fix some of these issues.

http://drupal.org/node/109513 converts our temp tables to the HEAP engine type over MyISAM. This alone was a huge improvement.

You can also turn on mySQL slow query log. To do so, simply add these lines to your my.cnf:


log-slow-queries=/var/lib/mysqllogs/slow-log
long_query_time=2
log-queries-not-using-indexes

The long_query_time is the queries you want to log longer than 2 seconds, or whatever value you might want to use. I'd increase this to start with to track down the worst queries. We started it at 5 seconds.

The log-queries-not-using-indexes will also log queries not using indexes, of course.

By using our slow query log, we found a few queries which were desperately in need of optimization. A very handy tool in defining whether a query is causing issues is by using the EXPLAIN command. This can be used by simply typing this into your mySQL command line prompt:


EXPLAIN SELECT DISTINCT(n.nid), e.event_start FROM node n INNER JOIN event e ON n.nid = e.nid INNER JOIN node_access na ON na.nid = n.nid WHERE (na.grant_view >= 1 AND ((na.gid = 0 AND na.realm = 'all') OR (na.gid = 1 AND na.realm = 'content_access_rid'))) AND ((n.moderate != 1)) AND ( n.status = 1 AND ((e.event_start >= 457516800 AND e.event_start = 457516800 AND e.event_end = 460195199)) ) ORDER BY event_start;

And we were given results that look like this:


+----+-------------+-------+--------+-------------------------------------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-------------------------------------
| 1 | SIMPLE | na | ALL | PRIMARY | NULL | NULL | NULL | 49571 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | n | ref | PRIMARY,status,node_moderate,node_status_type,nid | nid | 4 | brenda2_06_02_08.na.nid | 1 | Using where |
| 1 | SIMPLE | e | eq_ref | PRIMARY,event_start | PRIMARY | 4 | brenda2_06_02_08.na.nid | 1 | Using where |
+----+-------------+-------+--------+--------------------------------------

Notice the Using where; Using temporary; Using filesort part. This tells us right there it's using a temporary table.

You can also use show processlist to get a list of what's currently going on.

For example gives me this row:


| 1237 | XXX | localhost | database | Query | 1 | Copying to tmp table | SELECT DISTINCT(node.nid), node.created AS node_created_created, users.name AS users_name, users.ui |

This will only show you things currently happening, though.

Now that we know this query is bad, we can work on rewriting it.

Drupal Quick Tip #2: Using Images as Submit Buttons (5.x)

This is a pretty simple task but one I seem to end up doing over and over again for whatever project I'm working on at the time. I've done it a number of ways, from simply using CSS and dealing with multiple cross-browser issues, and probably quite a few alterations of this technique.

In our custom module we're going to need a couple functions. First, we need to use hook_elements to define our new image element.


function siteconfig_elements() {
$type['image_button'] = array(
'#input' => TRUE,
'#button_type' => 'submit',
);
return $type;
}
?>

Next, we need to theme our new type of element, which is called image_button above.


function theme_image_button($element) {
return '\n";
}
?>

This is a very similar to Drupal's core theme_button function.

This function is needed to ensure the default_value doesn't get moved to #value. This should solve the issue with multiple submit buttons some browsers have.


function image_button_value() {
// null value to guarantee default_value doesn't get moved to #value }
?>

Next, we need to use our trusty old hook_form_alter for the submit button of our choice.


function siteconfig_form_alter($form_id, &$form) {
if ($form_id == 'myformid') {
$form['submit'] = array(
'#type' => 'image_button',
'#image' => base_path() . path_to_theme() .'/images/search-btn.gif',
'#title' => t('Search'),
'#default_value' => t('Search'),
);
}
}
?>

Voila! Thanks chx and merlinofchaos. ;)

Tags: 

Drupalcon is Exhausting

Note: Not posting this to Drupal planet as it's not really that interesting for most people.

End of day three of Drupalcon Boston 2008. It's been fun, stressful, exhausting, interesting, insightful and I'm incredibly mentally and physically exhausted! I feel like I could sleep for days. I actually did oversleep this morning after a late night over at FELT, the awesome gathering Acquia threw. I was sad to miss the Simpletest session.

So... recap of my day yesterday. I went to the Future of Fields session which was really insightful, but I couldn't get my wireless working during it which tends to make me impatient.

Went to the Google in Open Source session since my wireless wasn't working. It was entertaining, which is always good.

I left early because I had some work to do and quite honestly the crowds of people were starting to get to me. I found out I forgot my hotel key in the hotel room, the hotel help desk wouldn't give me a replacement because I never actually checked in (it was checked in under my co-worker's name who was also staying in the room, nikkiana), and ended up finding the best wifi in this city at the mall across the street from the Royal Sonesta hotel. Woo hoo! Starbucks + awesome wifi = heaven.

Got some work done, yay. Went to FELT and that was fun. Met a lot of people I've been wanting to and played some really awful games of pool, wanted to play Wii but never got it, generally a good time. Stayed up way too late, though.

Today's sessions were pretty interesting. I went to .. ugh, I can't even remember now. Oh, the Dragon Drop session was awwwwesome. I can't wait to implement it in my soon to be released module and use it more in D6! Hurrah.

Went to the Flex/Flash session, that was cool. Something I don't know much about and wanted to.

That's all I can remember right now! I'm looking forward to catching up w/ all the video of the sessions I missed. All in all, a wonderful time, I met some amazing people, and I can see the future of Drupal is bright.

Drupal Boston - First Day, First Impressions

I meant to post this last night but the wifi sucks at the hotel (Royal Sonesta) and I gave up. Here's a recap of my day yesterday, first impressions, general thoughts. Not a whole lot of info on the sessions because I missed a lot of them attempting to get some work done.

This is my first Drupalcon and I've been really looking forward to it for months. So far, I'm mostly star struck. There are a lot of people I know of and recognize from the Drupal community, people I've chatted with on IRC, co-workers I'm meeting for the first time. It's a bit overwhelming, but pretty fun.

My first session of the day was the Usability one which is something I am interested in with Drupal 7. I've been doing some of my own usability testing and hope to publish those results soon. Some interesting tidbits I got away from that session:

  • Content in Drupal is really, really confusing. None of the participants seemed to get it.
  • Taxonomy was not confusing and people understood what it was. Go figure.
  • People use the "Welcome to Drupal...!" screen as navigation, and get really confused when it disappears. This is something I would have never thought about.
  • It's really hilarious to see the quotes of people thrust into the world of Drupal administration. Unfortunately I forgot to write any of the quotes down (but I'm sure there's some transcripts or video).

So that was good. After that was Dries' keynote and it was amazing to see the number of people really here. I am a little sad, to be honest, that I wasn't able to go to a Drupalcon where there were a lot less people. I feel a bit lost in the crowd and am looking forward to some smaller gatherings.

I missed everything after that. Oh, I did make DrupalChix which I just happened to look at the BoF board in time. I think if there were more notice, more women would've made it. Some interesting things about that:

  • There were 17 women there, wow! And I know there was a lot more here at Drupalcon.
  • There was, of course, also a Sprout. Bean joined us later, too.
  • There were women from all sorts of backgrounds, freelance developers, designers, those working for "Drupal shops", those who didn't know much about Drupal.
  • merlinofchaos' wife rocks.

So, yeah. It's been a blast, although pretty exhausting, too. Some other thoughts, in no particular order.

  • When you get beer into Drupallers, things get really interesting.
  • Free coffee goes fast.
  • Having Drupalcon in a freezing city kind of sucks.
  • People are a lot different in person than in real life. People who are curt online I have found friendly in person, people I have talked w/ online have blown me off in person.
  • We need more Drupal paraphernalia.
  • It's hard to talk to the "cool kids" because they are always swarmed by crowds of people. I managed to talk to a few briefly, though.
  • I forgot to charge my camera and couldn't take any pictures. :(

If you're at Drupalcon and reading this, come say hi! I'm friendly, just a bit shy when the number of people is in the thousands.

Drupal Quick Tip #1 - Setting CCK Option Selects to Blank as Default and Required

This took me a little bit of poking around to figure out, so here's a quick tip in case others are attempting to do the same thing.

For some CCK select options, you want it to both be required and to also not default on the first answer because a user might click through without changing the default without meaning to. There is the Default value options you can play with, but the easiest way I've found is in your Allowed values list you can simply enter this as your first choice:


|-Please Choose-

This will give you the -Please Choose- as a default, but if the user doesn't switch it, an error will come up. The reason for this is it's expecting you to have your answer in a format such as 1|Option 1, with the "machine" or "key" name first, followed by a pipe, and then the name of the option a user will see. If you don't give it a machine name it simply isn't valid.

Here's an example of an Allowed values list you might have.


|- Choose City-
la|Los Angeles
sd|San Diego
sf|San Francisco
sb|Santa Barbara

Please note that for normal options you don't have to have the key value at all. This isn't the best way to do this, as it now gives you a value without a key which can be troublesome later.

Alternatively, you could use hook_nodeapi. For example, let's say you add in your CCK field


choose|-Choose-

Now, in a custom module, in this case called "siteconfig", you could add:


function siteconfig_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
if ($node->type == 'custom_content_type') {
switch ($op) {
case 'validate':
if ($node->field_city[0]['value'] == 'choose') {
form_set_error('field_city', 'Invalid choice, please choose a city.');
}
break;
}
}
}
?>

Diving into Drupal E-Commerce: An Ubercart vs. E-commerce Comparison

While I've built oven a dozen Drupal-based websites spanning almost 2 years, my contact with e-commerce within Drupal has been limited. Two of my latest projects brought me face to face with a need for e-commerce, though, and while I toyed with the idea of using ZenCart once again, after installing it and relearning how to work with it, I immediately uninstalled it and turned back to Drupal.

Faced with two choices, Ubercart and the e-Commerce modules, I was left a bit bewildered. Back when Drupal was still in 4.6, I used e-Commerce for a website where users were rewarded with points via the Userpoints module, and with these points were able to purchase items using E-commerce. It worked well but because it was when I was still very new to Drupal, I remembered it being cumbersome, difficult to configure, and just a big headache, and that was without needing an actual merchant account or payment gateway.

NOTE: These evaluations are based on using Drupal 5.2 with eCommerce 3.3 and Ubercart Alpha7b.

So, for all these reasons when an e-commerce site fell into my lap a couple months ago, I chose Ubercart.

Another project came my way shortly after which used a variation of modules that led me to believe e-Commerce was the way to go, plus I wanted to know how well it held up in comparison - so for that project, I chose e-Commerce.

Having worked with both in overlapping time frames these past few months, I've got a pretty good understanding on how each works, the differences between them, the pros and cons as I see them, and community behind them, yadda yadda. And so here we go, let's jump right in -

The Pros and Cons of e-Commerce and Ubercart

E-Commerce

Pros Cons
Endlessly extensible UI is difficult and confusing
More options for contributed modules Requires a lot of theming
Been around the block longer Feels bloated and cumbersome

Ubercart

Pros Cons
More ready to go out of the box Difficult to customize some areas
Lots of jQuery built in Lots of jQuery built in
Vibrant, active community Still in alpha
User interface is awesome Not as many modules available

What's Available

Payment Gateways e-Commerce Ubercart
2Checkout No Yes
Authorize.net Yes Yes
CCard Yes No
CyberSource No Yes
Eurobill Yes No
Eway Yes No
e-xact Yes No
Fast Transact Direct No Yes
Google Checkout Yes No
iTransact Yes No
Moneris No Yes
Ogone PSP No Yes
Paypal Yes Yes
Viaklix No Yes
Wonderpay No Yes
Worldpay Yes No

Additional Functionality e-Commerce Ubercart
File Downloads Yes Yes (separate contrib)
Donations Yes No
Role Grants/Purchases Yes Yes (separate contrib)
Auctions Yes No

To touch on a few points... Ubercart is strong in that is has a much better user interface, makes more sense to someone not very savvy in the ways of Drupal to manage a store, has a lot of built-in, out of the box features that don't require a lot of customization and frustration, including some cool jQuery usage.

On the same token, that also makes it a bit if a pain to customize in some ways, since you have to strip these things out, overwrite them, etc. E-Commerce gives you what you need, but from there you need to mold it into the desired result.

You may also notice I mention jQuery as both a pro and con for Ubercart. I love jQuery. I've been using it more and more and I'm still amazed at how easy it can be. But, like I said, if you don't want these added features, want to customize the way specific things look and behave, you need to strip them out or overwrite them.

The Community

Community and buzz around projects can be incredibly important. The Ubercart community is active on http://ubercart.org. The guys working on the project answer questions quickly, are friendly, and the community feels vibrant. People are jumping in and writing modules which will possibly bring Ubercart's features up to par with e-Commerce, as well. One thing that has always bothered me, though, is when a project has it's own website and, more specifically, has it's own issue queue. I understand the reasoning behind it, but it tends to make things feel disjointed and confusing.

As for e-Commerce, there are some guys working really hard on it and the issue queue moves quickly with issues being fixed daily. e-Commerce 4 has been released in alpha, which is supposed to be a big improvement and sounds very promising. There's often activity in #drupal-ecommerce, and they have opened http://www.drupalecommerce.org as a more centralized place for communications on the project, raising funds, and building a community. The community doesn't feel as strong here, but appears to be growing and is very positive.

So which is better?

Well, I'm still not sure. There may be no solid answer. If you want to get a site up quickly, Ubercart may be the way to go. If you need something highly customized, then e-Commerce may be the way to go. For many, Ubercart has been the answer to all their shopping cart problems. But in the end, I failed to find anything it actually offered that e-Commerce could not do, it just might require some work to get there. Edited: After my original post I came to realize this is not quite true. There are a few things Ubercart offers that e-Commerce does not. Most importantly, as I see it, is a one page checkout. Ubercart has a really nicely done one page checkout, while e-Commerce's leaves much to be desired.

If nothing else, perhaps having them both will give us two strong options for using e-commerce in Drupal. We'll see how well e-Commerce 4 goes, as well as where Ubercart leads us in a final release. And if all else fails, there's always ZenCart.

Just kidding, of course!

jQuery Tabs: Creating "Continue" and "Previous" Buttons

jQuery is neat, and so is the jQuery tabs plugin. This is a method I used to add "continue" and "previous" buttons to some jQuery tabs, essentially giving it a "wizard-like" look and feel. While I am also using a version of the JS Tools tabs module for Drupal, this should hopefully work for the stand-alone plugin, too.

Despite the tabs being assigned anchor IDs, simply trying to skip to the next one only seems to work in Firefox. Let's break this down and say I have a page with 3 tabs. The tabs are named #section-1, #section-2, and #section-3.

In #section-1, I want a "Continue" button going to #section-2. This is the code I add directly to my tabs page at the bottom of #section-1.

I also added some similar links on #section-2 for a previous AND continue button, like so:

Now I need to add some stuff to the tabs javascript file (in my case, tabs.js).


$function() {
$('

Continue').prependTo('#first-continue').find('a').click(
function() {
$('#container').triggerTab(2);
return false;
});
$('

Continue').prependTo('#second-continue').find('a').click(function() {
$('#container').triggerTab(3);
return false;
});
$('

Previous').prependTo('#first-previous').find('a').click(function() {
$('#container').triggerTab(1);
return false;
});
});

Note that container is the main ID of the whole tabs structure. These names are similar to what you'll find at the jQuery tutorial. If you're using the Drupal JS Tools tabs module, you may need to change this to .drupal-tabs.

That's it. Tested in IE6, IE7, Safari3, and Firefox. You can style the look of them to appear more like buttons using the continue class with CSS.

Creating "Groups" Using CCK, Views, Node Profile and Node Reference in Drupal

Being an avid user of CCK and Views, I decided to try essentially creating "groups" using them and the CCK node reference. This isn't going to be a step by step how-to, but more of a general proof of concept.

The modules used in order to achieve this functionality:

The idea here is we're going to create a content type to act as the group's main page. For instance, let's say we create a CCK called "group". We'll have the title of the group and a description.

Now we need to set up nodeprofile. For more info on nodeprofile and how it works, check out the documentation. There's also a great step by step tutorial on creating a nice user profile with the nodeprofile set of modules. Basically, it allows for users to be treated as nodes. So, let's say we create a node called "user profile". We'll have fields like name, location, picture, etc. We'll also add a CCK node reference to this node, and and check "group" as a reference. Set it as a nodeprofile.

Now, when a user is created and their nodeprofile is created, you can choose which "group" content type to reference the user to.

There's lots of things that can be done from here, but let's assume we want to create a list of all the users associated with a specific group. We would create a view called "group_user_list", add the correct Node Reference as an argument, filter it by the nodeprofile content type, and add it to the "group" content type. In my situation, I embedded the view directly in node-group.tpl.php


global $current_view;
$current_view->args[0]=$node->nid;
$view1 = views_get_view('group_user_list');
print (views_build_view('embed', $view1, $current_view->args, false, false));
?>

You can also create other content types to associate with the group. A CCK event type, blogs, etc. Creating views to add them onto the main group page is pretty straight forward. Each content type will need the same node reference field, and when creating the view, use the node reference field as the argument. You will need to add:


$args[0] = arg(1);
?>

in the "Arguments Handling Code" section of the view in order to get things to display properly. Voila, groups without using organic groups!

Pages

Subscribe to RSS - drupal