Jan 02 2019
Jan 02

A lot of my work over the last few years has been working on migrations between various versions of Drupal. That usually means that I need to configure a local Drupal development environment with more than one database. And although this is relatively easy to do with Lando, I often have to look up how I did it before. So, I figured I should write it down and share with everyone else at the same time.


Adding a database to an existing Lando environment is as easy as adding a few lines to the .lando.yml file and restarting.

    type: mysql

This will create a new container called legacy with a MySQL database in it. Out of the box, Lando supports many common types of DB servers, including: MySQL, MariaDB, MongoDB, MSSQL, and PostgreSQL.

Often, your .lando.yml file might already have configuration in it. If the services line already exists, just put the new configuration underneath with the correct indentation. You can see examples of more complex configuration files at any of the links in the previous paragraph.


Now, you will need to tell Drupal about the new DB. To do this, go to the command line and type lando info. In the output, you should see something like this:

  "legacy": {
    "type": "mysql",
    "version": "5.7",
    "hostnames": [
    "creds": {
      "user": "mysql",
      "password": "password",
      "database": "database"
    "internal_connection": {
      "host": "legacy",
      "port": 3306
    "external_connection": {
      "host": "localhost",
      "port": "not forwarded"
    "urls": []

With that information, you can add the new DB configuration to Drupal's settings.php file.

$databases['old_db']['default'] = array (
  'database' => 'database',
  'username' => 'mysql',
  'password' => 'password',
  'prefix' => '',
  'host' => 'legacy',
  'port' => '3306',
  'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
  'driver' => 'mysql',

Note that, by default the host name is going to correspond to the name of the service/container and will not necessarily be the same as the name of the database (or the name of the Drupal DB alias, for that matter). In other words, you should find the host and port values in Lando's internal_connection array. If, for some reason, you need to have a custom database name, credentials, port numbers or something else, you can refer to the links above.

Related Article(s)

Nov 07 2018
Nov 07

Installing Lando on a Windows machine is easy. Just follow these 30 (or more) simple steps:

  1. Review the directions.
  2. Figure out which version of Windows you are running.
  3. Realize that you need to upgrade to Windows 10 Professional, because apparently you have to pay extra to actually do work on a Windows machine.
  4. Open the Windows Store.
  5. Spend half an hour trying to figure out why the Windows store is only showing blank pages.
  6. Take a break, go vote, spend some time with your kids, and seriously consider buying a Mac so that you don't have to deal with this shit.
  7. Reboot your computer and finally get Windows store to respond.
  8. Pay $100 dollars, while updating your account information because everything is three years out-of-date. Do not pass Go.
  9. Reboot your computer twice.
  10. Go to the Lando releases page.
  11. Spend some time looking for the last stable release (note: there is no spoon stable release).
  12. Download and run the latest .exe.
  13. The installer will complain that you don't have Hyper V, which you just paid for.
  14. Find the obscure command you need to enable Hyper V.
  15. Find Powershell in the Start menu.
  16. Discover that you can paste into PowerShell just by right-clicking your mouse. This seems convenient, but it's a trap!
  17. Run the command. It doesn't work.
  18. Learn how to run PowerShell as an administrator.
  19. Run the command, again.
  20. Reboot your computer, again.
  21. Run the .exe, again.
  22. The installer wants to install Docker. Let it.
  23. The Docker installer wants you to log you out. Let it
  24. Log back in.
  25. Open Babun and try the lando command. It isn't found.
  26. Open Powershell and try the lando command. It isn't found.
  27. Open the Command Prompt and try the lando command. It isn't found.
  28. Re-run the Lando installer, for the third time. It turns out that it never finished because Docker logged you out.
  29. Open Powershell and try the lando command.
  30. It works! Congratulations, you are done!*

* Just kidding...

  1. Open PowerShell. Go to the directory where you have your Drupal site.
  2. Run lando init.
  3. Choose the drupal 7 recipe.
  4. Why is it asking for a Pantheon machine token? This isn't a Pantheon site! Hit Ctrl-C.
  5. Log into Pantheon, create a machine token for your Windows machine. note: Terminus and Lando are notorious for asking for this machine token over and over, so make sure to paste this machine token into a file somewhere, which kind of defeats the entire point of having a machine token.
  6. Run lando init, again.
  7. Right clicking to paste doesn't work for the hidden machine token. So, learn a different way to paste the machine token into PowerShell.
  8. Congratulations, you are done!**

** Just kidding...

  1. Run lando start. Your terminal will proceed to spew error messages for several minutes.
  2. Spend an hour searching through the Lando issue queue trying to find the magic sequence that will fix these errors.
  3. Go apple.com and start comparing the new MacBook Air to the new Mac Mini. Figure out if you can afford either one so that you don't have to deal with this shit.
  4. Your kids are picking up on your frustration, and everyone is melting down because it is bedtime (and your are anxious about the election).
  5. Give up for the night, and obsessively refresh the election results at fivethirtyeight.com until the results are clear at 11:00 PM.
  6. Get up the next morning and write a satirical article about installing Lando on your Windows machine.

I will let you know if I ever actually get it working.

Update November 19, 2018

I was finally able to get Lando working. Here is what I did:

  1. Deleted the "hidden" directory at ~\.lando.
  2. Uninstalled Docker and Lando with the Windows control panel.
  3. Downloaded and ran the latest version of Lando for Windows, which was lando-v3.0.0-rc.1.exe at the time I was writing this.
  4. The Lando installer also installed Docker, but Docker did not ask to log out this time. Also, Docker asked to install a newer version, which I did not allow.
  5. After a sucessful install, I used Powershell to navigate to the directory where I had my Drupal files.
  6. I removed the old version of .lando.yml.
  7. I ran lando init, chose the Drupal 7 recipe, and provided the Pantheon machine token. I knew I was going to need to keep that!
  8. I ran lando start.
  9. During the start, Docker asked for permisson to "share" with the C: drive, which I granted. I don't remember having to do that before, but I might have forgotten.
  10. Also, during the start Windows Defender asked what to do about a Docker sub-program that wanted internet access. I definitely don't remember that. So, I gave it permission on both private and public networks, since I suspect that it was crucial and I am on public networks somewhat regularly.
  11. The command ran cleanly, and the site responded in a browser. So, I am finally done. No kidding

Update January 2, 2019

I continue to have problems when starting Lando, especially when switching from project to project. However, I have found that right-clicking on the Docker icon in the Taskbar and choosing Restart... clears up the errors. So, I recommend trying this first before nuking your entire setup.

Related Article(s)

Jun 26 2013
Jun 26

This post explains how to use Drupal Webform select options to create four different types of HTML code: a drop-down select, radio buttons, a multi-select, or checkboxes.

The presentation

Recently, I did a presentation about the Drupal Webform module. You can see it here:


Or, watch it directly on Youtube here:


During the presentation, I talked about how Select Options can generate four different types of HTML code, and I drew a matrix on the whiteboard. This wasn\'t captured on the video, so I will reproduce it here.

The Matrix

The select options form element has two configurations that can be on or off: Multiple and Listbox. The four possible combinations of these options give you this grid:

Multiple On Multiple Off Listbox On Multi-select Drop-down select Listbox Off Checkboxes Radio buttons

What HTML code is actually generated for these four things? Here is the breakdown.


In most browsers, a multi-select is an open box. Depending on your OS, you either hold down the control button or the command button to select more than one option. If there are a lot of options, the box may have a scroll bar.

Code: <select multiple> … Example:

Drop-down select

Code: <select> … Example:


Radio buttons


So, by configuring the Multiple and Listbox settings on the Webform select options element in four different ways you can create four distinct of HTML tags. If this was helpful, or you have other tips, feel free to leave a comment.

Jan 25 2011
Jan 25

I'm on the intertubes!

Recently, I made two presentations at the Western MA Drupal Camp. One presentation was for people new to Drupal and the other was a very advanced look at the framework for database administrators. I have posted my slides on the camp website. Also, the first presentation was streamed live and is available for viewing. Feel free to take a look at the links below.

Explaining Drupal to your boss: What is it and why should we use it?


How to think in Drupal for DB App Developers

If you would like me to make a presentation for your ogranization, I am available.

May 21 2008
May 21

The setup

Recently, I did some pro-bono consulting work for a client. As part of our agreement I made it clear that I would retain the rights to the code I wrote. The reason I did this is because I wanted to be able to give back to the community, and now I am going to do that. The work was made up of six tasks, ranging from trivial to complex. I intend to write six articles based on these tasks. This is the first.

The environment

The site I was working on was built with Drupal 5. You should already be familiar with developing themes and modules in Drupal to get the most out of these articles. For more information, you should use the resources below:

Many of the tasks required changing settings on the Drupal adminstration pages. Code for the tasks was placed in either a custom module, or the site's theme. For the purposes of my articles, the site will be mysite.com and all the code in the examples would be placed in two folders:

  • sites/all/modules/mysite
  • sites/all/themes/mysite

Again, if you need an introduction to creating custom modules or themes, you should use the links above.

Task One: Restricting access by email domain

The customer only wanted employees from their company to have access to the site. However, they didn't want to have to put every employee in by hand. The solution to these conflicting specifications was that anyone could try to register for the site, but the registration would only accept users with an email address that had the same domain as the customer. In other words, a user could complete registration if their email was [email protected] but not if there email was [email protected].

Step One: Turning off anonymous access

The first thing to do was to turn off anonymous access to the site. This was done through Drupal's administrative interface:

  1. Log in to http://mysite.com/user as the administrator.
  2. Click on “Administer” on the "Navigation" menu.
  3. Click on the “Access control” link under “User management”.
  4. Turn off “access content” under “node module” for "anonymous".
  5. Only "authenticated users" should have permission to “access content”.
  6. Click on the “Save permissions” button on the bottom of the page.

Step Two: Allowing registration for mysite.com employees only

  1. Log in to http://mysite.com/user as the administrator.
  2. Click on “Administer” on the "Navigation" menu.
  3. Click on the “User settings” link under “User management”.
  4. Click on the radio button labeled “Visitor can create accounts and no administrator approval is required.”
  5. Make sure that the check box labeled “Require e-mail verification when a visitor creates an account” is checked.
  6. Add text to the “User registration guidelines:” text area.
  7. Click on the “Save configuration” button at the bottom of the page.
  8. Last, but definitely not least, in order to make sure that only mysite.com employees can register, the function below was added to sites/all/modules/mysite/mysite.module:

function mysite_user($op, &$edit, &$account, $category = null) {
$op) {
      if (
$edit['form_id'] == "user_register") {
        if (!
preg_match("/[email protected]$/i", $edit['mail'])) {
t("Only valid mysite.com email accounts are allowed."));
// end case "validate"
} // end switch($op)
} // end function mysite_user()

This function implements hook_user(). It looks for the "validate" operation and that the form that is being validated is "user_register". Then it checks the email address with a regular expression. If the email address does not end with "mysite.com" it returns the form with an error.


In addition, this code was added to sites/all/themes/mysite/style.css

.messages {
  margin: 0.5em;
  padding: 0.5em;
  border: 1px solid #009900;
  color: #009900;


Registration was tested with invalid and valid email addresses.

More tasks

This concludes the first task. Look for the next article to appear shortly.

Nov 27 2007
Nov 27

Below you will find my description of how I made a new component for Drupal's webform module that dynamically displays a selection of checkboxes. I will go over why I made it. Then I will explain the code step by step. I have also attached a copy of the file for download.

What's my motivation?

When I first approached this part of my project, I looked for examples of code that had already solved a similar problem. Unfortunately, nothing that I found did exactly what I wanted. However, I did find that there was a lot of other people looking for the same thing, so I promised to share my solution.

Please note that this component is just part of a larger registration piece that I built for a client site. It is not as robust as the core webform components, but I hope that others can use it as a framework to build similar components that they need.

The Code

I started by looking at the code for the webform select component and figuring out how I could modify it to do what I wanted. The first piece was fairly obvious. So, I created a new file in the webform/components directory called dynamicselect.inc and added the function below. Anyone who has done any Drupal development will recognize it.

function _webform_help_dynamicselect($section) {
  switch (
$section) {
$output = t("A dynamic list of upcoming events.");


// end function _webform_help_dynamicselect($section)

The form that makes the form

Next, I had to build the form element that would be used by the webform module to set up my dynamicselect element...

Confused? So was I. This is somewhat complicated. Here is what happens:

When you create a webform you have to fill out a form with things like the form's name and description and any other fields you might fill out to create any type of node.

In addition, you have to add the form elements, like textfields and markup. When you add the element, you are redirected to another form that asks about the element's properties, like its key value and whether or not it is mandatory. When you create a new component you have to create the form that asks these questions...

Maybe it would be better if I just showed you the function.

function _webform_edit_dynamicselect($currfield) {
$edit_fields = array();
$edit_fields['value'] = array(
'#type'          => "textfield",
'#title'         => t("Default value"),
'#default_value' => $currfield['default'],
'#description'   => t("The default value of the field.") .
"<br />" . webform_help("webform/helptext#variables"),
'#size'          => 60,
'#maxlength'     => 255,
'#weight'        => 0,


// end function _webform_edit_dynamicselect($currfield)

That's it. Actually, all of this is just cut and pasted from the select component. All I am doing is setting up a field where you can enter a default value for your instance of the dynamicselect component. All the other form items like key, name, description, etc. are added later by the webform module.

Just in case you are wondering, I did not make a _webform_edit_validate_dynamicselect() function because I did not need to do anything beyond the built-in validation.

Rendering the dynamicselect element

Now, I need the code to actually build the dynamicselect element when the webform is displayed.

function _webform_render_dynamicselect($component, $data = false) {
$form_item = array(
'#title'       => htmlspecialchars($component['name'], ENT_QUOTES),
'#required'    => $component['mandatory'],
'#weight'      => $component['weight'],
'#description' => _webform_filtervalues($component['extra']['description']),
'#prefix'      => "<div class='webform-component-" .
$component['type'] . "' id='webform-component-" .
$component['form_key'] . "'>",
'#suffix'      => "</div>",
// set the default value
$default_value = _webform_filtervalues($component['value']);
  if (
$default_value) {
$form_item['#default_value'] = $default_value;
// set the component options
if ($data) { // $data is set
$options = _dynamicselect_display_options($data);
  } else {
// $data is not set
$options = _dynamicselect_load_options();
$form_item['#options'] = $options; // set display as a checkbox set
$form_item['#type'] = "checkboxes";


// end function _webform_render_dynamicselect($component)

Again, almost all of the code above is stripped out of the select component and simplified. If you need to build a different type of select, like a drop-down or radio buttons, you will need to change the code here.

Experienced Drupal developers may have noticed two things. First, I stripped the code that handles multiple default values. This is because I am only ever planning on setting the default value to %get[id]. Obviously, a more modular version of this component would not skip this step.

Second, you may have noticed that I added a second, optional parameter to the function: $data = false. This will make sense later when we display submission results. For now, the important line in the code above is this one.

= _dynamicselect_load_options();

Dynamically loading the options

The _dyanmicselect_load_options() function is what makes this component unique. It dynamically generates a list of options each time the form is loaded. Here it is.

* dynamically load events that are happening in the next 13
* weeks excluding events that are not published or not open
* for registration
function _dynamicselect_load_options() {
$options = array();
$options[-1] = "Other";
$one_quarter = 7 * 13 * 24 * 60 * 60;
$query = "SELECT
FROM {node} n
JOIN {event} e ON n.nid = e.nid
JOIN {content_type_event} c ON n.vid = c.vid
WHERE n.status = 1
AND e.event_start > UNIX_TIMESTAMP()
AND e.event_start < UNIX_TIMESTAMP() +
AND c.field_registration_value = "
ORDER BY e.event_start ASC"
$results = db_query($query);
  while (
$result = db_fetch_array($results)) {
$start = format_date($result['event_start'], "custom", "n/j");
$end = format_date($result['event_end'], "custom", "n/j");
$option = $result['title'] . " $start - $end";
$options[$result['nid']] = $option;
// end while
return $options;


// end function _dynamic_select_load_options()

As you can see this function is unique to my needs. This would need to be re-written if you need to filter your options differently. In addition, you may not want to do everything inside a query. In fact, if someone was really ambitious they could turn this into a "real" webform component by implementing filters and fields, like the views module.

In the end the code above returns an associative array that becomes the checkboxes on the webform. It looks something like this.

  [-1] => Other
  [26] => Fraternity Event 12/7 - 12/9

That's all there is to creating the element. I don't even have to write a custom _webform_submit_dynamicselect() function because I don't need to change the values created by the default form functions.

Displaying the results

A very useful feature of the webform module is the fact that it stores all submissions in the database, and you can look at them in a variety of ways. However when the options are generated dynamically, displaying the submissions becomes more complicated. Here is the function for viewing one single submission.

function _webform_submission_display_dynamicselect($data, $component) { $form_item = _webform_render_dynamicselect($component, $data); // set the selected values as checked, i.e. default
foreach ((array)$data['value'] as $value) {
    if (
$value) {
$form_item['#default_value'][] = $value;
$form_item['#attributes'] = array("disabled" => "disabled");




// function _webform_submission_display_dynamicselect()

Again, most of this is just a simplified version of what is done in the select component, but, as you can see, this is where the second argument, $data, for _webform_render_dynamicselect() is used. This causes the rendering function to switch tracks when building the options.

if ($data) { // $data is set
$options = _dynamicselect_display_options($data);
  } else {
// $data is not set
$options = _dynamicselect_load_options();

There are several reasons that I want to display the options differently when I am showing submission results. First, I only want to show the events that were selected, not every open event in the next 13 weeks. Especially since I might be viewing the submission after the start date has passed or the event has been closed for registration. Also, I need to handle invalid data that might have gotten into the database, including events that have been deleted. Here is how I dealt with that.

function _dynamicselect_display_options($data) {
$options = array();
  foreach (
$data['value'] as $key => $val) {
    if (
$val == -1) {
$options[-1] = "Other";
    } else if (
$val && ctype_digit((string)$val)) {
$event_node = node_load($val);
      if (
$event_node->type == "event") {
$start = format_date($event_node->event_start, "custom", "n/j/Y");
$end = format_date($event_node->event_end, "custom", "n/j/Y");
$option = $event_node->title . " $start - $end";
$options[$val] = $option;
      } else {
// deal with deleted events
$options[$val] = "non-event id: $val";
    } else if (
$val) { // deal with invalid values
$options[] = "invalid value: " . check_plain($val);
// end function _dynamicselect_display_options($data)

Of course, there are other ways to display the submissions, including in the analysis tab, as a CSV export, in a table, and last but definitely not least, the submission can be sent as an email. Each of these has to be handled in a similar fashion to the function above. I will not include each of those functions here, but I will include them in the attachment below...

...if I ever finish writing them :)


Below you will find a ZIP archive of the component I have written. The archive includes the original directory structure in case you are confused about where to put this.

Also, the code in the archive may be different than the code shown above. When in doubt, follow the code in the archive and assume that I had a good reason for any changes that I made.

Nov 19 2007
Nov 19

Ever since I started making custom themes for Drupal, I kept running into a strange problem. If my theme or my content included a table, when I looked at it in Firefox the table would have a thin grey line on the top. I finally figured out what was causing this and I would like to share it with you.

If you want to skip the lecture, you can click here for the solution.

As you may already know, the HTML code that is initially sent to the browser is not always the same as the final code that is used to show the page to the end user. The code you send out can be changed by elements you control, like JavaScript, and it can be changed by elements out of your control, like browser plug-ins.

One of the elements that is mostly out of your control is the browser rendering engine. The rendering engine takes the code it is given and "cleans it up" for presentation. For instance if the browser is given bad code, it will apply a series of rules to try and recover from the error and make decisions about what to show and in what order.

One of the ways the rendering engine tries to clean up is to include missing elements. This can be useful and usually does not affect the final appearance of the page. For instance, it used to be acceptable to not close certain HTML tags. Leaving out the closing tags left it up to the browser to figure out where the ending tag was supposed to go. Take a look at the code below.

  <li>This is the first item.
  <li>This is the second item.

This was passable code, and the browser would take it and change it in to this.

  <li>This is the first item.</li>
  <li>This is the second item.</li>

Although this seems convenient, it often led to code that looked like this.

<p>Please look at the following chart.
        <p>Number of beans
      <td> 3

As you can see, even with indenting this code is difficult to read and may not be interpreted by the browser as the writer intended. Fortunately, the XHTML standard stops this by requiring that all tags are closed properly.

Anyway, let's get back to my issue. The mysterious grey line was appearing at the top of tables. I assumed that it was caused by one of the many CSS files that were automatically included by Drupal. So I took a look at my source.

Source View

Then I started checking every style that could possibly be causing the problem. Unfortunately, I didn't find any that were working on the HTML I had written. It turns out this was because the element that was being styled was not in my code. This all became clear when I opened up Firebug.

Firebug is an excellent debugging tool that lets you browse through your page and see tags, scripts, styles, errors and more all in one place. It is essential for any web developer. One of the things that Firebug does is that it shows you what your code looks like after it has been through the Firefox rendering engine. Here is what that looked like.

Firebug View

The Solution

Aha! Now the problem becomes clear. The rendering engine had added "missing" tags and, sure enough, in Drupal's modules/system/system.css there is this code.

tbody {
  border-top: 1px solid #ccc;

Finally, adding the code below solved the problem for me.

#main_content tbody {
  border-top: none;

I hope that you found this useful and that it saved you from the frustration that I experienced. Check my site regularly for tips like this.

Sep 18 2007
Sep 18

Update 2008-08-22: This page has received a large amount of traffic from people looking for implementations of the Blueprint CSS theme project. Here are some links to help out those people:

Original content: I finally moved away from the generic Drupal "garland" template today. This new "blueprint" design started as an outline for something much more complicated, but I liked it so much I decided to put it up. I plan to make subtle alterations to the template over time, so look forward to little surprises.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web