# Lullabot
> Strategy, design, and Drupal development for large-scale publishers.
---
title: "Newsletter Confirmation"
url: "/newsletter-confirmation"
type: page
date: 2015-06-23
updated: 2015-12-22
---
# Newsletter Confirmation
# Newsletter Confirmation
Thank you for signing up for our newsletter!
---
---
title: "Mission & Core Values"
url: "/values"
type: page
date: 2015-06-23
updated: 2022-09-01
---
# Mission & Core Values
# Mission & Core Values
## Mission
We aspire to create smart, delightful, and rewarding experiences with interactive technologies and to empower and inspire others to do the same.
## Core Values
Our core values are what we're all about. Let us walk you through them.
### Inspire & Empower
We inspire and empower those around us. We choose to be mentally engaged and invested rather than distant or simply there. We extend respect and responsibility to everyone and lift each other up at every opportunity. We share knowledge freely and support each other to collectively reach our fullest potential.
### Be Human
Though we may do technical, digital work, we know that it's the human connections that bring our work to life. We value our humanity in all its diversity. We speak and conduct our work in ways that include and celebrate others. We recognize that all of us make mistakes, and we all can learn and grow. We strive to be honest, humane, friendly, caring, and humble.
### Own It
We are employee owners who take great care to produce things that reduce frustrations and improve teams, workflows, businesses, and the lives of real people. We set big goals for ourselves and support and encourage one another to accomplish them. We strive to do excellent, successful, important, and rewarding work that we can all be proud of.
### Invent & Innovate
We are creative problem solvers. We constantly question which problems need to be solved and brainstorm ways to solve them. Weâre resourceful. We find joy in novel ideas and new inventions, but weâre just as satisfied to follow a well-worn path. Ingenuity is about harnessing creativity and insight to identify the *best* solution, not the most unique one.
### Collaborate Openly
We believe collaboration will allow us to accomplish more than chipping away individually. We work better and faster together. A singular vision can be powerful, but when we openly share our knowledge, efforts, and work, we get back more than we give. We seek different perspectives and backgrounds, avoid silos, and strive to give and receive insights with gratitude and an open mind.
### Have Fun
There's no need to sacrifice happiness for success. We believe that happiness, fun, and inclusive amusement bring an energy that fuels creativity, creates a more open and communicative workplace, and makes for a more relaxed, productive, and dedicated team. Itâs also just more fun. Tada!
---
---
title: "Benefits"
url: "/jobs/benefits"
type: page
date: 2015-06-23
updated: 2021-03-08
---
# Benefits
# Benefits
## Health Insurance
At Lullabot, we offer employees comprehensive health, vision, and dental insurance, covering 75% of the cost. We also cover 75% for families and spouses. In addition, we offer multiple options like PPO, HSA, and FSA insurance so that you can make your own choices about the type of healthcare you use.
## Paid Time Off (PTO)
Use these days however you like. We donât differentiate between sick days and vacation days. If you want a day off, request a day off. We think thatâs your business. We call this PTO.
During your first 2 years of service, employees are eligible for 15 PTO days. After 2 years of service, employees are eligible for 20 PTO days, after 7 years, employees get 25 PTO days, and after 10 years at Lullabot, employees also get a 4 week paid Sabbatical.
## Employee Ownership
Our success is defined by the contributions of our team. As a 100% employee-owned company, employees receive the benefit of ownership and a personal connection to the health and well-being of Lullabot. It also means you'll get to share in the long-term success of the company. Lullabot is an [ESOP](https://www.esopassociation.org/what-is-an-esop) (Employee Stock Ownership Plan) company, which means the shares are held in a retirement account to avoid the tax implications that typically come with ownership.
## Retirement Plans
We want to help our employees prepare for retirement, so our plans include an employer match of up to 4% of salaries, vested immediately. If you choose to invest 6% of your salary, combined with our 4% match, youâll be saving 10% of your income towards retirement.
## Life Insurance
Lullabot purchases and pays for a $50,000 life insurance policy for each of our full-time employees, and gives you the option to purchase more coverage at a discounted rate.
## Short/Long Term Disability Coverage & Workerâs Compensation
Lullabot also covers each full-time employee with both disability and workerâs compensation policies. This helps ensure that if you ever miss work for an injury/illness, you will receive some type of compensation.
## Other Benefits
### Tech Stipend
Lullabot gives employees a generous stipend to spend on expenses like computers, phones, software, or to pay your phone/internet bill. We like our people to stay up to date with technology. Items purchased are yours to keep.
### Event & Education Budget
Each employee has $2,750 per calendar year to spend on Professional Development. This includes conferences, education, books, etc.
### Donation match
When you donate money to a qualified charity (501C3), Lullabot will make a matching gift of up to $200 per year.
### Parental Leave
Parental leave is available for both the birth of your own child and the placement of a child due to adoption or fostering with the intent to adopt (concurrent planning). Full-time employees who have been with the company for six months or more are eligible for six weeks time off, fully paid and an additional six weeks time off, unpaid.
### TripIt Pro
Each employee who would like a TripIt Pro account is gifted one and renewed every year. As a jetsetter, this is an invaluable tool.
### Ergonomic Evaluations
Upon request, Lullabot will provide a professional ergonomic evaluation of your home workspace, to ensure you're working in a way that promotes your long-term health.
### Fitness Club
You will receive a stipend to purchase your own fitness tracker if you want it and participate in our âFitness Clubâ complete with challenges and encouragement.
### Noteworthy
- 40-hour workweeks
- No commute: work from wherever you please
- Work with awesome people to do awesome things
- We play games and celebrate birthdays and hiring anniversaries
Have further questions about a particular benefit? Feel free to contact us at
";
$regex = "/()(.*)()/i";
$spanned = preg_replace($regex, '$1$2$3', $html);
```
1. The regular expression looks for a header tag (h1 through h6): `()`. This will become `$1` in the replacement string.
2. Next, it grabs any text within the header tag: that's `(.*)`, which corresponds to `$2`.
3. Third, it find the closing header tag (again, h1 through h6): `(<\/h[1-6]>)`, which corresponds to `$3`.
4. And finally, I included the `i` at the end for case-insensitivity, in case the HTML contains `
` instead of `
`.
The replacement string is pretty simple: it just pieces the header back together. `$1` is the opening tag, then an opening span, `$2` is the text of the header tag, the closing span, then `$3` is the closing tag.
Now, with all that in mind, you might expect the output to look like this:
```
First Header
Some text.
Second Header
```
But you would be wrong, just like I was wrong. What you would actually get is this:
```
First Header
Some text.
Second Header
```
The opening and closing span get split up across the string. Every time I needed to use a regular expression for something, this would happen, and I would curse under my breath a little bit.
The problem here is that the middle match, the `(.*)`, is "greedy." It just keeps matching characters up until the last place that the third part, `(<\/h[1-6]>)`, will match. Because, remember, that will match on `
` and ``, and it's not smart enough to make sure that the number in the closing tag matches the number in the opening tag (if there's a way to do *that*, I haven't found it yet). So, the regular expression matches the first opening tag, and the last closing tag, and helpfully wraps everything in between in a `span` tag. It sees our HTML string as containing only a single match.
The good news is that this is easy to fix. Like the `i` I tacked on there for case insensitivity, I can also tack on a `U` to make the regular expression "ungreedy," like so:
```php
$html = "
First Header
Some text.
Second Header
";
$regex = "/()(.*)()/iU";
$spanned = preg_replace($regex, '$1$2$3', $html);
```
The only change here is the addition of the U at the end of the `$regex` variable. With that in place, the regular expression will find two matches in the HTML, and I finally get what I wanted all along:
```
First Header
Some text.
Second Header
```
The `U` modifier works for the entire regular expression, so it's good to use if you want your entire expression to be ungreedy. Just today, I learned from esteemed Lullabot [James Sansbury](https://www.lullabot.com/who-we-are/james-sansbury) that you can also be more specific about greediness by adding a `?` after a `*` or `+` to make that wildcard ungreedy. In our example, it would look like this:
```php
$regex = "/()(.*?)()/i";
```
Placing the `?` after the `.*`, I get the same result as I did when using the `U` modifier at the end. In this case, I'm only using a single `*` in my regular expression; if I had more than that one, I might want to use this method instead of the global modifier.
You can learn more about how `i`, `U`, and other regex modifiers work in the [Pattern Modifiers documentation on php.net](https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php). There's also a handy tool called RegExr that will visualize the string as it's matched by a regular expression. Check out [the original, greedy regex](https://regexr.com/?35egs) as compared to the [revised, non-greedy alternative](https://regexr.com/?35egv).
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "AJAX form builders"
url: "/articles/ajax-form-builders"
type: article
date: 2006-03-01
updated: 2014-05-14
---
# AJAX form builders
# AJAX form builders
By
[ Jeff Robbins ](/about/jeff-robbins)
March 1, 2006
Ajaxian.com has a [roundup of ajax-based form builders](https://www.techtarget.com/). I've been thinking about what it would take to make a drag-and-drop form builder for Drupal and these implementations make things look promising.
Imagine using [this interface](https://www.jotform.com/) to create Content Creation Kit (CCK) node types. Drag and drop Drupal site creation. Awesome!
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "The Drupal Song - Remix Tracks"
url: "/articles/the-drupal-song-remix-tracks"
type: article
date: 2007-03-29
updated: 2014-05-14
---
# The Drupal Song - Remix Tracks
# The Drupal Song - Remix Tracks
By
[ Jeff Robbins ](/about/jeff-robbins)
March 29, 2007
Several people have expressed interest in remixing [The Drupal Song](https://www.lullabot.com/podcasts/drupalizeme-podcast/the-drupal-song). And since we're licensing this song under the [GPL](http://www.gnu.org/copyleft/gpl.html), we'd like to release the "source" of the song. Attached here is a [zip file](https://www.lullabot.com/files/DrupalSong-stems.zip) containing high-quality stereo stem submix MP3s for each of the following:
- bass
- drums
- mallets (marimba, glockenspiel)
- piano
- voc - backups
- voc - lead
Update: The song is at **88 BPM** and the tracks all start on the "4" beat. So if you line them up at measure 2, beat 4, the song will start on measure 3, beat 1.
[Download here](https://www.lullabot.com/files/DrupalSong-stems.zip)
Load 'em up into your favorite (re)mixing software and go to town!
Post comments with links to your work.
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Designing \"in the Browser\""
url: "/articles/designing-in-the-browser"
type: article
date: 2012-06-01
updated: 2016-03-30
---
# Designing "in the Browser"
# Designing "in the Browser"
Is it for me?
By
[ Jared Ponchot ](/about/jared-ponchot)
June 1, 2012
If you're like me, and visual, canvas-based tools like Photoshop are still a part of your design workflow, I hope to assuage any fears that you're a troglodyte. If you're one of those fancy pants people who doesn't touch anything that's not a text editor in your design workflow, I hope to hang a question mark on your process.
> "In all affairs itâs a healthy thing now and then to hang a question mark on the things you have long taken for granted." â BERTRAND RUSSELL
I recently attended [A Web Afternoon](https://jokerfly88.com/tag/detective/), which is a fantastic little event here in Atlanta that brings in great designers and technologists from inside and outside Atlanta to speak about the web, design, and emerging technologies. It's a great event and I'm grateful to J. Cornelius and the others that put it on. J. and others like him help make the web design community here in Atlanta active and fun. There were a number of interesting and helpful talks at this year's Web Afternoon. There were talks on business, freelancing, social media strategy, women in tech, and some that were more design-focused as well.
There was one talk by [Divya Manian](http://nimbupani.com) that provoked me a bit. Divya works for Adobe's web platform team now and was formerly working for Opera. She's one of the people behind [HTML5please](https://html5please.com/) and [HTML5boilerplate](https://html5boilerplate.com/), among other things, and an all around smart person and fun to listen to. Divya's talk was about "designing in the browser", and was largely focused on "why mockups suck". Now, I should say I've had thoughts on both sides of this issue for several years, and still find talks like this intriguing.
I've heard these arguments ad infinitum and I completely understand the limitations and weaknesses of high fidelity "mockups" created in a tool like Photoshop. As far back as 2008 I was reading the 37signals team talking about [how they'd ditched Photoshop and went straight to HTML/CSS](https://signalvnoise.com/posts/1061-why-we-skip-photoshop), and they made some compelling arguments. In case you've managed to avoid that conversation entirely, here's a list, handpicked and paraphrased by me from a variety of sources, of some of the most compelling arguments against designing website mockups in Photoshop:
- Photoshop lacks tools for truly flowing type and floating elements within type.
- Photoshop (at least prior to CS6) lacks the concept of reusable "styles" and therefore forces a great deal of re-work.
- Photoshop provides a fixed canvas to work in and you're designing for a canvas that is not fixed.
- Photoshop lacks any concept of pages and states of elements (this isn't entirely true), and leads to designers who don't really think about an interactive interface.
- Clients looking at Photoshop mockups for approval are approving something that can't be identical to the real, finished product. Fonts are one of several things that simply don't render the same in Photoshop as they do within browsers.
- Photoshop mockups are responsible for the concept of "pixel perfect design" being forced upon a highly-flexible medium.
- Photoshop allows visual designers who have no knowledge of markup or CSS to "blue sky" in ways that are often not well suited to the medium they're designing for.
In spite of these arguments, designers like me (and perhaps you) continue to use tools like Photoshop within our workflows. Here are just a few reasons why.
## The design process is much broader than any one tool or discipline
To me, suggesting that good design for web can only be done "within the browser" is akin to suggesting that good architectural design can only be done with 2x4's. On the flip side, suggesting that a web designer doesn't need to know and understand rudimentary things like HTML markup and CSS to design well for browsers is also like suggesting an architect need not know anything about how buildings get built in order to design one. Here be dragons!
I think the key point is that mockups and markup are just one small part of a broader design process. This is a process that should begin not in mockups or markup, and that often ends in something more complex and varied than simple markup as well. Many websites today are services with multiple interfaces, not all of which are built on markup or rendered in browsers. I'll soon be publishing articles on various aspects of our design process here at Lullabot, so I'll save more for another day on that.
## Design drives technology more than technology drives design
While there may be exceptions to this claim, to a large degree, browser improvements have adapted technology to design, and not the other way around. Here's an elementary example. If designers had historically ONLY used CSS to design websites, and no designer had ever used a visual tool that allowed for things like gradients to be created, it's somewhat unlikely that we'd have gradients within the current CSS spec. In the words of the late Steve Jobs, "a lot of times, people don't know what they want until you show it to them."
## Style and beauty come from people, not computers
Markup is about structure, CSS is about style. Style comes from the designer, not from the code. I encourage designers to use whatever tool suits them best to get them thinking visually, especially when in the styling phase of a project. As I mentioned before, our web design process at Lullabot begins well before we even think about opening Photoshop, and early on, is focused on understanding the root problems we're solving, the users we're solving them for, and the patterns by which similar problems have been solved in the web, software and elsewhere. By the time production of visual style comes into the process an enormous amount of work has already been done, none of which happens in Photoshop!
## You can have it both ways!
Now that I've defended the use of Photoshop in your workflow, I should mention that we've engineered our process at Lullabot with the goal of producing as few Photoshop mocks as possible! We produce [style tiles](https://styletil.es/) that are fully markup, though we create assets for them in Photoshop (and have a Photoshop template for doing so). These style tiles help define a direction for the fundamentals of style that often save us from producing a visual mock for every last corner of a website. Once a general aesthetic has been landed on, often from a mock of a single page of the site, we then begin implementing that visual style in markup and CSS and only use Photoshop along the way when we need to create specific elements or get back to thinking visually for a unique visual problem. Recent projects we've worked on have had only one or two static visual mocks for an entire large scale website. Yay! I promise, in my next article I'll begin to layout in more detail our design process here at Lullabot.
## In Conclusion
As web designers, we work in a vibrant industry filled with lots of ideas and many who are great at voicing strong opinions. At least for me, it's easy to get sucked into trying to get everything right and wind up focusing on the wrong things. We all want to be great (or, I hope we do). I would posit that if you want to be a great web designer, focusing on the specific tools you use within your workflow is really not that helpful. Rather, focusing on how to uncover problems, how to understand users, how the technology of your medium works, how to discover and emulate beauty, and how to enjoy your work are going to be far more beneficial to your long term success! So go make something amazing, and use Photoshop if you want to. :-)
Published in:
- [ UX & Design ](/topics/design-and-ux)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Command Line presentation"
url: "/articles/command-line-presentation"
type: article
date: 2010-09-06
updated: 2014-05-14
---
# Command Line presentation
# Command Line presentation
Drupalcon Copenhagen slides and a video!
By
[ Addison Berry ](/about/addison-berry)
September 6, 2010
I had a great time at Drupalcon Copenhagen! Thanks to everyone who made it happen. I did one presentation this time around, "[The Command Line is your friend](http://cph2010.drupal.org/sessions/command-line-your-friend)." It covered the basic commands for getting around and doing things, most of which are covered in more detail in the Command Line Basics video series. One thing that was new and that I ended up not having time to get to in Copenhagen was showing how to install Drupal from the command line. A number of people expressed interest in seeing that part, so I promised I'd make a video of it, and now I've gotten it done. I'm attaching the slides from the presentation here as well, so please have some fun playing around on the command line.
- [Archive.org video of the live presentation](http://www.archive.org/details/TheCommandLineIsYourFriend)
- [My slides as a PDF](https://www.lullabot.com/sites/lullabot.com/files/cphDrupalcon-CLI.pdf)
- The new [Command Line Basics: Install Drupal](https://www.lullabot.com/articles/command-line-basics-install-drupal) video
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Yonder: The Distributed Teams Conference"
url: "/articles/yonder-the-distributed-teams-conference"
type: article
date: 2014-04-17
updated: 2014-05-14
---
# Yonder: The Distributed Teams Conference
# Yonder: The Distributed Teams Conference
By
[ Jeff Robbins ](/about/jeff-robbins)
April 17, 2014
If you have a web site, you've probably worked with someone who works from home. Every day, more and more people â and companies! â are leaving the office lifestyle behind. Whether you call it "distributed," "remote" or "virtual," itâs clear that the trend is taking off.
But, where do business leaders running distributed companies go to find information and share advice? Books like [Remote](https://www.amazon.com/exec/obidos/ASIN/0804137501/orbit0b-20) and [The Year Without Pants](https://www.amazon.com/exec/obidos/ASIN/1118660633/orbit0b-20) have hit the market, but sometimes thereâs just no substitute for getting together face-to-face with your peers to talk it out. With that in mind, we hosted [Yonder](http://yonder.io) â a two-day invite-only event for leaders of distributed companies to come together and meet their peers. Here's a look at who came to San Diego for Yonder in January, and some of the things we talked about.
Inspired? If youâre running a distributed team and are interested in staying in the loop with our plans for the next Yonder, you can sign up for [email updates](http://lullabot.list-manage.com/subscribe/post?u=579cc4bca784b8844042fea50&id=7a64ff23fe). We're also covering topics from Yonder on our blog â [check out the series here](https://www.lullabot.com/blog/tags/yonder)!
Published in:
- [ Business ](/topics/business)
- [ Community ](/topics/community)
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "DrupalCon Denver 2012 Wrap-up"
url: "/articles/drupalcon-denver-2012-wrapup"
type: article
date: 2012-05-04
updated: 2016-04-07
---
# DrupalCon Denver 2012 Wrap-up
# DrupalCon Denver 2012 Wrap-up
A 'Bot's Eye View
By
[ Lullabot ](/about/lullabot)
May 4, 2012
Another great DrupalCon has come and gone. We ran into some old friends, made some new ones, learned a lot, and had fun the entire time. Here are some of the highlights, from our perspective.
### Lullabots Front and Center
Not only did all but 3 of Lullabot's employees attend DrupalCon, but 9 of us presented at sessions, some of us led sold-out pre-con training classes, and CEO Jeff Robbins was on the Day Stage twice: once to take part in the Drupal Game show, hosted by our friends at [Four Kitchens](https://www.fourkitchens.com/) on Wednesday, and again to discuss [Videola](http://videola.tv/) on Thursday.
Weâve received good feedback on our sessions, and thank everyone who attended (so many good presentations were happening simultaneously!). Some of the highlights included:
- **James Sansbury** presented with the Martha Stewart digital group to share how we migrated their site to Drupal. ([Session video](http://denver2012.drupal.org/program/sessions/changing-tires-60-mph-how-martha-stewart-living-migrated-drupal))
- **Nate Haug** officially unveiled [Webform.com](http://webform.com). ([Session video](http://denver2012.drupal.org/program/sessions/webform-survey-tool-drupal))
- Lullabot President **Matt Westgate** talked about founding and growing a successful distributed company, and [CMS Wire published a nice review of Mattâs talk](http://www.cmswire.com/cms/social-business/how-to-grow-a-virtual-company-drupalcon-014925.php). ([Session video](http://denver2012.drupal.org/program/sessions/growing-virtual-company-maintaining-team-moxie))
- Lullabot Creative Director **Jared Ponchot** presented about Designing For Content Management Systems. ([Session video](http://denver2012.drupal.org/program/sessions/designing-content-management-systems))
### Proud Sponsors of DrupalCon
Every year the sponsor expo is bigger and better. We wanted to stand out from the crowd, but also have a fun, friendly, and welcoming presence. Thus, the âLullabot Loungeâ and âDrupalize.Me Live!â were born.
The Lullabot booth, envisioned and designed by Jared Ponchot, was a great place to hang out and talk and we spent a lot of time doing just that. We got to reconnect with Drupal friends and clients and meet lots of new people. We brought hundreds of Lullabot t-shirts along with us and much to both our delight and our sorrow, they were all given away in the first 3 or 4 hours. So if you got one of those t-shirts, wear it proudly! Your friends are bound to be jealous.

Over at the the Drupalize.Me booth, the coveted item was our [sparkly glitter pony sticker](http://twitpic.com/8srxbx). Even the most jaded web programmersâ eyes lit up as we handed them a sparkly sticker. They were a huge hit. We saw many [creative uses](https://x.com/) for the stickers throughout the week, and were thrilled that most attendees were as excited about the stickers as we were! And if you passed by the booth on Wednesday, you may have caught one of the âDrupalize.Me Live!â presentations. Senior Developer and Lead Trainer Joe Shindelar conceived the idea and coordinated the show, which featured costumes, rapping, a puppet, and a trainer in his boxers. When we say that you can learn Drupal in your underwear with [Drupalize.Me](https://drupalize.me/), we mean it.
### âAinât No Party Like a Lullabot Partyâ \*
If you were one of the lucky folks to grab a diskette invitation (or you were just cool enough to show up without one), you were witness to the [temporary tattoo and faux hirsute goodness that was EatonCon](https://www.facebook.com/media/set/?set=a.10150713973766473.415853.22921791472&type=3). On Wednesday night we took over the back room of Rock Bottom Restaurant and Brewery for our annual DrupalCon party.

The more bacon-infused bourbon that flowed, the more creative people got with their fake mustache placement. Coincidence? We think not. Everyone made it home safely, but there was at least [one casualty found on the sidewalk the next morning](https://lockerz.com/sanrio-clothing-outfit-aesthetic/).
\*Quote from an actual tweet from the event.
### Next, Munich; Then, Portland!
The [Drupalize.Me](https://drupalize.me/) team will be bopping about [DrupalCon Munich](http://munich2012.drupal.org/) in August. Theyâll have pockets full of coupon codes, and if youâre nice to them, you might just get a special limited-edition Munich pony sticker (stay tuned for a sneak peek soon).
And weâre already putting a bird on our plans for [DrupalCon Portland in 2013](http://portland2013.drupal.org/).
Want to keep reminiscing? Check out [Lullabot Podcast #102](https://www.lullabot.com/podcasts/podcast-102-drupalcon-denver-wrapup) to hear more about our take on DrupalCon Denver.
Big thanks to the Drupal Association, the Denver DrupalCon team, and everyone involved in making DrupalCon Denver a success!
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Tales from the D7 Front"
url: "/articles/tales-from-the-d7-front"
type: article
date: 2011-04-14
updated: 2014-05-14
---
# Tales from the D7 Front
# Tales from the D7 Front
Tips and tricks from upgrading several sites from D6 to D7
By
[ Karen Stevenson ](/about/karen-stevenson)
April 14, 2011
I wanted to experiment with the D6->D7 upgrade path to see how well the CCK data updates are working, so I was looking around for some real sites to upgrade. Like many of you, I maintain a bunch of 'friends and family' sites in addition to my 'day' job. Usually those sites are the last ones upgraded, but it occurred to me that they would be good fodder for doing some testing of the site upgrade process. I've been upgrading them, rolling back, and re-upgrading them over and over and I found a number of tips and tricks I thought I would share.
## Clean House First
You know how when you move you always are amazed at the amount of junk you forgot you had lying around? Your site probably has some junk in it, especially if it is a few years old. These sites date back to Drupal 4.7 and include the results of a lot of early experimentation where I was installing and then removing lots of modules just to see what they would do. All that cruft is still in my databases. Time to clean it out.
First thing to check is whether there are any modules I never used, or previously disabled but never uninstalled. Disabling unused modules gets them off the list of things I need to worry about. Taking the extra step of uninstalling them will often clean a bunch of cruft out -- database tables and variables that they used that I no longer need.
But that won't necessarily find all the extra tables, especially if I installed a module and then removed it without uninstalling it, or used some modules that didn't properly clean up after themselves. I downloaded and installed the [Schema module](http://drupal.org/project/schema) to help me find database tables that I no longer use. It provides a handy report that compares the schema of my installed modules with the ones actually in my database, to find things like those Aggregator tables that I don't need any more.
Any place I'm using [Features](http://drupal.org/project/features) in D6 that define content types, I need to do one more thing before I upgrade. I need to go into the node\_types table and find any content types that have the module name of 'features' and change it back to 'node'. Without this step, when I disable the Features module all those content types will be dropped and will no longer be available in D7. Once everything is ported to D7 I will have to re-create the D7 feature from scratch, there currently is no upgrade path for the Feature itself, but my data will be fine.
Fields are in core in D7, so I won't need [CCK's](http://drupal.org/project/cck) Text and Number modules, or modules like [File Field](http://drupal.org/project/filefield) and [Image Field](http://drupal.org/project/imagefield). I don't want to actually uninstall any of them though. If I uninstall them my fields will be marked inactive and my data deleted, and I need that data for the field upgrade.
## Identify Changed Module Needs
Next I need to think about the modules I used in D6 that I won't need any more in D7, and new ones I will need that I didn't use before. One example there is the [Admin Menu module](http://drupal.org/project/admin_menu). D7 has a nice built-in Toolbar module that I'm going to use. So I want to disable and totally uninstall the Admin Menu in D6 before I do my upgrade, so that it will clean all the Admin Menu items out of the menu tables. I used [Modal Framework](http://drupal.org/project/modalframe) in D6, but won't need it in D7 with the built-in Overlay module, so I'll uninstall it, too. I won't need [jQuery UI](http://drupal.org/project/jquery_ui) or [jQuery Update](http://drupal.org/project/jquery_uupdate) now that jQuery UI is in core, so I can get rid of them.
I have numerous modules enabled in D6 that define CCK fields that I won't need in D7. I will still need the D7 version of CCK for the Content Migrate module which will upgrade my field data from the CCK format to the format now used by core. In D7 I will also need:
- [Field Group](http://drupal.org/project/field_group) (for what used to be the CCK Field Group module)
- [References](http://drupal.org/project/references) (for what used to be Nodereference and Userreference)
- [Field Collection](http://drupal.org/project/field_collection) (for the Multigroup feature in CCK 6.3)
In D7 I could rely on the core file and image handling and not add any contrib modules, but I plan to use the new [Media module](http://drupal.org/project/media) and its relatives. That whole collection of modules can provide image galleries, image plugins for WYSIWYG editors, and a lot of other nice features. So I have a list of modules that I used in D6 that I won't need any more and another list of modules I will use in D7:
Out With the D6 Media Modules:
- [Filefield](http://drupal.org/project/filefield)
- [Imagefield](http://drupal.org/project/imagefield)
- [Imagecache](http://drupal.org/project/imagecache)
- [Image API](http://drupal.org/project/imageapi)
- [Filefield Paths](http://drupal.org/project/fieldfield_paths)
- [Filefield Sources](http://drupal.org/project/filefield_sources)
- [Insert](http://drupal.org/project/insert)
- [IMCE](http://drupal.org/project/IMCE)
In With the D7 Media Modules:
- [Media](http://drupal.org/project/media)
- [Media Browser Plus](http://drupal.org/project/media_browser_plus)
- [Media Gallery](http://drupal.org/project/media_gallery)
- [Media Element](http://drupal.org/project/mediaelement)
- [Styles](http://drupal.org/project/styles)
- [Plupload](http://drupal.org/project/plupload)
- [Multiform](http://drupal.org/project/multiform)
I will also need a couple other modules that weren't required in D6:
- [Entity](http://drupal.org/project/entity) (required by Field Collection and Media modules)
- [CTools](http://drupal.org/project/ctools) (required by the D7 version of Views)
## Do the Upgrade
Now I'm ready to perform the upgrade. I use the [Backup and Migrate module](http://drupal.org/project/backup_migrate) to easily create a backup of my D6 database before I start. I have to create an empty database for the D7 version of the site and a folder on my directory where I can put the new site. I won't re-use the old location and old database, I want to leave them pristine so I can go back and re-use them if necessary. And I'm doing this NOT on production but locally so I can be sure everything works before I do anything to my live site.
There are several ways to actually do the upgrade. I can do it manually, or I can use [Drush](http://drupal.org/project/drush) to make it much easier. I'll describe both approaches.
### Upgrade Manually
For the manual upgrade I have to download and unzip a copy of Drupal 7 into my new site folder. Then I need to copy the D6 version of the settings.php file into the new directory. The only thing I need to change is the name of the database, if I'm using a different one for the D7 version. Other than that I don't make any other changes, the upgrade process will alter it as necessary for D7.
The easiest, best way to to the upgrade is to start with just the core code, no contrib modules. So navigate to the D7 folder and run update.php on only core. The official upgrade instructions say to disable all contrib modules before you upgrade, which can take a while, especially if you have lots of module dependencies that prevent you from disabling a module until all its dependencies are disabled. Running update.php without any contrib modules in the modules folder will basically accomplish the same goal -- which is to keep any contrib module updates from running until I'm ready for them.
Then I have to find, download, and unzip D7 versions of all my contrib modules into my sites/all/modules folder. That means looking each one up to find the right D7 version of the code. This step is really time-consuming. Part of the reason for cleaning house first is to avoid doing this for modules I don't even need.
Once I have added all the contrib modules I have to go back to my modules page to make sure they are enabled, then return to update.php to allow the contrib modules to do any updates that are needed.
### Upgrade with Drush Site-Update
There are actually two ways to use Drush for the upgrade. I'll want to be sure to be using Drush version 4 or higher for best results. I prepare an empty database for the D7 version of my site, as above, and decide where I want to place the new files in my directory. I don't need to create the directory, Drush will do that.
I need one more thing to give Drush enough information to know what to do, I need to create a Drush alias file for the new site. If I already am using Drush aliases, I can add an alias to my current aliases. If I wasn't already using aliases, I would create a new file called 'aliases.drushrc.php', add it to the folder where my Drush code lives, and put something like the following text into it (I can give the sites whatever aliases I want, they don't have to be 'old' and 'new'):
```php
$aliases['old'] = array( // The alias I want to use for the old site.
'uri' => 'www.oldsite.com', // The url of the old site.
'root' => '/var/www/drupal6', // The physical file location of the Drupal root for the old site.
'db-url' => 'mysqli://username:password@localhost/oldsite', // The db_url string for the old site's database.
);
$aliases['new'] = array( // The alias I want to use for the new site.
'uri' => 'www.newsite.com', // The url of the new site.
'root' => '/var/www/drupal7', // The physical file location of the Drupal root for the new site.
'db-url' => 'mysqli://username:password@localhost/newsite', // The db_url string for the new site's database.
);
```
Then I just type the following into a command line and watch it go:
```
drush @old site-upgrade @new
```
If I want more information about what it is doing I can append '--verbose --debug' to the command.
This will create the new site directory, copy the old database to the new database, add the core files to the new site location, run update.php on the core files, add the contrib modules, and run update.php on them.
### Upgrade With a Drush Make File
Using Drush Site-Upgrade is pretty slick, but it does not do everything. It does not find a specific version of module code for my contrib modules if I need something that is not standard or not yet marked as supported. And it does not add the new modules I will need in D7 that did not exist in D6. And in some cases it adds modules to D7 that I won't actually want to use.
A more fine-grained approach is to create a Drush Make file for my upgrade that contains the exact modules and versions that I want in D7 and let me grab them all at once. It will also do things like add external files needed by some of the new modules, like the mediaelement library required by the Media Element module.
The easy way to do this is to go to the old site and type the following:
```
drush make-generate mymakefile.txt
```
This will create a Drush Make file for my old site. Then I can edit it to change the core version to D7 and fix the versions of each of the contrib modules to the version I want to use in my D7 site. I can also remove modules I no longer need and add new ones that are required.
Then I create an empty directory for my new site, place the make file in it and type:
```
drush make mymakefile.txt
```
That will populate my site with all the new code. I copy the settings.php file from the D6 site to the D7 site, copy the D6 database to the D7 database, navigate to update.php, and run update.php to update the database. As noted earlier, I may want to add an extra step of moving the contrib modules out of the sites/all/modules folder while I run update.php on only core, then move them back and run it again with the contrib modules in there.
## Final Steps
I can then go to admin/modules and enable any new modules I need plus several modules that won't be turned on by default in an upgrade:
- Toolbar
- Overlay
- Image
- Contextual Links
- Dashboard
Many things were updated automatically but if I was using CCK in D6 I won't see any fields in D7. I have to update my field data as a separate step. I install the Content Migrate module (in the D7 version of CCK), then navigate to admin/structure/content\_migrate.
There I will see three sections: a list of all the fields that are available to migrate, fields that cannot yet be migrated (because I don't have the right modules installed and available yet), and fields that have already been migrated.
After I migrate the fields to move the D6 field data to D7, I can go to the content types pages to confirm that the fields are there and structured correctly. I can roll the migration back if it doesn't work right and try it again later until I have the results I need.
## Conclusion
I hope these tips are helpful. The upgrade process is never totally painless, but hopefully these ideas will make it a little smoother.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "The Drupal Song on the Hurdy Gurdy"
url: "/articles/the-drupal-song-on-the-hurdy-gurdy"
type: article
date: 2008-09-04
updated: 2014-05-14
---
# The Drupal Song on the Hurdy Gurdy
# The Drupal Song on the Hurdy Gurdy
By
[ Jeff Robbins ](/about/jeff-robbins)
September 4, 2008
Of all of the [remixes](https://www.lullabot.com/articles/the-drupal-song-remix-tracks) of [the Drupal Song](https://www.lullabot.com/podcasts/drupalizeme-podcast/the-drupal-song), I think this may be my favorite. Kristof Van Tomme played this at DrupalCon Szeged. It's a [hurdy gurdy](https://en.wikipedia.org/wiki/Hurdy_gurdy), [man](https://www.amazon.com/exec/obidos/ASIN/B001382GO6/orbit0b-20)!
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Performance and Scalability Seminar Slides"
url: "/articles/performance-and-scalability-seminar-slides"
type: article
date: 2007-04-02
updated: 2014-05-14
---
# Performance and Scalability Seminar Slides
# Performance and Scalability Seminar Slides
By
[ Matt Westgate ](/about/matt-westgate)
April 2, 2007
On March 24th, 2007 Lullabot hosted the [Performance and Scalability Seminar](https://www.lullabot.com/seminar/drupal_performance_and_scalability/sunnyvale_ca_2007) in Sunnyvale California the day after the [OSCMS Summit](http://2007.oscms-summit.org/). The panel spent the day evaluating the role each part of the software stack plays in performance and scalability. To continue the discussions we've made our slides available for download. Enjoy!
- [Matt Westgate](https://www.lullabot.com/about/mattwestgate) ([Lullabot](https://www.lullabot.com/)) - [Introduction](https://www.lullabot.com/files/Matt-Westgate_Perfomance-and-Scalability-Intro.pdf) \[602KB\] and [Finding Your Server's Bottleneck](https://www.lullabot.com/files/Matt-Westgate_Isolating-a-Servers-Bottleneck.pdf) \[2.02MB\]
- [James Walker](https://www.article.com:443/about/the-team/james) ([Bryght](https://www.article.com:443/)) - [Optimizing The Web Server](https://www.lullabot.com/files/James-Walker_Optimizing-the-Webserver.pdf) \[740KB\]
- [Jeremy Andrews](http://kerneltrap.org/) ([CivicSpace Labs](https://www.putrawin78x.art/)) - [Optimizing the Database](https://www.lullabot.com/files/Jeremy-Andrews_Optimizing-the-Database.pdf) \[108KB\]
- [Robert Douglass](https://www.lullabot.com/user/8) ([Lullabot](https://www.lullabot.com/)) - [Memcache - Lightning Fast Drupal Sites](https://www.lullabot.com/files/memcache-presentation.pdf) \[146KB\]
- [Dries Buytaert](https://dri.es/) ([Drupal Project Founder](http://drupal.org/)) - Optimizing Drupal (not available)
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "The Buzzr Demo Video - Making Drupal Usable"
url: "/articles/the-buzzr-demo-video-making-drupal-usable"
type: article
date: 2009-04-13
updated: 2014-05-14
---
# The Buzzr Demo Video - Making Drupal Usable
# The Buzzr Demo Video - Making Drupal Usable
By
[ Jeff Robbins ](/about/jeff-robbins)
April 13, 2009
A few weeks ago, I put together an [April Fool's Day post](https://www.lullabot.com/articles/announcing-drupal-ue-the-usability-edition) about a bunch of usability work that Lullabot had been doing with Drupal. My favorite April 1 posts are usually heavily based in reality, and, [as we've mentioned previously](https://www.lullabot.com/news/20081011/lullabots-new-venture), Lullabot has, in fact, been doing a lot of work trying to create a streamlined version of Drupal.
We started our project about a year ago, working with [Karen McGrane](https://karenmcgrane.com/) from [Bond Art + Science](http://www.bondartscience.com) heading up our user experience work and [Ed Sussman](https://www.domainmarket.com/buynow/edsussman.com) coordinating all of the business aspects of the project. We spent about 8 months building a prototype and started fund raising a little over 4 months ago. Having done all of this work on spec, and since it's still in flux, we were hesitant to share it publicly during our ongoing V.C. meetings. But as we've been watching the great [usability work that Mark Boulton and Leisa Reichelt have been doing](https://forexidx.com/) for Drupal 7, we've found that they're struggling with a lot of same issues that we have, and even starting to solve them in the same ways.
So rather than playing it conservatively and keeping our work hidden, we've decided to unveil it to the world and contribute our thinking to the usability discussion. Our project, now called [Buzzr](https://buzzr.com/), is still moving forward and there are many more features and ideas that we are working on. But I've made this video to highlight many of our usability ideas and show how they were implemented... no joke!
Enjoy.
Problems playing the video embedded here? Go to http://blip.tv/file/1988015 for other formats and options.
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal Wins GRAMMY.com"
url: "/articles/drupal-wins-grammycom"
type: article
date: 2010-01-20
updated: 2014-05-14
---
# Drupal Wins GRAMMY.com
# Drupal Wins GRAMMY.com
By
[ Jeff Robbins ](/about/jeff-robbins)
January 20, 2010
[Lullabot](https://www.lullabot.com/) is proud to announce that [GRAMMY.com](https://www.grammy.com/), the official site of the [GRAMMY Awards](https://www.grammy.com/), is now a Drupal site. The GRAMMY Awards is the music business' largest and most prestigious awards ceremony. This year's telecast, happening January 31st, will be the 52nd annual awards ceremony held by the The Recording Academy.
GRAMMY.com has run on several platforms over the years, but The Recording Academy decided to move to Drupal for its flexibility, speedy build-out, scalability, and performance under pressure. The website sees a huge traffic spike around the telecast and the Academy needed a content management system which could be both resource efficient throughout the year and provide high-performance and high-availability around the dates of the awards ceremony.
Lullabot, whose portfolio includes [Lifetime Television](https://www.mylifetime.com/), [FastCompany.com](https://www.fastcompany.com/), and the Sony Music artist platform (running over 100 Sony artist websites), brought in developers from [Santex](http://www.santex-net.com/) to help get the site built in about eight weeks. The site features extensive photo and video galleries, a live video feed, blogs and news, and of course listings of all the nominees. The site also features integration with Twitter and the GRAMMYs' active Facebook community. The project was assembled using mostly existing free add-on modules from Drupal's vast contributions repository.
In the past, a website like this would have cost millions of dollars to build. But Drupal allowed The Recording Academy to assemble the site quickly at a fraction of the cost.
### Site Modules and Architecture:
This [PressFlow](http://pressflow.org)-based Drupal 6 site makes heavy use of [CCK](http://drupal.org/project/cck) and [Views](http://drupal.org/project/views). Other modules of note are [Views Slideshow](http://drupal.org/project/views_slideshow) on the home page; [Fivestar](http://drupal.org/project/fivestar) for ratings throughout the site; [ImageCache](http://drupal.org/project/imagecache), [ImageField](http://drupal.org/project/imagefield), and [ImageField Extended](http://drupal.org/project/imagefield_extended) for image handling and galleries, Poll module (part of Drupal core) on the home page, and [Views Cloud](http://drupal.org/project/views_cloud) for sidebar tag clouds throughout. The site also uses the [Custom Page](http://drupal.org/project/custompage) module to provide the custom home page layout.
New modules to come out of the project include [Gallery Summary](http://drupal.org/project/gallery_summary) and [iFrame Filter](http://drupal.org/project/iframe_filter) which improves page load performance for remote javascript-based content. Many module patches and improvements were also contributed including work on [Flag](http://drupal.org/project/flag), [NodeQueue](http://drupal.org/project/nodequeue), [ShareThis](http://drupal.org/project/sharethis), and the [Ooyala](http://drupal.org/project/ooyala) video module, which handles all of the video on the site.
### Design and Theming:
The site design theme is based on the [We're All Fans](http://wereallfans.com/) GRAMMY marketing campaign by [TBWA Chiat Day](https://www.tbwachiatday.com/). The site also makes extensive use of the [Cufón](http://cufon.shoqolate.com/generate/) javascript-based font rendering engine to implement standards-compliant custom header fonts. The site also uses a custom base theme and sub-themes to allow for easy design changes from year to year.
### Hosting:
Knowing that the site would need very flexible hosting to handle the traffic spike around the telecast, Lullabot did a lot of research to find a company who could pay close attention to the site and scale up and down quickly to handle the load while minimizing costs.
The final solution has 2 MySQL servers in a database cluster and 8 load balanced Apache servers, each running Memcache and acting as a MySQL slave server. The entire setup is hosted with [NeoSpire](http://www.neospire.net/). "We chose NeoSpire Managed Hosting for their interest in helping us come up with a custom solution and their willingness to monitor the site closely throughout the GRAMMY Awards event," says Lullabot co-founder, Matt Westgate. "We're also using [Akamai](http://www.akamai.com/) and Varnish reverse proxy caching to offload most of the anonymous traffic â a trick we picked up from Alec Hendry at MTV UK."
This year's GRAMMY Awards airs on the CBS television network January 31st at 8pm. Look carefully for members of the [Lullabot team](https://www.lullabot.com/about/team) in the audience.
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal Actions and Workflow Video"
url: "/articles/drupal-actions-and-workflow-video"
type: article
date: 2006-05-28
updated: 2014-05-14
---
# Drupal Actions and Workflow Video
# Drupal Actions and Workflow Video
By
[ Jeff Robbins ](/about/jeff-robbins)
May 28, 2006
**NOTE: This video is no longer available as it contains outdated content.**
This videocast shows how to use Drupal's [Actions](http://drupal.org/project/actions) and [Workflow](http://drupal.org/project/workflow) modules to create a simple trigger to send out notices whenever new content is posted to your site.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Coding for Drush: What is a Drush Command?"
url: "/articles/coding-for-drush-what-is-a-drush-command"
type: article
date: 2012-12-05
updated: 2014-05-14
---
# Coding for Drush: What is a Drush Command?
# Coding for Drush: What is a Drush Command?
We have a new series all about custom Drush commands
By
[ Addison Berry ](/about/addison-berry)
December 5, 2012
Drush is a very powerful tool in any Drupal site-builder's toolbox. While Drush has lots of great features and commands, sometimes it just doesn't have one you would really like to be available. Well, Drush is designed like Drupal, and it is very extensible. We have a new series out, called [Coding for Drush](https://drupalize.me/course/learn-drush-drupal-shell), which teaches you how to create your own, custom Drush commands for the tedious tasks you'd like to make quicker. We have a free video that kicks off the series by explaining what a Drush command is, and how Drush knows where to find them.
If you'd like to get a sense of all the cool things we'll be covering in the series, Joe gives an overview in this introduction video:
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "The Great Pretender: Making your data act like a field"
url: "/articles/the-great-pretender-making-your-data-act-like-a-field"
type: article
date: 2009-06-26
updated: 2014-05-14
---
# The Great Pretender: Making your data act like a field
# The Great Pretender: Making your data act like a field
By
[ Jeff Eaton ](/about/jeff-eaton)
June 26, 2009
These days, almost every major Drupal site is using CCK, the module that lets you add custom fields to any content type. Among other things, CCK lets administrators rearrange a node type's contents using a simple drag and drop interface. In the past, this only worked for fields that CCK itself managed. If you worked with a custom module that altered a node's content, it was up to you to manage its position in the node content.
Now, though, it's possible for any module to tie into CCK's field management page to control the positioning of custom content. The key is `hook_content_extra_fields()`, and in this article we'll show you how to use it.

To demonstrate this technique, we'll fix something in Drupal that normally *can't* be re-ordered: the contextual links that go below each node, like "Add a comment" and "Bookmark this." While a Drupal theme can tweak the location of those links, there's no way to move them around from the administrative UI, and no easy way to position those links *between* two CCK fields.
The first step is to use hook\_nodeapi() to create a new entry in the `$node->content array` that contains the rendered links. `$node->content` is a collection of data that's ultimately used to build the `$content` variable used by themes when printing a node. Data inside of `$node->content` can be easily tweaked and reordered by any module.
```
/**
* Implementation of hook_nodeapi().
*/
function link_mover_nodeapi(&$node, $op, $teaser, $page) {
if ($op == 'view') {
$links = module_invoke_all('link', 'node', $node, $teaser);
drupal_alter('link', $links, $node);
if (!empty($links)) {
$output = theme('links', $links, array('class' => 'links inline'));
$weight = content_extra_field_weight($node->type, 'links');
$node->content['links'] = array(
'#weight' => !empty($weight) ? $weight : 100,
'#value' => $output,
);
}
}
}
```
One of the key lines in that function is the call to `content_extra_field_weight()`. It's a utility function provided by the CCK module that returns the current 'weight' of a given item in relation to other parts of a node's content. If CCK isn't keeping track of the item we ask about, it will return a zero -- the 'default' weight of an item. If that happens, we substitute 100, so that the links will fall to the bottom of the node's content by default. How, though, *can* we get CCK to handle our new "links" element?
```
/**
* Implementation of hook_content_extra_fields.
*/
function link_mover_content_extra_fields() {
$extras['links'] = array(
'label' => t('Node links'),
'description' => t('Links displayed when a node is viewed.'),
'weight' => 100,
);
return $extras;
}
```
`hook_content_extra_fields()` is provided by CCK as well; it gives modules a chance to tell it what items they have that need to be considered when reordering a node's component fields. In it, we just need to return an array defining the name, description, and default weight of our item. Once we've done that, visiting the CCK 'Manage Fields' page for a given content type will give us the following:

Ta-da! CCK now lets us reorder the node links like any other field. There's only one piece left, though: removing the default `$links` variable so that the theme won't print it out *in addition* to our reorder-able version. That's easy enough, using `hook_preprocess_node()`.
```
/**
* Implementation of hook_preprocess_node().
*/
function link_mover_preprocess_node(&$vars) {
unset($vars['links']);
}
```
Once that's in place (and we've cleared the cache to ensure Drupal recognizes the new preprocess function), everything should work fine. Below is a screenshot of the final results after moving the 'Links' item above the node's body and other fields. I've also attached a zip file containing the sample code. Feel free to tweak it and experiment -- CCK is immensely popular, and tying into its configuration forms is a great way to make things easier for a site's administrators.

Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Martha Stewart Adds a Dash of Drupal"
url: "/articles/martha-stewart-adds-a-dash-of-drupal"
type: article
date: 2011-03-07
updated: 2014-05-14
---
# Martha Stewart Adds a Dash of Drupal
# Martha Stewart Adds a Dash of Drupal
Lullabot helps launch MarthaStewart.com
By
[ Jeff Robbins ](/about/jeff-robbins)
March 7, 2011
Today we're proud to announce that Martha Stewart Living Omnimedia, Inc. (MSLO) is running Drupal! MSLO partnered with Lullabot on many aspects of the technical architecture and development efforts of this project. The Lullabot and MSLO teams worked closely over the past year to create a high-demand, high-performance Drupal installation specifically customized to the needs of this global company.
The Lullabot effort was led by Karen Stevenson, managed by Seth Brown, and developed by James Sansbury, Eric Duran and the MSLO Digital team. Lullabot performed a Discovery Audit of MSLOâs existing websites early last year and then produced analysis documents along with an extensive blueprint for the transition to Drupal. These documents became the foundation for the project.
Throughout the project, Lullabot worked side-by-side with MSLOâs development team, led by Ira Tau, VP of Internet Technology at MSLO. During the development process, the joint team created an enterprise content library to unify all of MSLOâs extensive recipes, images, video, articles, and other media on a Drupal instance that serves data to the front-facing websites. In addition, Karen Stevenson led the charge in analysing cross-functional needs and designing a robust content architecture to make the processes around content creation easier and more flexible.
James Sansbury built functionality that allows Drupal 6 and Drupal 7 instances to interact, enabling D7 to act as a central dispatcher for some of the sitesâ dynamic features and services. Drupal 7 was chosen because of its extensible comment functionality and other new features and optimizations. Integration with Varnish and CDNâs keeps the sites performant and scalable.
A major part of MSLOâs transition to Drupal was the migration of data from multiple sources in varying formats, including databases and XML feeds. Data migration specialist Cyrve (www.cyrve.com), and Cyrve's Mike Ryan helped to create a continuous migration process, and MSLO sponsored important commits to Migrate and other modules based on the projectâs needs. We also collaborated on some other aspects of the project with Northpoint Solutions (www.northps.com), a New York/New England-based consulting and development company that specializes in content management systems using various technologies, including Drupal.
The result is a network of interconnected Drupal installations that serve as both the primary front-end websites and back-end content repositories to power several different MSLO properties. By distributing functionality and traffic across multiple installations of Drupal, we were able to take advantage of the best parts of Drupal 6, Drupal 7, and other services, while keeping focus on enterprise scalability.
Drupal now powers the home page, main landing pages and the search functionality. The sites are being transitioned in ongoing, strategic phases, with the goal being for all of MSLOâs websites to be powered by Drupal in the coming months. MSLOâs current websites include: www.marthastewart.com, www.marthastewartweddings.com, www.wholeliving.com and www.emerils.com. In addition, a listing of MSLOâs blogs is available at www.marthastewart.com/blogs.
âOur experience with Drupal at MSLO has thus far been extremely positive and exciting,â said Mr. Tau, âthanks in large part to our work with Lullabot, Cyrve and Northpoint. We are looking forward to our continued work, forthcoming updates to our consumer experiences and additional contributions to the Drupal Community.â
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "A Client's Guide to Agile"
url: "/articles/a-clients-guide-to-agile"
type: article
date: 2014-02-07
updated: 2021-01-12
---
# A Client's Guide to Agile
# A Client's Guide to Agile
Deciding the processes that govern a project needs to be agile itself.
By
[ Darren Petersen ](/about/darren-petersen)
February 7, 2014
At Lullabot, our client services team works with all kinds of folks to deliver projects. Sometimes, we start with the back of a napkin, and need to discover all the requirements and priorities alongside our client. Other times, thereâs a clear goal complete with annotated wireframes, photoshop files and other assorted documentation.
Our clients come to us with varying degrees of rigor when it comes to project management, too. Some have a mature development culture that slices the work to be completed into bite-size chunks, sets priorities, and manages the delivery of those tasks. They may even have a defined quality assurance and deployment process. With a client like that, we use the tools and processes theyâre familiar with, and look for ways we can streamline our partnership even more.
Other clients donât have development processes in place when we meet them, and just want to know when their project is going to be done. In these cases, weâre often in the position of establishing processes, selecting tools, and educating the client about how we can work best for them.
### Making Lemonade
A client lacking rigorous processes is not necessarily worse off than a client with clear project management methodology. Loose project management requirements at the outset make it possible to fit the process to the project, while rigor on the client-side means we have less to reinvent.
Of course, a lack of process could be a chaotic mess, but navigating existing processes can be also be a minefield. In every case, we have to adapt as we go. Every project is different, and we try to adapt to whatever tools and processes our clients bring us.
Of course, weâve got our opinions about how a software project works best. Our project managers and developers all agree that the easiest and most fun way to deliver good software is by working closely with a client to build what they want, getting feedback along the way. In that respect, weâre basically an Agile development shop.
### Agile?
If youâve never worked on an Agile team before, you may be asking âWill this require physical fitness?â. Before you go put on your yoga pants and start stretching, Iâll explain:
Agile software development has been around for 20+ years in one form or another. The name Agile and the core ideas come from a group of seasoned developers who got together in 2001 to discuss what makes software projects succeed. Together they wrote a short statement called the Agile Manifesto, which captures the values they agreed were important. Hereâs what they came up with:
- Individuals and interactions over processes and tools
- Working software over comprehensive documentation
- Customer collaboration over contract negotiation
- Responding to change over following a plan
### OK, what does that mean?
As I read those values, the first two are high-falutinâ ways of saying that software development is a human process that should result in working software that youâre happy with. That means the people youâre working with matter to the end result, and itâs better to build things than talk or write about them.
That doesnât mean process, planning and documentation arenât important - far from it. But plans change, and documentation goes stale. Ultimately, happy people and working software matter more to the end result.
### And what about the other two?
The second two values recognize the fact that plans change and new ideas happen late in a project. Trust and flexibility on both sides of a client/vendor relationship are required to accommodate change. That kind of two-way relationship with a client is often the opposite of a fixed-price/fixed-scope contract.
Traditional fixed-price contracts are inevitable, especially when we havenât yet built the kind of trust together that would allow a different kind of arrangement. In the end, we want to work as smoothly and naturally with our clients as we can. Where that trust exists, we have the freedom to do our best work, and you have the freedom to change your mind as needed along the way.
### What does that look like?
Practically speaking this means that we donât make a big plan and then go hide out for months, until THE GREAT UNVEILING of your project. Instead, the process looks more like this:
- we work in short sprints of around two weeks each to deliver working software bit by bit
- at the start of a sprint, we set priorities with you for the work to be accomplished in that sprint
- meet on a daily basis to talk about what weâre doing and clear any roadblocks
- at the end of the sprint, we demonstrate the work thatâs been accomplished and get your feedback
- then we start the cycle over and set new priorities with you for the next sprint
Through that process, you interact with the software weâre developing and give us feedback that allows us to refine things.
Agile development has various flavors - many of them have funny names, like Scrum, Kanban, or Extreme Programming. If you want to learn more about formal agile methods, thereâs a good, general overview at Wikipedia, and the specific flavors have their own proponents, like the Scrum Alliance.
As Iâve said, we have to fit our processes to the client in most cases. Due to that fact, we donât rigorously adhere to Scrum, XP, or any of the other methodologies in the Agile family. We do, however, borrow at will from them to make our projects work better.
In future articles, weâll be delving more into specific topics around project management and agile methodologies, and how they seem to fit different kinds of projects we work on. Stay tuned!
Published in:
- [ Technical Project Management ](/topics/project-management)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Big Trouble in Little Content: Planning for Reusable Microcopy"
url: "/articles/big-trouble-in-little-content-planning-for-reusable-microcopy"
type: article
date: 2013-02-14
updated: 2021-01-12
---
# Big Trouble in Little Content: Planning for Reusable Microcopy
# Big Trouble in Little Content: Planning for Reusable Microcopy
Writing engaging, reusable microcontent is tricky business. Whether you need titles, tweets, or summaries, consider the destination channels and the workflow.
By
[ Jeff Eaton ](/about/jeff-eaton)
February 14, 2013
Writing short bits of user-facing text -- [microcontent](https://www.nngroup.com/articles/microcontent-how-to-write-headlines-page-titles-and-subject-lines/) -- is no picnic. Coming up with a punchy, attention-grabbing tweet is tough enough; writing a memorable 50 character title for a breaking news story can stress out even a creative wordsmith. It's like the writer's equivalent of [Fitts's Law](https://en.wikipedia.org/wiki/Fitts's_law): the smaller the target, the narrower the margin for error.
In heavy-duty, reuse-oriented publishing systems, it's common practice to save several variations of an article's title and summary text. That gives writers some breathing room in more forgiving display contexts, but ensures they don't blow past hard limits for the short stuff.
We're currently working with a client on the nitty-gritty details of their new content model, and we're trying to iron out the best mix of fields to provide flexibility without overloading content authors. How many variations are enough? Karen McGrane's advice is simple and to the point: "As many as the writers will fill out, but no more." We plan to do some experiments with simple prototype interfaces to see what they're comfortable with, but before proceeding I did a quick review of the microcontent landscape to better understand the constraints of popular formats and channels.
From longest to shortest, here's the rundown:
- App.net post: **256** chars
- Twitter card summary text: **200** chars
- Facebook og:description text: **160** chars
- Google page description: **155** chars
- Tweet: **140** chars
- Tweet with link: **116** chars
- Subject line in iOS Mail.app: **45** chars
Other than the sharp 70 character dropoff between a tweet and and email subject line, there's no easy boundary line between short and middlin', but we can defintely see where we'll run into some constraints. We need *something* that won't be cut off when sending out email alerts, we want to be able to fit *some* kind of descriptive text into a tweet along with a link, and we'd like to squeeze a bit more text into channels that support it, like Google search results and Facebook link sharing. We also need to be sure that the various permutations are flexible enough to serve the primary web site's design needs.
### So, how does NPR do it?
When analyzing how organizations currently handle this stuff, NPR's [COPE API](https://www.npr.org/api/index.php) is usually the first place to go. Their internal content model is well-documented and available to the public, so it's a good choice.
[Seamus](https://www.npr.org/sections/ombudsman/2009/11/birthdays_at_npr.html/), NPR's CMS, exposes three variations of every story's title, as well as two teasers. There's a primary headline, a subtitle that's supposed to be a one-sentence description of the article, a 30 character short title, a teaser and miniTeaser. Their API doesn't list any specific length limits for the teasers, but it looks like standard ones run around 400-500 characters while miniTeasers weigh in at 100-120 characters. (Interestingly enough, they use 'Slug' to capture the name of the regular show or feature that a story came from, rather than the unique identifier/name for the story itself, but that's a tangent.) What WordPress and many other CMSs call a slug appears to be generated from an article's Short Title, but depending on how much of a stickler you are, it could be considered a fourth variation of the title.
With those different building blocks in mind, we can take a look at the best matched channels for each story's microcontent. Short Titles, as the teeniest unique bit of information an article possesses, are the best (perhaps only) option for email subject lines and URL slug generation. The distinction between headline and subtitle is a tricky one: it looks like a lot of stories don't have subtitles, though, so I'd be nervous depending on them.
The uncomfortable part comes when you get into the slightly longer microformat scenarios. Twitter cards give you a full 200 characters to work with, for example, but standard NPR teasers are almost always too long. The best bet is probably to use the standard title and URL as the standard social media post, then include the full title and microTeaser in the the Twitter Card and Facebook-leveraged Open Graph meta tags. (When squeezed for space, say when the date or a show/feature's name must go along with the social post, Category + Short Title + URL is probably a good bet for Tweet text.)
It's worth remembering that the summary and title meta tags used by Twitter Cards and Facebook OpenGraph support aren't just for *an organization's own social media posts*. They'll get pulled in automatically whenever a user shares the link themselves; it's a way of ensuring that some well-crafted editorial content gets carried along for the ride even if the user writes their own tweet or post text to go with the link itself. With Twitter Card support, a well-crafted, metadata rich story could easily squeeze in the name of the show/feature, the short title, a link, as well as the full title and miniteaser. Photos and video players can even be worked in, but that's another ball of worms.
### Anyone else?
There isn't much public documentation around it, but friends who've talked to the New York Times note that the Times maintains four variations of each article's title: Long and short, with 'colloquial' and 'keyword-optimized' versions of each. URL slugs can be generated from the short-keyword-optimized version, the short colloquial version can be shown in small sidebar lists, and the full colloquial version can be shown as the actual page headline. I can see the value, but I'm curious how many teaser/summary variations they produce as well.
Another client of ours has developed a lightweight COPE-style API for content reuse, and decided to go minimalist. They support only *one* standard title; auto-generate their URLs from a combination of topical tags and post IDs; and treat social media posts as a separate writing task, with no pre-written article summaries. It allows their writers to fire off new stories with little time spent on extensive metadata and microcontent, but it also requires more manual labor by their social team: as with most systems, it's all about the tradeoffs that work for a given organization.
### Preliminary conclusions
Beyond the actual character limitations and the need for smooth editorial workflow, clarity is a real concern. Lots of distinct fields doesn't just mean lots of copywriting work, it also increases the potential for accidental misuse of a field. It's easy, for example, to put a catchy tease instead of a factual description in the short summary field and assume that it will only be displayed on its own (rather than with a full title). However, that could make a social media post automatically "assembled" from several short fields feel awkward. Making sure there are clear distinctions in purpose between the different fields is a key.
After talking to the editorial team and reviewing a few of the existing options, I'm leaning towards the following recommendation:
- A 40-50 character Title field that serves as the short title, and the source text for an auto-generated URL slug.
- A 100 character Colloquial title that's used when the article is displayed on its own page, and is also included in the OpenGraph/Twitter Card meta tags. This can default to the standard (short) title if a longer one isn't entered, but editors should get the chance if they want to write a longer one. If it's available, it would also be short enough to squeeze into a tweet.
- A 155 character summary field that's short enough to include in most of the standard description and summary metadata fields for search engines, social networks, and so on.
- A longer 200-400 character teaser that's auto-generated from the first paragraph of the article's text, but can be overridden by editors if they want extra control.
- An optional "excerpt" field that's an actual quote from the meat of the article, intended for use as a pull quote on the full article page. It can also be used as a supplement to the teaser on certain landing pages when a high-profile article is being promoted.
Titles and summaries should work in combination *or* independently, but the optional excerpt would always be used *with* some explanatory text like the summary or full body of the article. That setup would give them just two *required* fields -- the short title and the 155-character summary -- and allow everything else to be automatically generated or hidden by default. We'll see how it goes.
It's nitpicky business, these titles and summaries, but with microcontent the margin for error is slim. In the meantime, I'm curious to hear how other content modeling teams are handling these challenges. Any other examples of interesting breakdowns and how they're working for the teams that use them?
Published in:
- [ Digital & Content Strategy ](/topics/content-strategy)
- [ UX & Design ](/topics/design-and-ux)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Edward Sussman: Why Start (Up) Now?"
url: "/articles/edward-sussman-why-start-up-now"
type: article
date: 2008-10-14
updated: 2014-05-14
---
# Edward Sussman: Why Start (Up) Now?
# Edward Sussman: Why Start (Up) Now?
By
[ Jeff Robbins ](/about/jeff-robbins)
October 14, 2008
*With our recent announcement of our new venture, the web has been abuzz with speculation. What follows is a guest blog post by Ed Sussman who visits lullabot.com to help shed some light on the situation. If you'd like to reach Ed, contact him at *

Amid the gyrations of the stock market, and predictions of a severe economic downturn, I have found myself in the interesting position of launching a start up with my friends at Lullabot and Bond Art + Science. Over the past six years, I've worked within the comfortable fold of two well known brands in the media world: Inc. and Fast Company, the last four years as president of a digital division with six websites, 40 employees and more than $10 million in revenue. Now I've left to be the CEO of a self-funded company formed by Lullabot and Bond Art + Science that doesn't even have a name for its product yet (even the name of the company is just Codename Enterprises.)
Some people think we're crazy to do this now. Jason Calcanis wrote a couple of weeks ago that he expects 80% of the start ups already funded would collapse because of the down, part of a "[start up depression](https://linktr.ee/calacanis/2008/09/29/the-startup-depression/)." And legendary VC Fred Wilson said companies without angel or VC funding in place would [probably have to try to make it without VC funding](https://avc.com/2008/09/my-thoughts-on/).
There's an old axiom, "There's no bad time for a good company" but that's a bit flip for the times. After all, some companies with good products are going to fail this year because of the downturn â they won't be able to cut their expenses deeply enough to make up for lost revenue, and VCs will cut the cord before second or third round financing becomes available. That's why there's some panic in the start up world right now, tempered by lots of practical advice from VCs about tucking in for the long winter of recession ahead. Sequoia Capital's [long slideshow shared with their portfolio companies recently](https://avc.com/2008/10/watch-this-slid/) is the best I've seen on the subject.
With our fledgling company, we only need to move around headcount numbers on a spreadsheet to make phantom staff we never hired go away. We're working lean from day one. If this were a funded start up, about three million dollars of other people's money would have been burned up so far. Instead, we just burned a few more pounds off of Lullabot Jeff Eaton. (That's an inside "skinny" joke.) By the way, Eaton talks about the technical work done by Codename so far, and the excellent contributions that will ensue for the Drupal project, in [this blog post](https://www.lullabot.com/articles/reprint-of-power-to-the-people-a-new-approach-to-drupal).
That's been the story for almost a year, now, actually. Day one for Codename was about ten months ago, when Lullabot managing partner Liza Kindred and I started talking about how damn hard the Drupal open source social publishing platform was for the likes of her and me (non-developers), and seemingly, even for the many developers who were working on a large project for me. I was in the midst of launching two of the most complex Drupal-powered sites to date â FastCompany.com and IncBizNet.com â and the separation between the promise of Drupal and the practical restraints were fairly maddening. I advised the Lullabots (the world's leading Drupal consultants) to start working with Bond Art + Science, one of the best user experience firms in the nation. I also read an amazing post called "[How Drupal Will Save the World](https://www.lullabot.com/articles/how-drupal-will-save-the-world)" by Lullabot CEO Jeff Robbins, that pretty much laid out all the guiding principals that came to be the Codename company.
Some 4,000 hours of development and design by Lullabot and Bond Art + Science ensued. The object was and is to build a hosted platform, powered by Drupal, that gives ordinary people, businesses and organizations simple tools (like drag and drop or point and click) to custom-craft websites with features such as multi-user blogs, social networks, wikis, member reviews and ratings, photo sharing, and custom form fields. With these tools, even newcomers should be able to build feature-rich multi-user websites that go well beyond the boundaries of blog sites, or more rigid products such as WordPress.com and Ning.
"Working lean" is an understatement of what happened. Working for nothing is what happened. Lullabot juggled consulting and Codename to make it happen so far. The excellent user interface experts at Bond similarly kicked in their valuable partner time. An amazing advisory board has similarly been offering up valuable advice: Jeff Dachis, former CEO of Razorfish and senior partner at Bond Art + Science; David Bradley, owner of Atlantic Media; Jeff Veen, founding partner of Adaptive Path and former design manager for Google; and Lane Becker, co-founder of GetSatisfaction.com and a founding partner at Adaptive Path.
## The Product
But "Why Start Now" isn't answered just by saying, 'we know how to do it if we want to, even if it means working lean and in a tight economy.' "Why Now" requires a deeper examination of the importance of this product, especially in tough economic times.
The short answer is that websites that are social and dynamic are dramatically more useful than websites that are static, and that has a powerful social implication. In [his post](https://www.lullabot.com/articles/how-drupal-will-save-the-world), Jeff Robbins tells the story of a village in Nigeria that allowed an oil company to use its land in exchange for clean water and schools. Because they had a website with some flexibility, they were able to post the contract with the oil company and bring attention to the oil company not living up to its obligations.
It's incredible how many organizations and businesses in the United States, let alone the world, still have static websites where they can't even change their business hours without going back to the developer who built the site for them. The simplest CMS back-end remains unavailable to them, unless perhaps they keep a blog (which in all likelihood is hosted elsewhere.)
I switched FastCompany.com over to Drupal in February, making it a dynamic site for the first time. Within three months, repeat visits had increased 1000%. The site went from a straightforward publisher to a [platform for conversation](https://www.fastcompany.com/article/media-social). But it took us almost a year to build and the work of half a dozen full time developers - not something ordinary people or businesses can do.
Yet, think of the practical implications if we could create a widely accessible web publishing tool with great social tools and format flexibility:
- Small businesses in search of leads for scarce business online could do a significantly better job attracting and creating a conversation with clients. More efficiency means more business and more jobs. Really.
- Small organizations could tap into the knowledge and needs of their members, and help them better engage with one another. Stronger organizations mean more powerful grass roots social movements. (Or at least better organized bowling leagues.)
- Bloggers could expand their work into real websites, with highly flexible formatting of pages and forms, rich tools to interact with their readers, and a back-end CMS akin for group blogging to what a major publisher pays thousands of dollars for. Better blogging platforms mean better information to readers at a time when newspapers are disappearing.
Earlier this year I was a judge at a startup competition put together by Jeff Jarvis, one of the great voices of "[citizen journalism](https://buzzmachine.com/2008/10/06/citizen-journalism-ruins-the-world-again/)." We were charged with judging the business plans of a group of grad students who thought running their own websites might be a better alternative to getting a job. A couple of the plans were, in effect, community newspapers, and a big chunk of the money they were after would have gone to pay for development of their sites. A few others involved more sophisticated dynamic tools: bookmarking, ranking and rating, user profiles, and the links.
When our platform reaches its potential, the startup costs for making these business plans real will drop dramatically. Companies will launch that would otherwise have never had a shot. And more start ups equals a better economy -- it's large enterprises that shed jobs during a recession. Job growth comes from small business.
Drupal is a magnificent modular platform that lets you build most any website you can imagine. If only you have the special know-how. It's hard even for developers to master, though. And that's not good enough to reach the mass audience that needs a social platform to build their websites.
That's why we're building a layer between Drupal and the end-user -- a layer that simplifies choices, but leaves Drupal core intact. And it's free.
## Can we make money with a free product?
Yes.
Some websites will want help with advertising. That something I'm good at, having grown ad revenue almost 600% during my time at Inc.com and FastCompany.com. Some will want premium services, like extra storage space, beyond what we'll provide for free. And some websites will want to tap into our expertise in how to maximize a social website with great copywriting, custom branding, SEO, SEM, and community building.
The business model for freemium remains viable even in a weak economy. Fred Wilson [wrote a good post about this](https://avc.com/2008/10/free-vs-paid/). The services surrounding a free product can be very valuable, and even in the worst economy, people will pay to get help succeeding in whatever is most important to them.
We're well aware that plenty of others have their own visions of expanding social media platforms to more people: Ning with better social networks, WordPress.com with better blogs; Acquia with better, supported distributions of Drupal itself.
What we will offer as an alternative is a more flexible format that's still straightforward for average users. And we'll be improving Drupal all along the way by giving back to the open source project. Jeff Eaton discusses a number of important breakthroughs we've already contributed [in his blog post](https://www.lullabot.com/articles/reprint-of-power-to-the-people-a-new-approach-to-drupal).
We'll see over the coming months whether this approach interests outside investors -- outside investment money would certainly speed things along. But we're going to keep going in any case.
## So why start up now?
Because innovation is always important.
Because getting in at the bottom is how you [make the most money in the long term](https://userscape.com/blog/index.php/site/comments/why_now_is_a_great_time_to_start_a_software_company/).
Because aggressive companies [pick up market share more easily during bad economic times](http://answers.google.com/answers/threadview?id=178334%20).
Because efficient ad-supported media, like radio during the great depression, [can and do catch hold even when times are rough](https://profy.com/2008/10/06/sure-about-pending-collapse-of-ad-supported-internet/).
Because, as investor Mike Moritz put it, [the best time to invest is when people are cowering under their desks](https://www.barrons.com/articles/BL-TB-3868).
Because people need this product.
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Querying a Slave Database with Views"
url: "/articles/querying-a-slave-database-with-views"
type: article
date: 2010-11-18
updated: 2014-05-14
---
# Querying a Slave Database with Views
# Querying a Slave Database with Views
Scaling Views with Views 3 and Pressflow 6
By
[ James Sansbury ](/about/james-sansbury)
November 18, 2010
If you're not already aware of it, there's this little fork of Drupal called [Pressflow](http://pressflow.org), maintained by those really smart people over at [Four Kitchens](https://www.fourkitchens.com/). If you're at all serious about getting your Drupal site to succeed (as in lots of visitors), start using Pressflow *now*, before success comes knocking on your door. Pressflow is a collection of patches, or nips and tucks of Drupal, geared towards performance and scalability.
## Database Replication in Pressflow 6
One of the things that Pressflow 6 supports that Drupal 6 does not is **database replication**. Database replication sounds all scary and impressive, but put simply, it just means you are creating another database to allow more traffic. The way to do that is to **replicate**, or copy, the database. You can then move those copies of your database to other servers, which will allow you to **distribute** the traffic you are receiving across more than just one server. It's kind of like adding another lane on a highwayâyou're not only allowing more cars to drive on the road **(Scalability)**, you're also adding the space **(Bandwidth)** for those cars to drive a bit faster **(Performance)**.
The original database that you have copied is usually called the Master database. This database ideally receives all the `INSERT`, `UPDATE`, and `DELETE` queries. The Master database then replicates out these changes to the copied databases, called Slaves. The Slaves ideally would receive all `SELECT` queries, or read only queries.
Rather than add in any sort of functionality to auto detect whether a particular query should go to the Master or the Slave, Pressflow takes the conservative route and only modifies one Drupal core function, and then adds a few more. The modified function is `pager_query()`, and the new functions are `db_query_slave()` and `db_query_range_slave()`. The `pager_query()` function is the function that is used in Drupal core to create paged lists of data, and for this function alone, Pressflow basically assumes that it's safe to route this query to the Slave database, if it exists.
So, what about all those other queries? What about the `SELECT`s that are occuring outside of `pager_query()`? Pressflow stays hands off. It doesn't want to make any assumptions about how you want to handle those queries. This means that it's up to you to start implementing those two other functions, `db_query_slave()` and `db_query_range_slave()`. Now before you start going through all the modules on your site and hacking them to use `db_query_slave()`, back up a bit. Most of the public listing of content on your site is likely being presented by the [Views](http://drupal.org/project/views) module. So for the greatest gains, we really only need to hack one module, right?
## Getting Views to Query the Slave Database
Well, it turns out if you're using Views 3 (at the time of this writing, the current stable release of Views 3 is Views-6.x-3.0-alpha3), you don't have to hack anything, you only need to extend. Views 3 has what is called a pluggable query engine. This means that you can do a complete drop in replacement of the code that is driving how your database is queried. Now that sounds promising, doesn't it? It also sounds a bit scary. Let's walk through how to do this step by step.
### Tell Views You Have Something to Say
The first step in doing pretty much any sort of extension of Views is to introduce yourself. The place you do this is in a module, and the way you do it is with `hook_views_api()`. If you already have a custom module for your site, great! If not, go ahead and create one. In this example, I'm going to call my module **Views Query Slave**, and give it a machine name of `views_query_slave`. So, inside of sites/all/modules I create a directory called views\_query\_slave and inside that directory I create a views\_query\_slave.module file and a views\_query\_slave.info file.
Let's pop open views\_query\_slave.module and see how to introduce ourselves to Views:
```php
/**
* Implementation of hook_views_api().
*/
function views_query_slave_views_api() {
return array(
'api' => 3, // We are implementing a Views 3 only feature.
);
}
```
That's it! So the "hook" in `hook_views_api()` is the machine name of our module, `views_query_slave`. We tell Views that we are implementing features in the 3 branch of Views, just to be sure that in the off chance this module gets enabled on a site with Views 2 on it, it doesn't start barfing up PHP errors and such.
Now that we've introduced ourselves to Views, we need to actually tell Views what we want to do. For this we need to create a file called views\_query\_slave.views.inc, and put it in our module directory. This is the file that Views is going to look to for our customizations. Inside of that file, we're going to be implementing some more views hooks, namely `hook_views_data_alter()` and `hook_views_plugins()`.
```php
/**
* We only want to modify the query plugin if db_query_slave() exists. This is
* in effect saying, "Hey, are we on Pressflow 6?"
*/
if (function_exists('db_query_slave')) {
/**
* Implementation of hook_views_plugins
*/
function views_query_slave_views_plugins() {
$plugins = array(
'query' => array(
'views_query_slave' => array(
'title' => t('SQL Query (slave)'),
'help' => t('Query will be generated and run using the Pressflow Slave database API.'),
'handler' => 'views_plugin_query_slave',
'parent' => 'views_query',
),
),
);
return $plugins;
}
/**
* Implementation of hook_views_data_alter().
*/
function views_query_slave_views_data_alter(&$data) {
foreach ($data as $table => &$table_data) {
if (isset($table_data['table']['base'])) {
// If query class is set and it contains views_query, we can swap it out.
$is_views_query = isset($table_data['table']['base']['query class']) && ($table_data['table']['base']['query class'] == 'views_query');
// If query class isn't set, we can assume that it's using views_query.
if ($is_views_query || empty($table_data['table']['base']['query class'])) {
$table_data['table']['base']['query class'] = 'views_query_slave';
}
}
}
}
}
```
As you can see above, all the code is wrapped in `if (function_exists('db_query_slave')) {`. This means if the function `db_query_slave()` doesn't exist (in other words, this module isn't running on Pressflow 6), our code won't get executed. *Phew*.
The first hook we're implementing is `hook_views_plugins()`. This is where we define our new Query Plugin, the one that will be querying the slave database if it exists. The important parts here are the **key** of the array, the **handler** and the **parent**. The key is essentially the machine readable name of our Query plugin (`views_query_slave`), the handler will be the name of the class as well as the name of the file that Views will look for, and the parent is what class our class will be inheriting from, or extending. In this case, we are extending the default Views query plugin, whose machine name happens to be `views_query`.
The second hook we're implementing is `hook_views_data_alter()`. This hook is altering any Views configuration previously declared by other modules. This configuration contains information about all the database tables that Views can query, along with the fields, arguments, relationships, sorters, filters, etc., that are associated with these tables. The main thing we are interested in here is a nested setting called `'query class'`. Query class is the key of the Views plugin declared in `hook_views_plugins()`, *not* to be confused with the handler or actual PHP class that will get called.
So, if a module specifies a query plugin for a particular table, Views will use the handler associated with that query plugin for our queries. If a module doesn't specify a query class, Views just assumes it wants to use the internal default Query plugin we mentioned earlier, `views_query`. So, we want to hijack any table that says to use the default Query plugin, or any table that doesn't specify one at all, and instead inform Views to use *our* class instead. We do that by setting `'query class'` to the name of our Query plugin, `'views_query_slave'`.
## Building the Query plugin
Now that we've introduced ourselves to Views, and told Views a bit about what we want to do, the next step is to actually write our Views Query plugin. Now this sounds a bit scary, but really all it entails is copying the parent class we are extending, and then removing the parts that we don't want to change. We mentioned earlier how the machine name and the handler are distinct from each other. The default Query plugin was named `'views_query'`, but the handler for it is called `views_plugin_query_default`. So the file we want to copy is inside of a directory called `plugins` within the Views module. If you want to follow along but don't have Views 3 downloaded, [drupalcode.org](https://drupalcode.org/viewvc/drupal/contributions/modules/views/plugins/views_plugin_query_default.inc?revision=1.1.2.21&view=markup&pathrev=DRUPAL-6--3-0-ALPHA3) is a good place to go.
The first thing we'll do is just copy this file completely into our custom module. Rename the file to the name of our handler (`views_plugin_query_slave`), and then update the class declaration to reflect the name of our handler and the parent class we are extending (`views_plugin_query_default`):
```php
/**
* Extension of views_plugin_query_default to query a slave db if it exists.
*/
class views_plugin_query_slave extends views_plugin_query_default {
```
Now it's time for cleanup. The only method we care about within the default class is `execute()`, since that is where the query actually gets executed. So, just delete all the other methods and junk out of our classâsince we're extending `views_plugin_query_default`, it will just find all that goodness there.
Let's set up a new variable in this class first. We don't want to just assume that all views are safe to query against the Slave database, so we'll create a variable called `$slave_safe` within our class:
```php
/**
* Whether or not this view is safe to be run against the Slave database.
*
* @var boolean
*/
protected $slave_safe = FALSE;
```
Then, let's add some new methods in our class, ones we'll use to wrap around our query functions:
```php
/**
* Wrapper method for db_query().
*/
function db_query($query, $args = array()) {
$fnc = $this->slave_safe ? 'db_query_slave' : 'db_query';
return $fnc($query, $args);
}
/**
* Wrapper method for db_query_range().
*/
function db_query_range($query, $from, $count, $args = array()) {
$fnc = $this->slave_safe ? 'db_query_range_slave' : 'db_query_range';
return $fnc($query, $from, $count, $args);
}
```
We check our new variable, `$this->slave_safe` in each query method. If the view is "slave safe", the functions we'll be calling are `db_query_slave()` and `db_query_range_slave()`. Otherwise, it just defaults to the normal Drupal core functions.
### Modifying `execute()`
Now we need to modify `execute()` to set up our `$this->slave_safe` variable and use our new methods:
```php
function execute(&$view) {
$cache_settings = $view->display_handler->get_option('cache');
$this->slave_safe = $cache_settings['type'] != 'none';
```
In our example, we're only going to be saying a view is "slave safe" if it has any sort of caching on. When you create a view, you have an option to set up time based caching of the query results or the markup itself. It's pretty handy, so start using it! You can easily set up your own method for determining what qualifies a view as "slave safe".
Now that we've got our `$this->slave_safe` variable set, the next thing we need to do is to find all the instances of `db_query()` and `db_query_range()` within the execute method, and replace them with `$this->db_query` and `$this->db_query_range`, respectively. You should be seeing something like this:
```php
$result = $this->db_query_range($query, $args, $offset, $limit);
}
else {
$result = $this->db_query($query, $args);
}
```
**Hey, I already see that in there!**
If you're already seeing `$this->db_query()` and `$this->db_query_range()`, it could be that you are using a version of Views newer than 3.0-alpha3. [This feature request](http://drupal.org/node/968830) in the Views issue queue has been committed to Views 3, but is only available in versions newer than Alpha 3. Check out the code listed at the bottom of this article for more details.
Cool, so now we've got Views using our custom query wrapper methods! We're not completely finished though. There's one more function call in here we need to modify, and that's the pager count query. For any pager views, Views executes two queriesâone to get the number of total rows there are, and the other that actually returns the results. If we don't step in somewhere, the count query will actually get executed against the Master database, which could lead to some funkiness in our View results. Here's the code as you're probably seeing it now (unless you fall into the group above that were already seeing `$this->db_query()`):
```php
if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
$this->pager->execute_count_query($count_query, $args);
}
```
What we want to do is create our own method for the pager count query, and call that instead:
```php
if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
$this->execute_count_query($count_query, $args);
}
```
And here's the new method we're adding to this class:
```php
/**
* Execute the count query, which will be done just prior to the query
* itself being executed.
*
* @see views_plugin_pager::execute_count_query()
*/
function execute_count_query(&$count_query, $args = array()) {
$this->pager->total_items = db_result($this->db_query($count_query, $args));
if (!empty($this->pager->options['offset'])) {
$this->pager->total_items -= $this->pager->options['offset'];
}
$this->pager->update_page_info();
return $this->pager->total_items;
}
```
## Voilà !
Great! We've got everything set up to have Views start querying the slave. Keep in mind, Pressflow is smart enough to know whether or not you even have a Slave database, and it won't cause any problems to use this module if you don't. It will function the same either way, so we can safely enable the module on any Pressflow site without worry.
If you're not using Pressflow already, it's definitely something to consider switching to. Even if you don't need to scale right now, you'll be better prepared if you do. Having a module like this example one is handy as well, allowing you to scale your website to multiple databases. Just keep picturing that additional lane on the highway and you'll see why it can benefit you to add replication, and to have your Views query the slave database.
A complete version of this example module can be found on [GitHub](https://github.com/q0rban/views_query_slave/tree/Views-3.0-alpha3). Please note, if you are using a version of Views 3 *later* than alpha3, a lot of the code above is not applicable. You should view [this branch](https://github.com/q0rban/views_query_slave) instead.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Great Tool for Webshots"
url: "/articles/great-tool-for-webshots"
type: article
date: 2009-08-14
updated: 2014-05-14
---
# Great Tool for Webshots
# Great Tool for Webshots
By
[ Karen Stevenson ](/about/karen-stevenson)
August 14, 2009
If you want to get screenshots of a web page, what do you do? You might want them to illustrate a new theme or to display in a gallery of your work.
Now that I've switched to using a Mac I was exploring Mac alternatives for this. One great one was [Skitch](https://evernote.com/skitch), which lets you easily grab screen shots and mark them up. But what if you want to show more than you can see in your window, like to display a fairly tall web page?
I just found a great new tool, [webkit2png](https://www.paulhammond.org/webkit2png/). It works on any Mac with Mac OS X 10.5 Leopard or later. Just download the file, pull up a terminal window, and type something like:
```
python /Users/karen/screenshots/webkit2png http://www.drupal.org
```
... and you end up with three screenshots of the site, a full size shot, a thumbnail, and a shot clipped to just show what you can normally see in a window. The results for Drupal are posted below (who knew the front page was that long!!)

There are configuration options for the size of the window and the size of the result, like:
```
# screengrab google
webkit2png http://google.com/
# bigger screengrab of google
webkit2png -W 1000 -H 1000 http://google.com/
# just the thumbnail screengrab
webkit2png -T http://google.com/
# just thumbnail and fullsize grab
webkit2png -TF http://google.com/
# save images as "foo-thumb.png" etc
webkit2png -o foo http://google.com/
# full documentation
webkit2png -h | less
```
## Links
- [webkit2png Home Page](https://www.paulhammond.org/webkit2png/)
- [Download](https://github.com/paulhammond/webkit2png/)
Published in:
- [ Front-end Development ](/topics/frontend-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal Song For Your Mobile (a.k.a. Cell) Phone"
url: "/articles/drupal-song-for-your-mobile-aka-cell-phone"
type: article
date: 2007-09-18
updated: 2014-05-14
---
# Drupal Song For Your Mobile (a.k.a. Cell) Phone
# Drupal Song For Your Mobile (a.k.a. Cell) Phone
By
[ Jeff Robbins ](/about/jeff-robbins)
September 18, 2007
It's been about six months since the first release of [The Drupal Song](https://www.lullabot.com/podcasts/drupalizeme-podcast/the-drupal-song) and I've been meaning to post a roundup of the amazing [remixes](https://www.lullabot.com/articles/the-drupal-song-remix-tracks) that various people have done.
I'll try to get to that soon, but in honor of the [WAV download](http://barcelona2007.drupalcon.org/>Barcelona%20DrupalCon%20which%20starts%20tomorrow,%20I've%20edited%20together%20a%20few%20loops%20of%20the%20song%20as%20ringtones%20for%20most%20modern%20mobile%20phones.%0A
Etsy, [Craig's List](https://www.craigslist.org/about/sites) and [Yelp](https://www.yelp.com/).
We've got master classes from Views and Panels author [Earl Miles](http://www.doitwithdrupal.com/speakers/earl-miles), Ubercart uber-guy [Ryan Szrama](http://www.doitwithdrupal.com/speakers/ryan-szrama), Organic Groups author, [Moshe Weitzman](http://www.doitwithdrupal.com/speakers/moshe-weitzman), and [Karen "the Queen of CCK" Stevenson](http://www.doitwithdrupal.com/speakers/karen-stevenson) talking about both CCK and date/event handling. We've got Drupal 7 co-lead [Angie Byron](http://www.doitwithdrupal.com/speakers/angela-byron) talking about Drupal 7 and the list goes on and on. We've also got some great speakers from outside of the Drupal community coming to talk about many of the non-Drupal skills needed to build a successful Drupal project -- these include content strategy, community building, project management, and understanding some of the emerging technologies which we will all need to integrate into our sites over the next few years.
Another important part of the Do It With Drupal experience is the social interaction and networking. Want to share a drink with some of Drupal's top developers? Want to meet other site builders, developers, and decision-makers who are in the same boat as you? Want to find some people to help answer your Drupal questions? With most of the event happening in one central place and organized evening events â not to mention lots of fun stuff nearby in the French Quarter, it's easy to meet people at Do It With Drupal!
What else besides a lot of interesting information and amazing speakers do you receive if you come to New Orleans you ask? How about these highlights:
- Amazing food - free breakfast and lunch each day for attendees
- A sweet swag bag that includes, yes... swag! Oh... and a Lullabot t-shirt.
- Your choice of one DVD from our popular [Lullabot Learning Series](http://www.doitwithdrupal.com/blog/free-lullabot-drupal-tutorial-dvd-all-attendees)
- Access to the 2009 Do It With Drupal video archive. We're recording it all! Go online once you get home and catch the sessions you missed during the excitement.
- Come hang out with Lullabot team, with a hammer and nails, at the [Habitat for Humanity](http://www.doitwithdrupal.com/blog/come-habitat-us) event on Saturday.
- [Lullabot temporary tattoos](https://www.flickr.com/search/?q=lullabot+tattoo). Pretend they're permanent. Look tough.
We'll see you in New Orleans!
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Custom search forms with Views and Fastsearch"
url: "/articles/custom-search-forms-with-views-and-fastsearch"
type: article
date: 2007-08-20
updated: 2016-04-07
---
# Custom search forms with Views and Fastsearch
# Custom search forms with Views and Fastsearch
Drupal custom search
By
[ Robert Douglass ](/about/robert-douglass)
August 20, 2007
In this article I show you how to use views and views\_fastsearch to make customized search forms that fit your exact needs. Have you ever wanted to restrict search to just one or two content types? Or search on only an exact set of nodes, say all articles written by a given author? Views and views\_fastsearch are the right tools for the job.This article applies to [Drupal 5.x](http://drupal.org/download), plus the [views 1.6](http://drupal.org/node/159390) and the [views fastsearch 5.x-1.x-dev](http://drupal.org/node/102088) modules.
*UPDATE: As of August 21, 2007, there is no 5.x-1.2 release of views\_fastsearch. Until that release is completed, use the 5.x-1.x-dev version of the module to see the features described in this article.*
Drupal's search module contains a powerful indexer which makes keyword searching efficient and accurate. On its own, the search module provides either a plain vanilla search field, or the [advanced search form](http://drupal.org/node/28159), but doesn't provide a mechanism for customizing the offerings. It is an either/or proposition. The views module is well loved for its ability to let you define a custom set of content on your site, and through the use of exposed filters, allow the site visitor to narrow the selection even further. These two modules can be glued together and made to cooperate using the views fastsearch module. In this article I show how to build a search form that has filters for taxonomy terms and content types.
## What is a view?
For all the talk about the views module, it is often not fully understood what a view actually is. In its most basic form a view is a set of content (nodes). In fact, when you create a new view, it is, in its initial state, the set of all of the nodes on your site. All of the other stuff that you may do that view will either narrow the set of nodes to be smaller and more focused, or define how those nodes should be displayed.
One of the best features of views is its filters. Once you've defined the initial set of content that the view is dealing with, filters can be used to narrow the set. By exposing the filter to the end user as a form element you empower your visitors to slice and dice the view to taste, and find exactly the content that they are looking for.
## What is views fastsearch?
The views fastsearch is one such filter. It allows you to start with a view and narrow the set of content based on keywords. It utilizes the very search index that the search module has built, but it restricts its search to the content set defined by the view. It also uses clever SQL to do the actual searching quicker than the search module, thus the "fast" aspect.
The definition of a view as a set of content is a practical definition to work with. It makes it clear that if our goal is to replace Drupal's standard search (which searches all of the *published* content on your site), the default view (all content) is a good starting place. Once we create a new view, the steps that remain are to narrow the set (limit it to published content), tell it how to present the results (as search results), and to expose the views fastsearch keyword filter so that users can enter their search keywords. We will then have a near identical replacement for Drupal's standard search.
## Setting up
To get set up I installed the search, [views](http://drupal.org/node/159390) and [views\_fastsearch](http://drupal.org/node/136690) modules. I created a small amount of content by hand. There are five nodes in total, and they all contain the word *Drupal*. I kept the two default content types, Story and Page, that are present in a fresh Drupal 5.2 install. I created a taxonomy vocabulary called *Drupal topic* with the terms *core*, *modules*, and *themes*. I ran cron.php once to make sure that the search index has been built.
First I will show you how to search this content with views fastsearch, after which I will show how a customized form can be built to filter by taxonomy and content type.
## Building a view that works like normal search
Once the modules are installed and some content has been created, it's time to create a view. This is done by navigating to *Administer->Site building->Views->Add*.
### Basic configuration

*Figure I: Name, Access and Description*
Fill out the following fields:
1. **Name**: This is the machine name of the view. I used *content\_search*
2. **Access**: You have the chance to limit access to this view by role. Leaving all roles unchecked is equivalent to allowing access to everyone, which is the typical configuration for site search.
3. **Description**: A brief description of the view that will be useful to you as an administrator later. It appears on the views administration page and is never seen by your end users.
### Provide a page view
We need to tell views to provied a page view for the search results.

*Figure II: Page View, URL, View Type, Title, Use Pager and Nodes per Page*
1. **Provide Page View**: This must be checked in order for views to provide a page view.
2. **URL**: This is actually the Drupal path (the 'q' parameter), not a full URL. It should not conflict with existing Drupal paths, so using *search* here will cause problems. I chose *fastsearch*, but you can use any path you choose. This path can be aliased using the path module.
3. **View Type**: This should be *Search Results* to emulate traditional Drupal search. Feel free to try the other options, though. For *Table View* or *List View* you will need to also add the fields which you wish to be displayed.
4. **Title**: This will be the page title for the search page. This can be whatever you want. I chose "Search by keyword, type and taxonomy".
5. **Use Pager**: This is important if you want your end user to be able to see more than the first page of search results.
6. **Nodes per Page**: Unlike the core search module, views allows you to define how many results per page are displayed. If you are the type who likes Google to show 50 results per page, you can set this number to 50 and enjoy.
### Adding fields
For the *Search Results* view type the only field that is needed is the *Search Score* field. No configuration is need on the field. If you wish to experiment with other view types (*Table*, for example), you need to explicitly add each field that should be displayed.

*Figure III: Search Score*
### Filters and exposed filters
Nearly every view that you ever create should be filtered to only include published material. The views fastsearch is also a filter (*Search: Fast Index*). Both of these are added in the filters section. The fastsearch filter should also be exposed to the end user (otherwise they can't enter their search keywords!)

*Figure IV: Node: Published, Search: Fast Index*
In the *Filters* fieldset:
1. **Node: Published**: This limits the set of searchable nodes to those that are published. *Yes* means that the set should be filtered to only include those nodes that are published. If you wanted to build an interface for searching through the *un*published nodes on your site, you could set this to *No*.
2. **Search: Fast Index**: This is the filter that says "take the set of nodes in this view and reduce it to only those which have a certain search term in the search index."
3. After adding the *Search: Fast Index* to the filters, it will have an *Expose* button. Use that button to add the filter to the the *Exposed Filters* fieldset:
- **Label**: This is text that will appear as a label on the form element that instructs the end user what the field should be used for. I chose "Keyword search".
- **Optional**: This determines whether a value has to be given for this filter to get any results at all. When unchecked, as in [Figure IV](#figure4), it is not optional, and no nodes will be returned by the view if the *Keyword search* field is left empty. This is the same behavior that the Drupal core search module has, as well as Google and other search engines. First you are presented with a search screen and no results. Only after you type some keywords and submit the form are search results shown. If the **Optional** box were checked, it would instruct the view to ignore this filter if no value is present. The view would then show its default set of content (which is all content on the site in this case), and someone coming to the search screen would be confronted with a full array of search results even though they have not yet searched for anything. As you'll see later in this article, this creates a point of conflict when we add and expose further filters.
- **Filter settings Default**: When checked, this tells the filter to inherit default settings from the *Value* field for this filter in the *Filters* fieldset.
- **Force Single**: Some filters can have multiple values (eg. the taxonomy filter could have *core* and *modules* selected). Checking the *Force Single* checkbox limits the end user to only one choice. Practically, this changes the form element from a multiple select to a single select.
- **Lock Operator**: Some filters have multiple possible operators (eg. AND/OR). When this is the case, the default behavior is to show an extra form element which specifies the operator. This is usually too much for the normal end user (but can be pure candy for power users), and you can hide that extra form element by checking *Lock Operator*
### Sorting
Last but not least, it is important to tell views how to sort the results. If you think about this in terms of Google or Yahoo!, you'll realize that search is not just a matter of identifying the right results, but also making sure that the right ones appear at the top of the list for the convenience of the person searching. This task falls to the scores that have been created by the search index. Adding a *Search Score* sort to our view will guarantee that the most relevant results are displayed first. Make sure that the sort is set to *Descending*, or you'll end up presenting the least relevant items first!

*Figure V: Search Score, Descending*
## The search results - Part I
Now if you visit example.com/fastsearch you should see a nice search form waiting for you. Since the core search module is also active, we can compare the search results produced by the two. In my tests, the [scoring factors](https://www.lullabot.com/articles/drupals-search-module-and-scoring-factors) affect the ordering of the results differently for fastsearch and core search. Overall, the core search handles scoring in a more sophisticated manner than the fast search filter. Fast search makes up for this, though, by allowing more control over the set of content being searched, and also by defining a hook that lets modules add their own scoring factors to search results.

*Figure VI: Search results compared*
## Extending the search form by adding filters
Now it's time to add two more filters to the view so that we can narrow the results based on content type and taxonomy term. Go back to the view and open up its edit tab.

*Figure VII: Taxonomy Terms for a vocabulary, Node: Type*
In the *Filters* fieldset, add two more filters:
1. **Taxonomy: Terms for Drupal topic**: *Drupal topic* is the name of the taxonomy vocabulary in my test data. Different *Terms for ...* filters will appear for your specific vocabularies
2. **Node: Type**: This filters on the node type, and you must select all of the options in *Value* that you want to be available to the end user. In my test data there are only two content types, *Story* and *Page*, and I want the end user to be able to search in both. One common feature request on Drupal.org is to limit search to one or more content types, or to exclude a content type altogether. With this filter you can achieve just that.
Now click *Expose* on both of the new filters to make form elements available to the end user.

*Figure VIII: Search: Fast Index (Optional) and exposed filters*
1. **Search: Fast Index - Optional**: I mentioned in the discussion of the [Optional](#optional) checkbox in the *Exposed Filters* fieldset, there is some conflict between the *Search: Fast Index* field and the other filters. You'll notice in [Figure VIII](#figure8) that *Optional* has now been checked - the search term is now indeed optional. This is done because it is a nice feature to be able to use the other two filters, *Drupal term* and *Type*, on their own. You can select all of the *Story* type content that is categorized as *core*, for example. If the *Search: Fast Index* filter is required, then this is impossible because you always have to enter a search term (thus narrowing the results to only that term). The drawback of making the filter optional is that the initial search page will show a set of results even before the user has entered any criteria. If this is disturbing to you, you'll need to make the field not optional and do without the functionality of filtering based on type and taxonomy alone.
2. **Taxonomy: Terms for Drupal topic**:
3. **Node: Type**: For both the taxonomy and the node type filters, the same applies. We want them to be optional so that the user can search with keyword alone. I chose the *Force Single* option because I like the cleanlier feel of a single select more than the multiple select. I chose *Lock Operator* because I don't think my site users necessarily want to think about AND/OR operators when searching.
## The search results - Part II
Now with two more exposed filters, we can do some very precise searching.

*Figure IX: All content in the themes category*

*Figure X: All content in the themes category that matches the search term "deco"*

*Figure XI: All Story content in the themes category*
## Configuring the Views Fastsearch module
If you visit *Administer->Site configuration->Search settings* you will notice that the Views Fastsearch module has extended the options found on this page. There is now a *views\_fastsearch* fieldset at the bottom of the page with a field labeled *search\_index*. The issue here is the nature of the SQL query that gets built and an older bug with the search index building that leads to extra rows in the search\_index table. For a full discussion of the issue, see the [Drupal.org issue queue](http://drupal.org/node/143160). For those sites that started off life as Drupal 5.x sites, it is fairly safe to choose the *No duplicates (Unique Index Exists)* option and save the configuration. This will lead in the best performance for your fast searches.

*Figure XII: No duplicates (Unique Index Exists)*
## Other exciting stuff you can do
The Views Fastsearch filter is an exciting module that opens up many possibilities for custom searches in Drupal. One very neat feature is its exposing of a `hook_search_ranking` function. Modules can implement this function to add scoring factors to fast searches. For example, the Voting API could add a scoring factor that gave a higher score (and thus a higher ranking in search results) to content based on the average voting result. Since scoring factors can be weighed relative to each other, this gives administrators an amazing amount of control over the fine-tuning of search results.
Another neat trick that can be done with the fastsearch filter is to implement `theme_search_form` in such a way that the default search form is replaced with the filters from the fastsearch view. This effectively replaces core Drupal search with your fastsearch search.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal-powered webcomics make it big!"
url: "/articles/drupalpowered-webcomics-make-it-big"
type: article
date: 2009-10-15
updated: 2014-05-15
---
# Drupal-powered webcomics make it big!
# Drupal-powered webcomics make it big!
By
[ Jeff Eaton ](/about/jeff-eaton)
October 15, 2009
Almost two years ago, DC Comics launched an interesting web site called "[Zuda](https://www.dc.com/)." Built on Drupal, its goal was to provide talented indie webcomic artists with a chance to pitch their ideas, compete against other artists for reader votes, and potentially get signed for The Big Leagues. (Think of it like American Idol for web comics.)
[ ](https://www.dc.com/)

During the final lap before Zuda's launch, Lullabot helped its team with theming training, feature tweaks, and performance optimization. As a long-time fan of webcomics, I was excited to be a part of the project, even late in the game. We worked closely with the Zuda development team during that phase, and were thrilled to see it launch successfully. Drupal's emphasis on social features and support for audience contribution of content provided them with a great platform for development on a tight timeline.
At the time of its launch, Zuda was controversial -- some thought it was a great idea, others in the webcomics world thought that it was just an attempt by a corporation to elbow in on a new market. Everyone agreed, though, that the site itself was a new approach to bridging the free-for-all world of webcomics with the editorially-controlled land of "Pro Comics."
Today it's still going strong, still running on Drupal, and -- even more exciting -- three Zuda winners were nominated for [Harvey Awards](https://harveyawards.org/), a prestigious comics industry award. [High Moon](https://www.dc.com/), the comic featured in the screenshot above, even walked away with the award for Best Online Comic. Scott Kurtz, the mastermind behind the popular [PVP Online](https://www.toonhoundstudios.com/pvp/) comic, [had some really cool stuff to say about Zuda](https://www.toonhoundstudios.com/pvp2009/10/12/i-want-to-believe-2/) after the awards. Kurtz was an early critic of the site, but it sounds like he's come to respect the Zuda team for the passion they have for their vision, and their unique approach to bringing great talent to a wider audience.
It's encouraging to see Zuda evolving into a valuable part of the comics landscape -- and doubly exciting to see Drupal helping them do that. Congratulations, Zuda!
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Theming Best Practices (Garland Gets a Cleanup)"
url: "/articles/theming-best-practices-garland-gets-a-cleanup"
type: article
date: 2008-04-28
updated: 2014-05-15
---
# Theming Best Practices (Garland Gets a Cleanup)
# Theming Best Practices (Garland Gets a Cleanup)
By
[ Nate Lampton ](/about/nate-lampton)
April 28, 2008
Yesterday Garland got a long-overdue update: The [page.tpl.php file was updated to use best practices](http://drupal.org/node/251758). Now we can *finally* open up Garland in a [workshop scenario](https://www.lullabot.com/training) and not have to use it as example of the *bad practices* within a .tpl.php file. This article applies to Drupal 6 and higher, though the theming principles apply to all versions of Drupal.
What's this about best practices? Let's compare the before and after of a few of the improvements. Each of the items below are extremely common things you can do to keep your .tpl.php files clean.
## Avoiding Function Calls Directly in a .tpl.php file
The theme layer is a place where logic and markup should be clearly separated. The .tpl.php files should be used explicitly for markup, just printing out variables whenever necessary. Template.php can be used for any extensive stints of logic or anything more complicated that printing out a variable.
**Before:**
page.tpl.php
```
```
**After:**
page.tpl.php
```
```
template.php
```php
function garland_preprocess_page(&$vars) {
$vars['ie_styles'] = garland_get_ie_styles();
}
```
The difference here is that all functions have been removed from page.tpl.php. Instead, we set a variable in template.php, then simply print out that variable in page.tpl.php. Note that this is in Drupal 6, in previous versions of Drupal the same thing would be done in the `_phptempate_variables()` function.
## Using Drupal Core's Body Classes
In the new theming system for Drupal 6 and higher, the concept of "body classes" has become standardized. That is, the layout of the page is determined by classes set on the `` tag. Some of these classes are things like "front", "logged-in", "page-node", and "sidebar-left". These classes allow CSS to control the layout or display things in a different way for logged in users.
**Before:**
page.tpl.php
```
>
```
Hompage HTML
```
```
template.php
```php
function phptemplate_body_class($left, $right) {
if ($left != '' && $right != '') {
$class = 'sidebars';
}
else {
if ($left != '') {
$class = 'sidebar-left';
}
if ($right != '') {
$class = 'sidebar-right';
}
}
if (isset($class)) {
print ' class="'. $class .'"';
}
}
```
**After:**
page.tpl.php
```
```
Homepage HTML
```
```
No additional code is needed in template.php, since Drupal core now provides this $body\_classes variable for us.
## Moving Logic from page.tpl.php to template.php
This example is probably the worst offender in Garland's page.tpl.php. It contained a large section of PHP code, including creating an array, imploding that array, and several function calls. Moving that to template.php keeps our page.tpl.php clean and used just for what templates are made for: markup.
**Before:**
page.tpl.php (yikes)
```php
// Prepare header
$site_fields = array();
if ($site_name) {
$site_fields[] = check_plain($site_name);
}
if ($site_slogan) {
$site_fields[] = check_plain($site_slogan);
}
$site_title = implode(' ', $site_fields);
if ($site_fields) {
$site_fields[0] = ''. $site_fields[0] .'';
}
$site_html = implode(' ', $site_fields);
if ($logo || $site_title) {
print '';
if ($logo) {
print '';
}
print $site_html .'';
}
```
**After:**
page.tpl.php
```
```
template.php
```php
function garland_preprocess_page(&$vars) {
// Prepare header
$site_fields = array();
if (!empty($vars['site_name'])) {
$site_fields[] = check_plain($vars['site_name']);
}
if (!empty($vars['site_slogan'])) {
$site_fields[] = check_plain($vars['site_slogan']);
}
$vars['site_title'] = implode(' ', $site_fields);
if (!empty($site_fields)) {
$site_fields[0] = ''. $site_fields[0] .'';
}
$vars['site_html'] = implode(' ', $site_fields);
}
```
## Proper template.php Function Prefixes
Garland has always used "phptemplate\_" as the prefix for all it's theme functions. Why? The "phptemplate\_" prefix indicates a function owned by PHPTemplate (Drupal's default theme engine). Sure, this works, but Garland really should be using it's own theme prefix. It's consistent with the naming conventions used throughout all the rest of Drupal modules and themes.
**Before:**
template.php
```php
function phptemplate_breadcrumb($breadcrumb) {
if (!empty($breadcrumb)) {
return '
' . implode(' ⺠', $breadcrumb) . '
';
}
}
```
**After:**
template.php
```php
function garland_breadcrumb($breadcrumb) {
if (!empty($breadcrumb)) {
return '
' . implode(' ⺠', $breadcrumb) . '
';
}
}
```
## Wrap Up
Often part of the problem with Drupal's templating system is that PHP allows you to *too much* in the theme layer. It's tempting to just slip that SQL query directly into node.tpl.php... but don't do it! The end result can be messy, hard to update themes. Keep as much logic as possible out of your .tpl.php files, instead move large chunks of code to template.php. This keeps potential bugs in one place, and your designers will appreciate having a .tpl.php file that's easy to understand.
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Development Team Best Practices"
url: "/articles/development-team-best-practices"
type: article
date: 2012-10-03
updated: 2019-01-11
---
# Development Team Best Practices
# Development Team Best Practices
Some Friendly Advice for Maximizing Your Team Experience
By
[ Brock Boland ](/about/brock-boland)
October 3, 2012
I'm just a couple months shy of being seven years from my last college class. I know that this isn't a significant number to you, but it is to me: it certainly doesn't feel like I've been in the real world **that** long, but it does explain why, when I stop to think about it, I've learned a whole lot from the four companies I've worked for in that span. I thought I'd share a few of those lessons about what has worked and failed: both for me as a developer, and in terms of managing a development team. Some of these things may be out of your control, but you might be able to pressure management to change the things you can't.
I don't want to shame or praise any particular companies here: every company has their own style, and everyone is always striving to improve the way they work. For that reason, I don't want to say who does what poorly, or claim that Lullabot does everything right (though admittedly, the Lullabot way has worked best for me). But, just to give you a sense of where I'm coming from, I will say that I have worked in variety of environments: a team devoted to the company's product (a web app), an agency that built Drupal sites for a variety of clients, a company that built Drupal sites and an installation profile for non-profits, and now, at a consulting company where I'm mostly building client sites. For the past two and a half years, I've worked remotely for mostly-virtual companies, so some of my lessons learned are specific to working on a distributed team.
First, some all-purpose lessons:
## Keep standups focused on one project.
I've done some varietal of scrums in every company I've worked for, and at most, we had a daily standup call or meeting with everyone from the team or department.
This does not work.
When any one person is giving their update in a meeting like this, it's likely that half of the other people aren't on the same projectsâ¦which means they space out. It's not a good use of anyone's time. If the team is working on more than one project, have a standup meeting for each to keep them focused.
## During standups, focus on the updates and punt any questions or discussion.
If you've ever been the last in a list of people giving their updates, without someone to keep everyone else on point, chances are good that you are occasionally knee deep in another project by the time the conversation got around to you. People tend to mention problems they've run into, and others may chime in with ideas or questions, and before you know it the standup has turned into a twenty-minute conversation about LESS vs. SASS.
Again, this wastes time. During a standup, have everyone do their updateâwhat did you do, what are you going to do, what's blocking youâand if there's any part of it that requires discussion, make a note to address it at the end of the meeting. There's no reason for everyone else to wait around while two people hash out the implementation plan for some feature that no one else will touch.
## Take your lunch break.
And not at your desk! You need to get away from the computer for a bit. I know it's pretty standard in most companies for everyone to grab a quick lunch and keep working while they eat, but that just leaves you at the end of the day exhausted and feeling like you haven't taken a break in nine hoursâ¦because you haven't.
If you work at the kind of company where you'll get a judgemental glance for having the audacity to step away from your work for a bit, I know a lot of great companies that are hiring. (Confession: I'm really bad at this one. Working from home makes it REALLY easy to make a sandwich and go right back to my desk with it.)
## Spend the money on tools the team needs.
Make sure your team has a Github account with plenty of available private repositories. Subscribe to a time-tracking service. Get the more expensive hosting account when you're pushing the limits of what you've got. If you try to cut corners, it will bite you in the ass.
I worked for one company that didn't want to pay for more private repos on Github, so all of our clients were in separate branches in a single repository. Needless to say, this was a real pain to manage, cloning the repo took forever because it included a ton of stuff, and we couldn't really keep track of feature or bug branches so we just didn't use them.
Another time, management wanted the development team to manage the disk space on the development server, and get rid of dev sites that weren't absolutely necessary anymore. When asked to justify the greater expense of more disk on the VPS host, we had to explain that it would be cheaper to upgrade that account than to pay us for the time it would take to manage old files. Keep in mind that labor is a real cost, and trying to save money by using a manual process might do just the opposite.
## Handle team assignments on a weekly basis.
On any given week, I know how my time should be distributed among projects. In previous positions, time has been doled out a month at a time. I for one find it difficult to determine where to focus my time if I'm given a few projects for the month and told to spend, say, 20% of my time on the first, and 40% each on the second and third. On any given morning, then, I need to review my time logged for the month so far and do some math just to figure out how I should spend my day.
Furthermore, things change more quickly than that: co-workers take last-minute trips for family matters or miss a week due to illness, or you get pulled onto a project that's behind schedule, or another client comes in with a small-but-critical project that needs a week of your time. By the end of any given month, the plan that you start with looks nothing like reality any more.
Having a weekly plan for project assignments and the amount of time for each ensures that everyone knows where their focus should be, while allowing for those last-minute problems that would completely scrap any longer-term plans.
For distributed teams:
## Setup an IRC channel for everyone in the company.
IRC is free and there are plenty of free apps to use it. Setup a channel for the company, so people have an easy way to ask questions or chit-chat. And this really should be used by the entire company: at one employer, the developers had a Skype chat room that a couple of the PMs joined, but it really just meant that the couple of people in the company who were **not** using it missed out on all the in-jokes.
## Setup IRC channels for each project.
This is a complement to the "individual scrum meetings per project" tip above: keep all chatter about a particular project confined to people involved in that project. You can even invite the client to join this channel, which is handy for getting answers to quick questions. Actually, this one isn't even specific to distributed teams, but it makes a much bigger difference for teams that aren't all in the same office.
## Get a [Yammer](#) account.
I wasn't sold on Yammer at first, but have come to love it here at Lullabot as a way to keep up with co-workers. It's basically like Facebook but for a small group. I've talked to people who have used Yammer at larger organizations and hate it, and I can see how having too many people on there would skew the signal-to-noise ratio; I don't know how many people is too many, except that it's more-than-all-Lullabots.
With everyone at Lullabot on there, we get much greater insight into what everyone is working on, and a lot of personality that we wouldn't see otherwise: we post photos from our weekend excursions, share links to funny stories, and just generally get that social aspect that can be so hard to find in a virtual company.
And finally, for absolutely everybody:
## If you're working on Saturdays, you're doing something wrong.
Or, more likely, someone above you is doing something wrong. Having too much work is generally considered to be better than having not enough work, but if you have to work on weekends more than once in a while, there has been a failure in the planning process. You've probably bitten off more than you can chew, and we've seen time and again that [adding more man-hours to a project doesn't get it done any quicker](https://en.wikipedia.org/wiki/The_Mythical_Man-Month). Life is too short to spend every weekend working, especially if you're so burnt out that you're not doing good work anyway.
These tips won't necessarily work for every team. For example, doing a standup meeting for every project will probably take **way** too much time if everyone is working on a dozen different projectsâ¦though you've probably got bigger problems if everyone is trying to keep track of a dozen projects.
Similarly, I mentioned time tracking and Github, but no tool is going to be right for everyone. We use [Freckle](https://letsfreckle.com/) for time tracking, but I've also been happy with [Harvest](https://www.getharvest.com/) (which has more features and mobile apps, but also costs more). We use [Github](https://github.com/) for all our code repositories (and increasingly, for [project management](https://www.lullabot.com/articles/managing-projects-with-github)), but some teams need to use SVN or CVS or need to keep all their code on local servers behind a firewall; for those teams, some other code repository solution will be necessary.
As I mentioned at the beginning, I'm not trying to shame or praise any particular company, but I feel that the way we do things at Lullabot right now definitely works better for me than anywhere else I've worked. That said, we're always interested in improving any process or tool that we may use. What works where you work? What didn't work at your last job? How would you improve on the tips I outlined above?
And, if your current employer comes down on the wrong side of too many of my points above, maybe [it's time to consider something new](http://jobs.lullabot.com/) :-)
Published in:
- [ Business ](/topics/business)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Trim A String To A Given Word Count"
url: "/articles/trim-a-string-to-a-given-word-count"
type: article
date: 2006-06-22
updated: 2019-02-04
---
# Trim A String To A Given Word Count
# Trim A String To A Given Word Count
Lots of times when you're theming a site, you'll want to have a snippet of text in one place or another.
By
[ Jeff Robbins ](/about/jeff-robbins)
June 22, 2006
Lots of times when you're theming a site, you'll want to have a snippet of text in one place or another. Drupal doesn't really have a good way of doing this because different languages have different definitions of "words", and a space-character is not always the delimiter between words, as it is in most Latin-based languages. But for those of us speaking languages that stick spaces between words, clients will often ask us to "just show the first 10 words" here or there. So here's a handy PHP function to do just that. And what's more, it can even add "âââ¬Ã¦" at the end of the truncated text.
```php
/**
* Trim a string to a given number of words
*
* @param $string
* the original string
* @param $count
* the word count
* @param $ellipsis
* TRUE to add "..."
* or use a string to define other character
* @param $node
* provide the node and we'll set the $node->
*
* @return
* trimmed string with ellipsis added if it was truncated
*/
function word_trim($string, $count, $ellipsis = FALSE){
$words = explode(' ', $string);
if (count($words) > $count){
array_splice($words, $count);
$string = implode(' ', $words);
if (is_string($ellipsis)){
$string .= $ellipsis;
}
elseif ($ellipsis){
$string .= 'â¦';
}
}
return $string;
}
?>
```
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Views Distinct / Node Access Problems"
url: "/articles/views-distinct-node-access-problems"
type: article
date: 2009-06-19
updated: 2014-05-15
---
# Views Distinct / Node Access Problems
# Views Distinct / Node Access Problems
By
[ Karen Stevenson ](/about/karen-stevenson)
June 19, 2009
I've been battling a core bug that creates problems when you use node access systems like Organic Groups and try to create views that are limited to distinct nodes. When you are using a node access system and you set 'distinct' to 'true' in any node view, you get ugly ugly error messages like:
```
user warning: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DISTINCT(node.nid), node_data_field_date.field_date_value AS node_data_field_' at line 1 query: SELECT COUNT(*) FROM (SELECT DISTINCT(node.nid) AS DISTINCT(node.nid), node_data_field_date.field_date_value AS node_data_field_date_field_date_value FROM node node LEFT JOIN term_node term_node ON node.vid = term_node.vid INNER JOIN term_data term_data ON term_node.tid = term_data.tid LEFT JOIN content_field_date node_data_field_date ON node.vid = node_data_field_date.vid WHERE (node.status <> 0) AND (node.type in ('event')) AND (term_data.name = 'children') ORDER BY node_data_field_date_field_date_value ASC ) count_alias
```
Yuck!!
This is actually a core bug, see http://drupal.org/node/284392. Core's db\_rewrite\_sql() will rewrite the query from **DISTINCT(node.nid) AS nid** to an incorrect query of **DISTINCT(node.nid) AS DISTINCT(node.nid)**. This invalid query will cause a fatal error keeping the query from executing.
I've been looking for a way to work around this problem until core gets fixed without either hacking core or hacking Views, and I finally found a way to do it using the Views hook\_views\_pre\_execute(). The code snippet I add to this hook will replace the problem code in the Views query just before it gets sent to db\_rewrite\_sql() with a value that db\_rewrite\_sql() can handle properly. The core function will then rewrite our replaced text, **nid AS nid**, back to the correct value of **DISTINCT(node.nid) AS nid** in the final query.
You have to implement this from a module, but I nearly always create a custom module for snippets like this. To my custom module I add the following function:
```php
function MODULENAME_views_pre_execute(&$view) {
$replace = array('DISTINCT(node.nid) AS nid' => 'nid AS nid');
$view->build_info['query'] = strtr($view->build_info['query'], $replace);
$view->build_info['count_query'] = strtr($view->build_info['count_query'], $replace);
}
```
No patches to maintain for core. No patches to keep up in Views. I just have to remove this when the core bug gets fixed.
I encourage everyone to participate in the core issue, http://drupal.org/node/284392, and get it corrected for once and for all. In the meantime, this technique is a workaround that can be used on production sites that need it.
**UPDATE**
There is a patch that will hopefully be going into Views to work around this bug. See http://drupal.org/node/501552. If that patch gets in, using a patched version of Views will eliminate any need for this trick. We still need to get the core bug fixed, tho, so please keep that effort moving forward.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "What if a field title ends with a question mark?"
url: "/articles/what-if-a-field-title-ends-with-a-question-mark"
type: article
date: 2006-10-27
updated: 2014-05-15
---
# What if a field title ends with a question mark?
# What if a field title ends with a question mark?
By
[ Jeff Robbins ](/about/jeff-robbins)
October 27, 2006
Can you tell us a little bit about yourself?: Isn't it kind of annoying that there is a colon right after the question mark?
I know this one has been bothering [Matt](https://www.lullabot.com/about/mattwestgate) for a long time. He's even [submitted a core patch for it](http://drupal.org/node/67211). But nothing has gotten in yet due to translation problems.
However I ran into this problem again while putting together our new [contact form](https://www.lullabot.com/contact_work) (using the amazing [Webform Module](http://drupal.org/project/webform)). I decided to solve it in our theme and thought others might want to use this trick.
By copying the *theme\_form\_element()* function from the *theme.inc* and pasting it into our *template.php* file, we can do a little checking to see if the form element title ends with a punctuation character. And if so, suppress the trailing colon.
Here's what it looks like for Drupal 4.7. I'm guessing it'll be pretty similar, if not completely the same for Drupal 5:
```php
/**
* Rewrite of theme_form_element() to suppress ":" if the title ends with a punctuation mark.
*/
function phptemplate_form_element($title, $value, $description = NULL, $id = NULL, $required = FALSE, $error = FALSE) {
$output = '
'."\n";
$required = $required ? '*' : '';
if ($title) {
// I've added the next two lines
$punctuation = array(',', '.', '?', '!', ':');
$colon = in_array($title[strlen($title)-1], $punctuation) ? '' : ':';
if ($id) {
// I've modified this next bit
$output .= ' '
. t('%title%colon %required', array('%title' => $title, '%required' => $required, '%colon' => $colon))
. "\n";
}
else {
// and this one too
$output .= ' '
. t('%title%colon %required', array('%title' => $title, '%required' => $required, '%colon' => $colon))
. "\n";
}
}
$output .= " $value\n";
if ($description) {
$output .= '
'. $description ."
\n";
}
$output .= "
\n";
return $output;
}
```
Sorry about the weird output. Some of the lines are a bit long.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Strategies for Patch Management"
url: "/articles/strategies-for-patch-management"
type: article
date: 2007-05-07
updated: 2016-04-07
---
# Strategies for Patch Management
# Strategies for Patch Management
Managing code patches
By
[ Angie Byron ](/about/angie-byron)
May 7, 2007
## Introduction
Of course, we all know the golden rule about Drupal: "If you're hacking \[modifying\] the code, you're doing something wrong." And in general, this is a very good rule to adhere to. When you want to modify the behaviour of Drupal, you should in almost all cases be able to either write a custom module to do what you want, or handle the modification at the theme layer.
However, sometimes we *need* to modify the code. There might be a bug in a contributed module that the maintainer hasn't gotten around to fixing yet, or we might need to back-port a core patch for the next version of Drupal in order to gain a particular feature or performance benefit. We already know that [forking code has a variety of severe disadvantages](https://www.lullabot.com/articles/best-practices-in-open-source-development), so how can we best ensure that we don't get bitten in the future, while still meeting the needs of our project today?
Quick note to the uninitiated: A "patch" is a file containing a list of all of the modifications to a piece of code. For more information, see [Drupal.org handbook page on patches](http://drupal.org/patch).
## Consequences of Making Sweeping Changes
At our last round of workshops, a student asked \[paraphrasing\], "I needed to get a site out the door quickly, so I made numerous modifications to a particular module. What is the strategy for giving those changes back to the community?" The answer, unfortunately, is that **you probably can't.** No module maintainer is going to take a huge patch that does 10 different things to the code: adding a new feature here, fixing a bug there, re-wording some text in places, fixing the coding style... And trying after the fact to remember what changes you made, why you made them, and trying to split them up in a distinct way so that they're independent of one another is a mammoth, time-consuming task, which takes time away from you doing your next project. So that honest intention of "giving back" never ends up materializing. And worse, when your changes are not applied "upstream" to the module, that means that you're now using a proprietary fork and that *you* are responsible for making sure those changes get re-applied on every update.
So, don't do that. ;)
## Making Customizations Manageable
When I start a new project, I create a "patches" directory, to store patch files that contain the modifications I've made. The patch files in this directory all have the following naming convention:
**UPDATE 2007-MAY-26:** Because drupal.org interprets # in the filename as a link fragment, updated the naming convention to use - rather than # to indicate the issue reply number.
> *module\_name*-*description\_of\_patch*-*Issue number*-*Issue reply number*.patch
So for example:
```
googleanalytics-hook_requirements-137536-0.patch
nodecomment-disable-comments-121357-3.patch
```
This tells me:
- Which modules have I modified?
- What the heck was I modifying in each case?
- Where can I check up to see if anything was ever done with this patch by the module maintainer?
- Which specific patch in that issue was I using? (Other patches could be contributed later that improve the patch I initially used.)
So each time I go to update Drupal or one of the modules I'm using, I spend 5 minutes going through the issues referenced by the patches in that directory and see how many of them I still need. Often, it's not too many... if the patch is already in the next version of the module, I can simply delete the patch file. If not, I know I need to re-apply that patch to the updated version.
## But There's No Issue ID!
What if I modified this module myself (rather than applying someone else's modifications) so there is no issue ID? **Go and make one.** And be disciplined about doing it **right now** and not putting it off until the end of your project, or even the end of your current coding stint... each distinct change you make needs to have an issue associated with it.
Doing this means there are more eyes on your code. Someone else might improve upon the patch you put out there, or the module maintainer might come along and say, "Did you know you don't actually have to do that, and you can do it this way instead?" The client benefits, because their site is infinitely more maintainable -- you don't have to skip module updates, which may contain critical security fixes, for fear of screwing up your customizations. Many of your improvements could make it to the "upstream" module, which means that there's now a community responsibility for maintaining those changes, rather than just your own. And you also essentially have built-in documentation for your project and how it differs from the norm.
So don't delay, maintain your patches the easy way, today. ;)
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "New Redesign of GRAMMY.com Just In Time for 54th Awards"
url: "/articles/new-redesign-of-grammycom-just-in-time-for-54th-awards"
type: article
date: 2012-02-10
updated: 2017-10-06
---
# New Redesign of GRAMMY.com Just In Time for 54th Awards
# New Redesign of GRAMMY.com Just In Time for 54th Awards
Lullabot powers The Recording Academy website for third year
By
[ Nate Lampton ](/about/nate-lampton)
February 10, 2012
When the 54th GRAMMYs begin this Sunday evening, millions of people will be glued to their televisions -- and a large number of them will also simultaneously be on their computers and smartphones to catch up with the online-only behind-the-scenes action. This type of web traffic calls for a responsive and robust site to handle it all.
Lullabot recently launched a new design for GRAMMY.com. The new site carries over all the functionality from the previous iteration, while adding all the responsive web design goodness users have come to expect from high-profile sites.
The Recording Academy's Kevin Colligan explains the importance of a responsive design for GRAMMY.com:
> This responsive approach isnât just cool, itâs vitally important because more and more people are surfing the web on smartphones and tablets. Over the past 30 days, about 17% of our visitors were on mobile devices. And we expect that percentage to rise steadily.
The GRAMMYs site provided many challenging scenarios in building a responsive design. The media-heavy nature of GRAMMY.com means that we had to work with not only resizing images, but also restructuring the page to fit appropriate advertising, Facebook comments, inline YouTube videos, and various positions of embedded Ooyala videos. Besides simply responding to the size of the screen, all video content also has to be HTML5 compatible of course, to help mobile devices that don't support Flash, such as iOS devices or the new Chrome for Android browser.
As always, Lullabot has done its due diligence in making sure the site is going to be able to handle this year's traffic with ease. By utilizing Akamai as a CDN, we expect to manage more traffic than ever during this year's show. Last year we were pushing over 2,500 megabits a second through the content delivery network. With wider device compatibility, more generously sized photos (one of the site's most popular feature during the show), and better social integration with Facebook, we expect to exceed that amount of bandwidth this year by keeping more users for longer visits.
For those interested in technical details:
- The design was done by [Lullabot's Jared Ponchot](https://www.lullabot.com/about/jared-ponchot). Most of the theming and CSS work was done in conjunction with Ben Brown and his company [XOXCO](http://xoxco.com/).
- We did *not* use a responsive base theme such as Omega or Responsive Theme to build the site. The complexities and variations in our layouts did not make an existing theme a very good fit.
- Almost all the videos on the site are powered by Ooyala, a streaming video provider. Lullabot has partnered with Ooyala in the past and we developed the feature-rich [Ooyala Module](http://drupal.org/project/ooyala) for Drupal.
- The site is built upon Drupal 6. This year's iteration has been an incremental update to the existing site. We're planning moving to Drupal 7 for next year's show.
For more information about the site architecture, you can listen to [Lullabot Podcast 92: Grammy.com](https://www.lullabot.com/podcasts/drupalizeme-podcast/grammycom). And if you can't wait for Sunday, the GRAMMY Live weekend video stream begins today. See you on the red carpet!
*Updated 2/12/2012: Corrected Akamai bandwidth of 53rd awards.*
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Oh no! My laptop just sent notifications to 10,000 users"
url: "/articles/oh-no-my-laptop-just-sent-notifications-to-10000-users"
type: article
date: 2013-03-20
updated: 2014-05-15
---
# Oh no! My laptop just sent notifications to 10,000 users
# Oh no! My laptop just sent notifications to 10,000 users
Preventing accidental announcements and other email tragedies
By
[ Andrew Berry ](/about/andrew-berry)
March 20, 2013
Email functionality is something we web developers often forget to account for when working on client sites. It's so easy to forget that almost everyone has a horror story sending out a mass email to a site's users from the test server, or local development environment. Luckily, there are a few different ways to manage how emails are delivered from a Drupal site -- and prevent unwelcome accidents.
## Method 1: Postfix Email Rewriting
Postfix is a popular mail server that's installed on many UNIX machines -- *including* the Mac OS X machines often used by developers. It supports the creation of rewrite rules that can channel email away from its original destination, and because it's so widely supported, this is my preferred method of preventing accidents. Postfix maintains a "canonical" database which is used to determine address mapping for local and non-local addresses. The canonical file is incredibly powerful, as it can use regular expressions to rewrite email destinations. Unlike the aliases or virtual files, the canonical file will rewrite mail headers as well. For more information, see the canonical man page. This method will redirect all mail sent, so it's great if you have non-Drupal applications on your system as well.
To set up the canonical address table:
1. Load up the terminal and change to the postfix configuration directory with `cd /etc/postfix`
2. Edit `main.cf` as root with your editor of choice: `sudo vim main.cf`
3. Add the following line to the end of main.cf:
`canonical_maps = regexp:/etc/postfix/canonical`
4. Create and edit a file, with `sudo`, called `canonical` if it doesn't exit. It should exist on OS X by default, but only contain comments.
5. At the end of the file, add a line with the following to redirect all mail to your local mail spool, filling in your user name:
`/.*@.*/ USERNAME@localhost`
6. Update the `canonical.db` file by running `sudo postmap canonical`
7. Test your mail rewrite rule by send a message with a command like `date | mail -s Test me@myrealemail.com`. Run `mail` from the command line to view your message.
It's possible to redirect messages to a real account somewhere. However, it can be tricky to set up depending on your ISP (most will filter outbound port 25) and you run the risk of being caught by spam filters. For development servers this shouldn't be an issue.
*Note for OS X 10.8 users*: If Postfix doesn't seem to be running for you, take a look at this [fix for Postfix](http://blog.deversus.com/2012/07/fix-for-postfix-in-mac-os-x-10-8-mountain-lion/). While postfix used to be an "on demand" service that would automatically run when needed, it was disabled entirely for me after upgrading from OS X 10.7.
## Method 2: Reroute Email
[Reroute Email](http://drupal.org/project/reroute_email) is a Drupal module that allows for both redirecting email and whitelisting specific addresses from rerouting. It's great for stage environments where QA or other team members might need to update rewrite rules. Simply enable the module, and browse to `admin/config/development/reroute_email` to configure it. One thing to keep in mind is that only the first email address is the redirect address. Subsequent addresses are whitelisted from rewriting. For example, in this configuration, qualityassurance@myproject.ca gets copies of all emails except those sent to admin@myproject.ca.

### Method 3: Devel's mail logger
The [Devel module](https://drupal.org/project/devel) ships with a replacement mail library that writes all emails to the watchdog instead of delivering them. This is great if emails can't be sent at all from a development environment, or when emails are plain text and don't need to be viewed in a mail client. Add the following to `settings.php` to enable the mail logger.
`$conf['smtp_library'] = 'sites/all/modules/devel/devel.module'; `
With these three methods, you never again will have an excuse to send out unintended emails from your development environment. What other tips have you used when debugging email notifications?
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ Drupal Site Building ](/topics/drupal-site-building)
- [ Deployment ](/topics/deployment)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Module-In-A-Box: We Built Admin Tools So You Don't Have To"
url: "/articles/moduleinabox-we-built-admin-tools-so-you-dont-have-to"
type: article
date: 2008-06-01
updated: 2014-05-15
---
# Module-In-A-Box: We Built Admin Tools So You Don't Have To
# Module-In-A-Box: We Built Admin Tools So You Don't Have To
By
[ Jeff Eaton ](/about/jeff-eaton)
June 1, 2008
Building a Drupal module from scratch can be remarkably simple -- just create an .info file, create a .module file, then implement a few functions like hook\_menu and hook\_nodeapi. In no time, you've got a module up and running, leveraging Drupal's APIs and adding functionality to your site.
### The problem
Unfortunately, things can get a bit more complicated if your module needs to store and maintain its own collection of data. The Custom Links module, for example, allows users to add clickable links to the bottom of each node on a Drupal site. While the code to actually add the links to each node is only a few dozen lines of PHP, it takes a few *hundred* lines of code to store and manage the information *about* those links. The module needs to create a database table to store its records, provide management pages so an admin can add new links, manage permissions, handle adding and editing records, request confirmation when administrators delete a record, and so on.

This pattern appears over and over in many Drupal modules. None of these tasks are horrible in and of themselves, but they add up to a lot of code, and a lot of special cases to overlook when you're busy focusing on the *real* functionality of a new module. And while Drupal.org provides a [a large selection of example modules](http://cvs.drupal.org/viewvc.py/drupal/contributions/docs/developer/examples/) that demonstrate the use of various APIs, none of them provide skeleton code for these common, repetitive back-end maintenance tasks.
None, that is, until now. Inspired by yet another evening of copying-and-pasting administrative UI code and trying to remember all the special-cases, I decided to clean up the 'template' code that I keep lying around for these situations, comment it thoroughly, and bundle it up as a re-usable example module. Angie "Webchick" Byron helped pore over the resulting code and made improvements to the style and documentation... and the result -- Scaffolding Example Module -- [is now available for download on Drupal.org.](http://cvs.drupal.org/viewvc.py/drupal/contributions/docs/developer/examples/scaffolding_example)

### Who Shot Who In The What, Now?
What do you get when you download Scaffolding Example module?
- CRUD
- Uses Drupal's Schema API and an .install file to define a custom database table.
- Provides an example update hook, to give users of your module an upgrade path if the database schema changes.
- Implements load/save/delete functions for the module's data.
- Demonstrates the use of Drupal 6's new drupal\_write\_record() function, to generate hassle-free insert and update SQL.
- Demonstrates the use of a Menu API 'auto-load' function, new in Drupal 6.
- Provides a 'batch' loading mechanism for all records, with notes on when and where to add caching if your module needs a performance boost.
- Administration
- Provides permissions and user access checks to keep out non-administrators.
- Provides an overview form that uses Drupal 6's new drag-and-drop system to reorder records in a table.
- Provides a dual-purpose add record/edit record form.
- Provides a standard confirmation form to delete records.
- Demonstrates Drupal 6's custom button callbacks, allowing each button on a form to trigger a different function when it's clicked.
- Uses swanky little edit and delete icons -- GPL'd icons, at that.
- Presentation
- Provides a simple listing page to display all records.
- Provides a simple themable function to render a single record..
In addition -- and this is the important part -- it does all of these things 'the Drupal way,' using standard API functions and presenting information in a way that's consistent with the rest of Drupal's core interface. An example is the confirmation form that's presented when an administrator deletes a record. I'm always forgetting the bits of syntax needed to use it properly, but using this Scaffolding module, the hard work is already done.

### Is there a catch?
Two caveats are in order. First, you'll still need to replace the 'example' portions of the module with your own code. Chances are, you want more than the 'title' and 'content' fields the module keeps track of. Changing hook\_schema() to set up the columns you need, and changing the add/edit form to manipulate them, is still in your court. Second, Scaffolding Example doesn't *do* anything with the data that it manages. It's up to you to build something on top of it, whether that's spitting out XML files, changing the site's breadcrumbs, or curing cancer.
Finally, there are portions of the code that may be unnecessary for you -- if your module's data doesn't need a 'weight' field, for example, there's no need to add the Drupal table-dragging code. That's a small tweak to the theme function, though, and is clearly marked in code comments.
### Go west, young coder!
Scaffolding Example module obviously can't solve every problem. But it does make it easy to fly through the 'grunt work' of building a simple administrative interface for your module. Download it, check it out, and if you can think of other improvements, post your ideas and your patches on Drupal.org!
***Update:** Shortly after publishing this article, several people jumped in and contributed code to make the Scaffolding module even better! Be sure to visit the [Drupal Developer Examples](http://cvs.drupal.org/viewvc.py/drupal/contributions/docs/developer/examples/scaffolding_example) collection for the latest version of Scaffolding module's code.*
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal Usability Testing Day 5"
url: "/articles/drupal-usability-testing-day-5"
type: article
date: 2009-03-01
updated: 2014-05-15
---
# Drupal Usability Testing Day 5
# Drupal Usability Testing Day 5
By
[ Addison Berry ](/about/addison-berry)
March 1, 2009
The usability team has been going non-stop with 13-hour+ days for the last four days in Baltimore. Yesterday we wrapped up the bulk of our testing and sifted through the results from Friday's marathon of tests (7 hours of testing, almost all back-to-back). Big hugs go out to [PingVision](http://pingv.com/) and [Matt Tucker](http://pingv.com/about/people/matt-tucker) (ultimateboy) for treating us to a nice Indian dinner last night. You can't know how much that was appreciated by a very tired and hungry crew. :-)
This morning we gave ourselves a bit of a break so we could sleep in and let our brains rest. We'll be meeting back at the lab around noon to prep for our last test subject of the study. After that test is done, we will do a big pass through all of our notes and stickies on the wall (we have some more from when I took [this picture on day 2](https://flickr.com/photos/add1sun/3312540611/)) to organize the issues and articulate them well enough to be added as issues to the Drupal.org issue queue. We also need to scrub all of the data and videos that we have to make sure no personally identifying information is there. We need to ensure the testers' anonymity. Once we pull all of that together, we will start to work on the Drupalcon DC presentation so we can share the big points that we learned in the last week and what we can do about it. We'll have audio and video clips along with information and issues to talk about. Hopefully the big snow (6-11 inches) headed our way tonight won't mess up continuing work on Monday too much, but thankfully all of the testing itself will be completed as of this afternoon. (You may wonder why some snow would bother people in a city too much, but Brab (beeradb) and Nat (catch) are staying at my house which is quite a distance from Baltimore and we drive 40 minutes to the lab each day.)
I'm going to post a wrap-up of the entire experience on Tuesday but so far, other than the main issues we have uncovered or reaffirmed, some of the big take-aways for me are how awesome people are, and seriously big respect for the work and effort of people who take part in usability studies, on both sides of the glass.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Using Lighttpd as a static file server for Drupal"
url: "/articles/using-lighttpd-as-a-static-file-server-for-drupal"
type: article
date: 2008-01-02
updated: 2016-04-07
---
# Using Lighttpd as a static file server for Drupal
# Using Lighttpd as a static file server for Drupal
An alternative file server for Drupal
By
[ Robert Douglass ](/about/robert-douglass)
January 2, 2008
This article discusses [Drupal 5.5](http://drupal.org/node/198523) and [Lighttpd 1.4](http://www.lighttpd.net/), with special consideration for the [imagecache module 5.x-1.3](http://drupal.org/node/152382).
Building websites that can handle high amounts of traffic involves finding points of scalability in the network architecture. There is a lot of discussion about database replication and redundant web servers, but very little discussion has taken place about serving static files from a different server than the one which executes PHP. This article shows how you can configure Drupal to serve static files from a separate server, potentially on a separate machine. There is even a solution for those of you who are using the imagecache module.
## Static vs Dynamic content
A webpage in your browser usually consists of HTML plus Javascript, images, CSS, and perhaps some Flash. The typical order of events is that the browser requests the HTML, parses it, and then begins to request the additional .js, .css, .png, .gif, and .flv files. This sequence is well diagrammed on the [Yahoo! Developer Network](https://developer.yahoo.com/performance/rules.html). For Drupal sites, the initial request that returns the HTML is a dynamic request, meaning PHP code and a database are required to generate the HTML. The rest of the requests, however, reference static files. These files require neither PHP nor a database and can be returned to the browser by the simplest and most lightweight web servers available. This is the fundamental difference between a dynamic request (one that requires a script language like PHP) and a static request (one which returns an simple file from the file system).
For a web server like Apache to serve a dynamic Drupal page, it must load extra software (mod\_php) in order to be able to execute PHP. This extra software increases the memory footprint of the server and reduces the total number of requests that it can handle before the machine's physical memory is exhausted. Even more memory intensive is the act of executing PHP. A Drupal site with lots of modules installed that handles a lot of data from the database can easily require 64M of memory per thread. This is a huge expenditure of memory compared to the 1-2M it takes to serve a static file. Since Apache recycles its worker threads, you end up in a situation where the same 64M monster that created the Drupal HTML is also used for serving a .jpg file. This is a huge waste of resources.
Adding a static file server to your network thus brings the following advantages:
- Static files are served from a server optimized for the task
- Better utilization of "heavy" PHP server resources
- A new point for scalability; you can add more machines to run static file servers if needed using typical load balancing techniques
## Sharing files
Where exactly are the static files in a Drupal site? Here's a list of the typical places:
- files/: Files uploaded by the application
- misc/: Drupal's Javascript files and some images
- modules/: Any module might have extra static files, such as .css, images, .js and so forth
- themes/: Most themes introduce .css and images
- sites/all/: More modules and themes can be found here
With Drupal's static files scattered throughout a directory structure that also contains all of the PHP files needed for Drupal execution, the idea of collecting them separately and putting them on a separate static server is impractical. The solution is to make the entire directory structure available to the static file server and disallow that server from serving requests for the PHP files.
How the files become available to the static file server is another question. One approach is to host the files on an NFS server which all web servers and the static file system mount. Another approach is to [use rsync to keep redundant copies](http://www.johnandcailin.com/blog/john/scaling-drupal-step-one-b-nfs-vs-rsync) of the entire directory structure available to every server. There are [other options](https://krisbuytaert.be/blog/?q=node/504) as well.
It is even possible to run the static file server on the same machine as the dynamic web server and have the two share a document root. This is the approach I take in this article as it demonstrates the principle adequately.
## Routing requests
The next issue is how should requests be routed? One approach would be to have a proxy server which routes requests for static files to a separate server. This leaves the application blissfully unaware of the concerns of the static file server. If you have experience with this approach please discuss it in the comments.
A second approach, which I take in this article, is to adjust the application to write the URLs to static resources differently. In Drupal this turns out to be a very simple task because all URLs are generated by a small number of functions. A minor tweak to these functions is sufficient to send all static file requests to the appropriate server.
Here is a survey of the changes that I needed to make to Drupal 5.5 and the Garland theme in order to serve all static files from a separate server. A patch with the complete set of changes is [attached below](https://www.lullabot.com/files/static-file-server.patch.zip).
Add a variable to $conf in settings.php:
```php
$conf = array(
'static_url' => 'http://static.example.com/'
);
```
In every function where static files get included in the HTML, update the logic to use the static\_url variable. This includes:
- includes/common.inc: drupal\_get\_css(), drupal\_get\_js()
- includes/file.inc: file\_create\_url()
- includes/theme.inc: theme\_get\_setting(), theme\_image()
```php
// use either the URL to the static server (if set) or the base_path()
$base = variable_get('static_url', base_path());
// Anywhere a resource is being included, use $base
$output .= ''. "\n";
```
For the theme, I added a variable to all templates called static\_base.
```php
// in template.php
function _phptemplate_variables($hook, $vars) {
$vars['static_base'] = variable_get('static_url', base_path());
...
```
The static\_base variable can then be used where files are directly linked in the theme. For example, in Garland's page.tpl.php:
## The static file server
I chose to use [Lighttpd](http://www.lighttpd.net/) (aka Lighty) to be the static file server based on its reputation for being lightweight and fast, and because I had never used it before. There are many web servers that can be optimized for the task, however.
I installed Lighttpd on Mac OS X (Leopard) using [MacPorts](https://www.macports.org/). After the package was installed I made the following changes to the lighttpd.conf file:
```
## This is the same document root as is used by the Apache server for Drupal
server.document-root = "/Users/robert/public_html/"
## Make sure that directory listings don't work.
index-file.names = ( )
## For the Mac OS X users
server.event-handler = "freebsd-kqueue"
## This plays a similar function to the .htaccess directive that hides certain file extensions.
url.access-deny = ( "~", ".engine", ".inc", ".info", ".install", ".module", ".profile", ".po", ".sh", ".sql", ".theme", ".tpl.php", ".xtmpl" )
## I want Apache to run on 80 so this needs to be something else
server.port = 81
```
I also added this to my .bash\_profile so that I could start lighttpd from the command line easily: `PATH=$PATH:/opt/local/sbin export PATH `
You may have to take the additional steps of adjusting your firewall to allow a process to bind to port 81, and some of the directories referenced in the lighttpd.conf file may need to be created.
Once you've finished with the above steps you can test Lighty's configuration with the following command: `sudo lighttpd -t -f /opt/local/etc/lighttpd/lighttpd.conf `
You can start the server with this command: `sudo lighttpd -D -f /opt/local/etc/lighttpd/lighttpd.conf `
A production instance of lighttpd will require some further configuration, most notably you'll want to use mod\_expire and mod\_compress to set expiry dates in the future, and to compress textual content for faster transfer over the wire.
## Turn off KeepAlive
One of the big gains that can be had by using a static file server is the freedom for your dynamic server to close the connection to the client immediately after serving the initial HTML. In your main web server's configuration you can now turn off the KeepAlive directive. For my setup, using Apache 2 (via MAMP), this involved adding the following line near the top of httpd.conf:
`KeepAlive = Off `
A restart of Apache is necessary.
## Using /etc/hosts
Your static files should always come from a different hostname than your dynamic HTML. This allows the browser to make more efficient use of its connections. On your local machine you can simulate this by editing /etc/hosts:
`127.0.0.1 localhost static `
This adds a hostname static that also resolves to the local server. Your $conf in settings.php will then look like this:
```php
$conf = array(
'static_url' => 'http://static:81/'
);
```
## Testing it out
With Drupal patched and Lighttpd up and running, you should have a Drupal site that gets its HTML from Apache and its static files from the static file server. Please describe any problems (and their solutions) that you run into in the comments below and I'll update the article accordingly.
## Imagecache
The above techniques will work will with any Drupal site that doesn't use imagecache. The [imagecache module](http://drupal.org/project/imagecache) presents a special challenge because it plays sneaky games with Drupal's 404 error handling. When Drupal receives a request for a resource that isn't on the file system and isn't a valid Drupal path, the Drupal application serves a 404 Not Found page, resulting in a full Drupal bootstrap. Imagecache takes advantage of this and generates image derivatives during this process. This means that imagecache requires requests for static images to come to Drupal - at least in the case when they are 404 Not Found.
To sidestep this problem we want Lighttpd to redirect any 404 requests to the Drupal server. In your lighttpd.conf file, change the following directives so that we can run a small Perl script to do the redirect.
```
## Uncomment the "mod_cgi" option from server.modules
server.modules = (
"mod_cgi",
...
## Add a 404 handler
## The path is relative to your Drupal installation
server.error-handler-404 = "/scripts/redirect.pl"
```
Now you must add a script to the scripts directory of your Drupal installation and make it executable.
Save this to scripts/redirect.pl
```
#!/usr/bin/perl
// Here localhost is the hostname for the Drupal server. Update so that your domain or hostname
// is used instead.
print "Location: http://localhost$ENV{REQUEST_URI}\n\n";
exit;
```
Update the URL in the script to use your hostname or domain instead of localhost, if necessary. The file must be executable by the user running the Lighty webserver. Now, when Lighty encounters a 404 request, it will be forwarded to the Drupal web server where imagecache will be able to make the derivative image. After that, Lighty will be able to serve requests for that image.
Please note that imagecache 2.0 is said not to need this workaround.
## Conclusion
Setting up a static file server to handle all non-dynamic requests is a moderately simple task that is well worth the while for sites that need to get the best performance and handle the most visitors. It provides a new point of scalability, manages existing server resources better, and can lead to overall faster page loads.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Announcing Drupal UE, the Usability Edition"
url: "/articles/announcing-drupal-ue-the-usability-edition"
type: article
date: 2009-04-01
updated: 2014-05-15
---
# Announcing Drupal UE, the Usability Edition
# Announcing Drupal UE, the Usability Edition
By
[ Jeff Robbins ](/about/jeff-robbins)
April 1, 2009
[ ](https://www.lullabot.com/drupal-ue/)

As many of you know, there has been a lot of focus and work put into [Drupal usability testing](http://www.google.com/search?q=drupal+usability+testing) over the past year or so. And we at Lullabot have been both interested and involved with as much of this as we can. It is fascinating to go out and talk to *real* people about what they *really* want to get out of a content management system.
And what have we found? What do people really want to do? Well it turns out they just want to put their stuff on the Internet. They want to put their thoughts on the Internet. They want to put their hopes and dreams on the Internet. They want to put their pictures on the Internet. They want to put their old junk on the Internet to sell it.
And they want it to be easy! They don't want to think about input formats. They don't want to think about user permissions and roles. They don't want to think about paths and path aliasing. They don't even want to know about nodes.
Now much of this usability research has been going into the [Drupal issue queue](http://drupal.org/project/issues/drupal) and the core development community has been doing what they can to try to accommodate the needs of *real* end users. But work has been slow going. There is so much legacy code and old-but-working methods of handling functionality. And all of this needs to be undone in order to implement newer, more usable functionality.
But what if we just dive in and start anew? What if we throw out concern for upgrade paths and database compatibility? What if we say Drupal has just gotten too confusing and it's time to flood it out, build our ark, and start fresh?
Well, this is exactly what we've done! We call it **Drupal UE**. The "UE" stands for "usability edition"... or maybe "user experience"... we haven't decided yet.
Now many people are going to criticize us for forking Drupal. I wouldn't call it a fork per se. I'd call it a "reworking"... or maybe a "fresh start". But we're very excited about its possibilities. Which leads me to the next announcement that [the Lullabot team](https://www.lullabot.com/about/team) will henceforth be dedicating itself exclusively to building and supporting **Drupal UE**. All of our future [workshops](https://www.lullabot.com/training) will be about **Drupal UE**. Our future [consulting](https://www.lullabot.com/about) will be about **Drupal UE**. All of our future [videos and DVDs](http://store.lullabot.com) will be about **Drupal UE**. And all of our future [podcasts](https://www.lullabot.com/resources?type%5Bepisode%5D=episode) will be about **Drupal UE**. We certainly value the time that we've given to the Drupal project and all of the wonderful people involved with it, but our company's future lies with **Drupal UE**. And speaking for the rest of the team individually, we lie as well.
It's sort of a shame really, because we've been so proud of our new [CCK and Views videos](http://store.lullabot.com/). But we need to face facts. These complex Drupal modules are a thing of the past.
We've been working on this project for months. And I'm sure we have months ahead of us before it's perfect. We hope that many in the Drupal community will see its significance and that **Drupal UE** will eventually build as large a following as Drupal.
So without further ado...
[Drupal UE](https://www.lullabot.com/drupal-ue) beta
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupalcon: Report from formal Drupal usability testing at the University of Minnesota Libraries"
url: "/articles/drupalcon-report-from-formal-drupal-usability-testing-at-the-university-of-minnesota-libraries"
type: article
date: 2008-03-03
updated: 2016-04-07
---
# Drupalcon: Report from formal Drupal usability testing at the University of Minnesota Libraries
# Drupalcon: Report from formal Drupal usability testing at the University of Minnesota Libraries
Drupal usability testing results
By
[ Angie Byron ](/about/angie-byron)
March 3, 2008
CODY HANSON: A lot to cover -- posting a lot of raw data on http://groups.drupal.org/usability -- How this got started -- He was putting together academic sites to find materials. Went to Barcelona, and noticed that Dries had prioritized usability. After 4th time hearing usability, he realized that they have a state of the art usability lab at the University of Minnesota. Approached Dries, and it got started from there. Excited about the network effect of this usability work.
CODY HANSON: Why would we do formal usability testing? Main reason is because none of us can "unlearn" how to use Drupal or forget what a node settings. People who care about Drupal the most, can't use it again for the first time. Got some volunteers w/ a one-way glass and an eye movement, mouse movement and cameras. They watched them, and it was frustrating to see how hard it was for the evaluators. They would watch what they were doing and saying out loud. The usability group had discussion about what they were going to test and how to test it. Drupal is highly customizable, and so they tested what would be relevant to most users -- CCK + core and the Garland theme. Tasks to get into CCK fields, and deal w/ user roles and permissions, and use taxonomies -- librarians, menu system and blocks. Be careful about tasks -- had to match their mental model of the evaluator. Like saying "Page" not "node." Have to avoid Drupal terminology -- otherwise it becomes a Word-find task. They also talked about "Personas" -- Use four spare different types: 1.) anonymous 2.) Content contrib 3.) site maintainer 4.) Site Admin It was too broad for everyone, and so they settled on the site admin who would deal with site admin and content admin. Finding evaluators, find ppl with experience with similar CMS type software -- movable type, Wordpress, but NOT used Drupal. These are "our" people -- not our mom, or our grandma who you expect to have problems. These are people we want to use Drupal, and could use Drupal
KAREN STEVENSON: What we see. Admin screen looks like a busy picture of Where's Waldo. 'Yowza!' -- open up node page and see all comment settings First task -- Started with a hard task -- could've been easier. First create a form to add a new content type and add new fields. Went to "Site Building" -- 'Content management doesn't sound right, maybe blocks.' Went to over and over again, but nothing on that site building page gave them a clue -- looking for "Form" or "Field" Unfamiliar language like "Content Type" Finally got to the content management panel -- Use "content" over and over again. Can't find what they're looking for, and went from content management panel and back to site building Got all hung up on what was a "Story" Not a single person got there without calling the help desk. One person backed out from the content types panel. The content types panel doesn't have any words that indicate that. They were looking for "previews, and didn't find it. Eye Tracking NEVER saw the Upper Right "FIELDS" screen. Clearly there's some problem with tabs -- theme is probably some issue Want to create forms, but don't know where they live. Completely confused by the word "content type" They were completely page by the Add content Type -- and what it meant. Mostly never found the fields tab, as an aside, it didn't even work when they got there. Were confused, and didn't know what they were doing, and then they started clicking EVERYTHING in site.
ANGIE BYRON: Personally shocked by that 6 of them thought that Content Types were Fields, and they were trying to get them back into the earlier created page.
KAREN STEVENSON: A page, hung onto that. \[But they were wrong\] Am I creating a page, thing for the page, confused. Confused page vs. story -- they thought they were the only options, and didn't think they could create it. They thought of page as something to create and put blocks and other stuff in. No one knew what story was. After 35 minutes after being pointed to it. Got them to the page to add a field, but they could never find it. They didn't notice the names -- CCK Field type name page was confusing. Saw node reference text and thought it created text. On the Create Node form -- Menu settings has been moved up to be right below the Name, and then they got caught up in it and
He put stuff in Parent item never understood. No one understood the "teaser splitter" and the whole concept entirely. Everyone understood Input Filters, and if didn't get what they wanted to see, then they backed out. Once preview, disappeared. Went to Homepage CHANGED after posting the content -- they didn't know why it was there. They were using that initial page as navigation, and were blown away when it disappeared.
NATHANIEL CATCHPOLE: Massive contrast of going through lots of pain, and they really found the user information quickly. Admin page was fine -- adding roles was easy -- people found the permissions immediately, and they knew exactly what they were looking for. Edit own vs. Edit Any was a bit confusing and giving people more permissions They usually went to access rules before user permissions. They would look alphabetically down before clicking, and access rules comes first. \[People still make that mistake\]. TASK 3: Classify content with taxonomy. Taxonomy admin page, they immediately got it -- but all of the users were librarians, People would read the help text, and not see where they can do what they read about it -- but what they wanted to do was in the upper right hand of the screen after looking all done. \[reading quotes\] They got and understood taxonomy very quickly. Vocabulary vs. Term -- only 1 immediately made appropriate vocab, and then terms. But others were putting terms in vocab. Only 3/8 people made it to task 3.
ANGIE BYRON: TASK 4 -- No one actually made it to task 4. I'm trying to get back to the screen with the step-by-step.
The big wall of text of the intro to your site page, they read EVERY SINGLE word, and use it as navigation, and it DISAPPEARS as soon as you post any content. It says that it uses context sensitive help, help was useless. No search in the homepage. Update status, menu, node are Drupal-based -- not task-based. Missing a glossary was removed because it's unmaintained. Wanted to define module and node. What I want to see is a simple HTML form builder People couldn't figure out what a module was. If you enable content page w/o the field modules, it throws you an error to enable one. The yellow box was jarring -- Update status box. People see the help text on the Modules page was too confusing. 'I see a lot of this CCK - what is it' Clicked field set, and they saw the word "Field" and clicked on field group. Saw green "enabled" and thought that they were already enabled. They had a help desk, people will do ANYTHING to avoid calling the help desk. They'll expand every single fieldset, enable every single module. Something in PHP.ini needs fixed "Interesting..." means complicated in MN The Administration panel page was too overwhelming to read all. Need a flash tutorial that shows what Drupal can do, and so they waste a lot of time clicking on the admin page. They focus on the Upper left -- and ABOVE the fold, because that's where people are looking. The little arrows on the side of the menu - and when you click on fieldsets they expand, but on menus they don't Site Building vs. Site Configuration, and they go to one first. 'I didn't expect to feel so people. I don't like feeling tested.' People take it personally 'I need a tutorial' -- is what people say over and over again. 'I already lost the page I just created' -- don't promote to front page. Didn't create a menu page. They had to call help, and tell them admin/content and it's there We take our mastery of the "suck threshold" for granted When stumped, they use brute force. TABS are invisible -- they'd look everywhere else except No one clicked on Content Type Looking for "forms" "fields" or any thing else Lots of work to change from "Node" to "content types" -- but it dilutes it and makes it a bit worse. get away from module-based help topics -- what do users want to do, and then build help around that. B/c site building, then they'd go to the block -- and started typing HTML forms into the blocks. It was easy for them to log in. The permissions page -- they could figure it out -- but looked at access rules first. Content manage User management was easier They taxonomy was shockingly easy for them But that's it -- those are the only easier
Teaser splitter -- they thought it was "cool" -- but they had no idea what a teaser. Need to work on that feature. Title -- menu settings -- body. People thought that it was a required field, and they'd
Asking for "Help" can Kill your data. All of the people were using IE7, which after clicking help, they'd go to a new page. After that people said, I'm not going to ask for help because that'll destroy my data. Password checker -- As you type. You get an error condition as soon as you start typing -- which was frightening to them. Organization of the admin page. Collapsible fieldset, they'll expand them immediately. Usability tests -- give them something easy first to build their confidence Seeing usability testing will change your outlook on Drupal. Every single person found new ways to get stuck, and ways to get out of being stuck. We need to change a lot in Drupal 7, because Drupal 6 is in string freeze. They're talking on groups.drupal.org/usability They're making a wiki page, and lots of issues Need help with the harder issues. Also summer of code is coming up.
NEIL DRUMM: Works at Advomatic BEVAN RUDGE: Civic Actions GREG KNADDISON: Really enlightening to him.
QUESTION: How much did this cost? CODY HANSON: Usability lab is part of the Office of Information Technology and they test enterprise projects. For them it was free, but they do charge. Amazon is trying to get some free or inexpensive lab space.
ANGIE BYRON: They only got a chance to test about 5% of Drupal core. Creating content types, user and taxonomy, and that's it. And there are a lot more things that you could do in various different scenarios.
QUESTION: Is there a document or link of proposed changes.
ANGIE BYRON: On the usability group on g.d.o. there will be a total spreadsheet, and it's more hodgepodge for creating issues. What we need is a mass army to convert that spreadsheet to the issue queue. And there will be a wiki page on the usability group.
CODY HANSON: Did post tasks and ideal path on the site as well.
NEIL DRUMM: A lot of the stuff we can't put directly into issues -- Issue queue is implementing solutions, and we have to discuss the possible solution first.
NEIL DRUMM: There is a heatmaps module as well.
NATHANIEL CATCHPOLE: go to Drupal.org and search for UMN, and you will find a tiny fraction of issues there.
QUESTION: Users weren't used to finding help in a module-way. What if you have a task that has several modules involved, what's the best procedure to have a test around a task.
CODY HANSON: It would have been nice to give people success early, but wanted to give something as functional as possible in creating content types. It's tricky, because real-world tasks span modules.
ANGIE BYRON: We should start a discussion on that because that is a real problem. Simple tasks can involve 3-4 problems, and so it's tricky to have consistent help and consistent UI.
NATHANIEL CATCHPOLE: Experienced users start to type in the correct URL path, but new users have scroll up and down and all over the place. If you're in content types, then there's no cross linking to posting new content. So if it's related, then putting more contextual links could help.
NEIL DRUMM: When you enable a module, it's often tricky to figure out what it does -- and so they do need their own pages. But we need a better way to have multiple module tasks flow together better
BEVAN RUDGE: URL bar -- not one of the evaluators looked at the URL bar for an indication. So thinking that you're hook menu is clearly indicating where they should go is not a safe assumption. They were reading a lot of the help text on admin and at the top of forms -- usually not enough links. Should have more because they're usually task-based.
QUESTION: Did you get a sense of whether to change the interface to work with non-Drupal terminology -- or to create more help text to educate people on the Drupal jargon. Should we rename "content type" again, or have more help text?
CODY HANSON: Answer is probably both. It's not that the vocabulary didn't match -- it's that the meaning didn't match their own mental model. Task-based tutorials is what is really going to help. They'll eventually internalize the vocabulary, and it'll be easier.
ANGIE BYRON: If you ask a web developer on the street, then they'll have no idea about what "content type is" They do know "form" or "web page"
NATHANIEL CATCHPOLE: probably a mixture. About 3 people wanted a video to show them what to do, but it wasn't there. With terminology -- sometimes it was on the admin page, but they just weren't seeing it. Issue is the thing like "story" and "content" are everywhere -- "story" is the most jargony. People knew what they were looking for, but didn't see it.
CODY HANSON: Concerned that Garland was too similar to drupal.org -- but they were able to tell the difference.
QUESTION: Aren't you results biased due to librarians? Roles admin page looks very similar, and they're obviously very familiar with taxonomy.
CODY HANSON: Certainly some bias, but there were some staff, and not all professional, full-time librarians.
QUESTION: (merlinofchaos) Concerned that they started with a really difficult task of creating forms -- problems with all systems and forms. We gave them a really hard task. It's at it's worst. Be cautious. Overreaching to invalid data is what changed "taxonomy" to "category"
CODY HANSON: It was really difficult, and still difficult no matter how familiar you are with Drupal. The kinds of changes that you'll find are very minor. None of us are thinking that we'll have some massive wholesale changes.
NEIL DRUMM: We should always make evolutionary changes, and not revolutionary. And go back and do more testing -- hopefully BEFORE they next release.
QUESTION: Site building is where they did go first. They were looking were they were supposed to. But we don't have enough info there. Need more links.
CODY HANSON: Yes.
QUESTION: Curious to know if after the testing, is this something that they would want to use.
CODY HANSON: Debriefing question: what would you tell a colleague. They realized that they were being put into an artificial situation. They realized the power and flexibility of the system, and used to working with static pages, and so they were helpful.
KAREN STEVENSON: Some people were excited to hear that it was more difficult to use because of the UI of Drupal and not them.
CHAD FENNEL: People were excited about the power, and saw that there was going a lot underneath the hood.
ANGIE BYRON: One participant ironically said that it's more like OSX and not DOS. They wanted more power to get more access to the raw HTML.
KIERAN LAL: One person said, I'm going to work on it over the weekend. Another said I'm going to the bar. And another person said, "This will be really good after it's released." (Laughs)
QUESTION: This is an advanced task that used to require a web programmer to do.
KAREN STEVENSON: Shouldnââ¬â¢t have put first task first. What screwed them up was creating a content type, and figuring out how to get started -- not adding a field. They got that pretty quickly.
QUESTION: Why didn't people see tabs?
NATHANIEL CATCHPOLE: Don't really know. Possibly the colors and the position. They tend to look down and not across.
CODY HANSON: There are no tabs on the default homepage.
KIERAN LAL: Start to reading tons of terminology, and it's done and they're over. And if we're forcing people to look through fishbowl glasses, then that's not good.
CHAD FENNEL: People wanted to walk through things and get a overall context as to where they were at. Some of the complex tasks like creating content types, they want to feel secure. There was also a vertical workflow, and so would not see the tabs.
QUESTION (chx) Drupal is too flexible, and so let's NOT take away features. The methodology. Their eyes didn't read them because the Garland theme isn't tab like.
CODY HANSON: We should test that, because that is very well the case. The one was ONLY one person who didn't see the tabs at all.
ANGIE BYRON: These are our people, and if they didn't see
QUESTION: Permission page was easy to use. When people
who would think to click on blocks -- maybe blocks need to be renamed.
CODY HANSON: That's actually the first person where one person went.
ANGIE BYRON: Probably that it's that block isn't a module.
NEIL DRUMM: User permissions was easy to deal with shows that maybe we should spend more time on content admin or content config.
QUESTION: mostly didn't have problem with users and making a role.
CODY HANSON: Role ended up being an optional step. There was difficult telling the difference between anonymous, authenticated and beyond. People were making insecure permissions.
NEIL DRUMM: People did see the registration options as well.
ANGIE BYRON: They didn't all know that authenticated users were people couldn't create their own account -- they thought that only admins could make users, and did some insecure things.
QUESTION: jstools for admin? Menu block, and ajax call from clicking on the arrow.
NEIL DRUMM: Need to sit down and talk about what happens when you submit a form. That's a research project to figure out what would be best to put into core to make the menu dropdowns more.
QUESTION: This is for a new user. Collapsible fieldsets, advanced users appreciate it. So how do you balance the design decisions for a first-time user vs. advanced user.
BEVAN RUDGE: We don't know. Am working on vertical tabs. Usability finds problems, and doesn't give solutions.
NATHANIEL CATCHPOLE: A few people want wizards to make content types, but no advanced users want to always use it. As people got to the end of the hour, they found stuff a lot quicker. And so they picked it up. Fieldsets are good and they save space, but they also dump stuff in the space, and it's there, and they will still have to look at it anyway.
QUESTION: What will you take and use in other projects beyond Drupal
KIERAN LAL: Use proper terms for the audience -- like say "web pages" and not "content"
BEVAN RUDGE: People don't use the URL bar. Vertical movement of the eyes are the biggest one.
BEVAN RUDGE: User experience goals, and how to repeat this. This is a draft to open up the discussion for how to do this and what should they be. High-level things that we should be aiming to achieve and consider when building UI for Drupal, and for expectations and goals for core. We should make "Where's Waldo" into more order.
1.) Measure the user experience -- gives us data to make the changes 2.) Consistency 3.) Understandable language 4.) Not feel overwhelming 5.) Give them informative feedback to move to the next task.
on http://groups.drupal.org/node/9252
Repeating this testing. We want to see this happening more. The first goal is to measure the user experience, and not guess about it. One of the way we can measure it is with usability testing, but we need labs and people to observer, facilitators to run the lab, and then interviewers to debrief the evaluators, and then resources to get everyone to the lab. Another way to measure is with informal usability testing, which can be as valuable. But it can be guided to be a lot more helpful. Something else we'd like to see is a set of tools to make informal testing more helpful -- the click Heatmap.module. Watch the Usability group for more details!
CODY HANSON: thanks for coming, and hope that the conversation continues throughout the week. Found a lot of issues, but we should be hopeful because the mental models of the evaluators actually matched Drupal's mental model very well.
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Humility in Design"
url: "/articles/humility-in-design"
type: article
date: 2012-07-12
updated: 2021-01-12
---
# Humility in Design
# Humility in Design
Humility is much more than an essential quality for life and relationships, it can also make you a better designer!
By
[ Tim Smith ](/about/tim-smith)
July 12, 2012
**Humility is often an underdeveloped quality. Some books refer to it as an essential personality trait. I wholeheartedly agree. However, humility is much more than an essential quality for life and relationships, it can also make you a better designer!**
Now, if you've read other pieces I've written, you know I'm a big fan of dictionary definitions. They can give another level of insight into a word. With that in mind, what does humility mean?
> "A modest opinion or estimate of one's own importance, rank, etc." âDictionary.com
That definition is quite interesting isn't it? It communicates the idea that weâre limited. However, it doesnât mean we should lack confidence in our abilities. Many confuse this quality as weakness when it really isn't. It's having the correct amount of confidence.
So now that we know what humility is, how does it help us become better designers? I'm all about being transparent so before we move on, I'll tell you that this is something I've had to work hard at. Here are a few things Iâve learned cultivating this quality and how itâs helped me become a better person and a better designer.
## Ask for Help
There is no shame in asking for help. It doesn't matter how many years we've worked as designers, we get stuck. It doesn't mean we don't know how to do our job, it means we're human. In my personal experience, I've seen that I hesitate to ask based on pride and honestly, when I look back, it's stupid. Asking for help is one of the many ways to improve.
Hearing from people who have more years of experience (or even if they don't) can help you see problems you hadn't thought of or details that you missed. The perspective of someone else is oftentimes vital to the creation of a better solution to a design problem.
## When Youâre Asked For Help
When others ask ***you*** for help, try your best to be approachable and helpful. It can be difficult for someone to ask for help.
Time for a little anecdote. When I was fifteen, I worked at a small college radio station in my hometown. Fifteen-year-old me was eager to learn, ecstatic to be working at a radio station (Iâve always loved broadcasting) and, Iâll admit, annoying. Iâve never really been shy so, I asked tons of questions. Yet, every time I asked a question, I was always looked at like, âUgh, here comes this kid again.â
As I look back, did I ask too many questions? Yes. But, itâs my belief, that everyone starts out that way. When you start out, you donât know anything. Itâs our responsibility to pay forward what knowledge weâve acquired and help those who need it.
> Give a man a fish and he will eat for a day. Teach a man to fish and he will eat for a lifetime.
Don't go creating mockups of how you would've solved the particular problem. As designers, we have to learn to think from different perspectives and let that influence the way we design. The growth of this ability is hindered when you do someone elseâs job. Explain your thoughts and offer constructive criticism on how to improve the solution.
## Recognize Your Mistakes
Now that you've asked for help, it's time to recognize the mistakes you made. More often than not, something will be wrong on a first attempt. Really, this applies to all situations. Sometimes you won't ask for help, you'll be presenting a design to clients and they'll give their thoughts and critique.
Trust me. Iâve made lots of mistakes and I know how tough it is to bite your tongue and realize youâve messed up. Here's what's really interesting about our brain. Often, we know when we're wrong but we decide to ignore and fight against it.
Don't do it! Recognize your mistakes and be willing to accept critique. This is critical to not only your design career but, life.
## Value the Opinion of Others
Great design is compromised by ego. Unfortunately, some designers and companies have made it popular to be arrogant. Arrogance doesnât serve you, your team or your clients; Value their opinions and contributions. No matter how talented you are, there are always other talented people out there, some even more than yourself. When I came to terms with this fact, which is even truer in my case, I began learning, maturing and improving my craft.
## Wrap It Up Tim!
To sum it all up, humility is definitely an important personality trait. Working towards this quality makes you a more likeable person and people will love working with you. Letâs be humble and make the web awesome.
## Related Bits
- [Humility: The Lost Art of Design](https://sparkbox.com/foundry/humility_the_lost_art_of_design)
- [Humble Experience Design](https://uxmag.com/articles/humble-experience-design)
Published in:
- [ UX & Design ](/topics/design-and-ux)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Tip: Using #pre_render in Multistep Forms"
url: "/articles/tip-using-pre_render-in-multistep-forms"
type: article
date: 2011-05-05
updated: 2019-01-11
---
# Tip: Using #pre_render in Multistep Forms
# Tip: Using #pre\_render in Multistep Forms
The Form API in Drupal is a complex and powerful system that touches nearly
every page in a Drupal site
By
[ Andrew Berry ](/about/andrew-berry)
May 5, 2011
The Form API in Drupal is a complex and powerful system that touches nearly every page in a Drupal site. Forms can be as simple as the search or login blocks commonly used. Or, they can be complex forms of interaction using [\#ahah](https://drupalcode.org/project/examples.git/tree/refs/heads/6.x-1.x:/ahah_example), [jQuery](http://drupal.org/node/171213), and [multiple steps](https://drupalcode.org/project/examples.git/tree/refs/heads/6.x-1.x:/form_example) to gather complex information while providing a usable and unique user experience. A common requirement for a multistep form is to have a different page title for each step of the form. This allows users to know what step they are on during a multistep process. In this article, we'll examine how FormAPI's #pre\_render functions can make that happen. A normal multistep form implementation will look something like this: In `site_join.module`:
```php
/**
* Implementation of hook_menu().
*/
function site_join_menu() {
$items = array();
$items['join/%membership_type'] = array(
'title' => 'Apply for a Membership',
'description' => 'Application form for new memberships',
'page callback' => 'drupal_get_form',
'page arguments' => array('site_join_application', 1),
'access callback' => 'site_join_application_access',
'access arguments' => array(1),
'file' => 'site_join.application.inc',
'file path' => drupal_get_path('module', 'site_join') . '/includes',
'type' => MENU_CALLBACK,
);
return $items;
}
```
In `includes/site_join.application.inc`:
```php
/**
* Form API callback for the membership form.
*
* @param $form_state
* The current state of the form.
* @param $membership_type
* The type of membership.
*/
function site_join_application($form_state, $membership_type) {
if (!empty($form_state['storage']['step'])) {
// We are beyond the first step of the form.
$form = $form_state['storage']['step']['callback'](#);
}
else {
// Start the form.
$form = site_join_application_member_validation($form_state, $membership_type);
}
return $form;
}
/**
* Form API callback for the member validation step of the application form.
*
* @param $form_state
* The current state of the form.
* @param $membership_type
* The type of membership.
*
* @return
* A form API array.
*/
function site_join_application_member_validation(&$form_state, $membership_type) {
// If we are on this form step, set the page title.
drupal_set_title(t("Validate your membership"));
$form = array();
// Build your form in $form here.
return $form;
}
```
This works really well, until you want to start reusing the form generation code as a smaller part of another form. Each form function needs to know if it should set the page title or not. So, we add a third parameter to our form function:
```php
/**
* Form API callback for the membership form.
*
* @param $form_state
* The current state of the form.
* @param $membership_type
* The type of membership.
*/
function site_join_application($form_state, $membership_type) {
if (!empty($form_state['storage']['step'])) {
// We are beyond the first step of the form.
// The form determines if the step should set the page title by
// setting 'set_page_title' to TRUE.
$form = $form_state['storage']['step']['callback'](#);
}
else {
// Start the form.
$form = site_join_application_member_validation($form_state, $membership_type, TRUE);
}
return $form;
}
```
```php
/**
* Form API callback for the member validation step of the application form.
*
* @param $form_state
* The current state of the form.
* @param $membership_type
* The type of membership.
* @param $set_page_title
* Optional parameter to indicate that this form is the "primary" form for
* the page and should set the page title.
*
* @return
* A form API array.
*/
function site_join_application_member_validation(&$form_state, $membership_type, $set_page_title = FALSE) {
if ($set_page_title) {
drupal_set_title(t("Validate your membership"));
}
// The rest of your form function goes here.
}
```
Running this code will initially look to work fine. Unfortunately, it will break when your form throws a validation error. Drupal caches the output of form functions and uses the cached output when rebuilding a form that has failed validation. This means that `site_join_application()` is never called, and `drupal_set_title()` never gets a chance to override the page title on the rebuilt form.
## \#pre\_render to the rescue!
Luckily, Drupal provides a few FAPI properties that will get called every time a form is built. One of them is [`#pre_render`](http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/6#pre_render). `#pre_render` is called every time before an element is rendered with [`drupal_render()`](http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_render/6). Using a `#pre_render` callback, we can ensure that the page title is set when needed for any page.
```php
/**
* Form API callback for the member validation step of the application form.
*
* @param $form_state
* The current state of the form.
* @param $membership_type
* The type of membership.
* @param $set_page_title
* Optional parameter to indicate that this form is the "primary" form for
* the page and should set the page title.
*
* @return
* A form API array.
*/
function site_join_application_member_validation(&$form_state, $membership_type, $set_page_title = FALSE) {
if ($set_page_title) {
$form['page_title'] = array(
'#type' => 'value',
'#value' => t('Validate your membership'),
'#pre_render' => array('site_join_form_page_title'),
);
}
// The rest of your form function goes here.
}
/**
* FAPI #pre_render callback to set a page title.
*
* We have to do this for multistep forms as when a field fails validation, the
* form is pulled from the form cache. This means that we never get a chance to
* call drupal_set_title(). We also can't do it from hook_menu()'s title
* callback as hook_menu() doesn't know anything about the state of the form.
*
* @param $element
* The FAPI element who's value contains the page title to set.
* @return
* The modified element that was passed in.
*/
function site_join_form_page_title($element) {
drupal_set_title($element['#value']);
return $element;
}
```
Even though the element hasn't been modified, remember to return it as the function calling `#pre_render` expects it. With this method, we can easily set the page title to any value by creating a #value element and setting its #pre\_render to the `site_join_form_page_title()` function. Although it's easy to miss, FormAPI's #pre\_render functions can be a powerful weapon in your page-tweaking arsenal.
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Building Views Query Plugins, Part 2"
url: "/articles/building-views-query-plugins-part-2"
type: article
date: 2013-09-04
updated: 2014-05-15
---
# Building Views Query Plugins, Part 2
# Building Views Query Plugins, Part 2
Writing and testing the plugin itself
By
[ Greg Dunlap ](/about/greg-dunlap)
September 4, 2013
Welcome to the second installment of our three part series on writing Views query plugins! In part one, we talked about the kind of thought and design work that needs to be done before coding the plugin begins. In this part, we'll start coding our plugin and end up with a basic functioning example.
While writing this article, I realized that creating a functional remote data integration for Views involves not only the query plugin, but also field plugins to expose that data to Views, and potentially filter or argument plugins to limit result sets. That's a lot of code to write, so lets get to it!
## Getting Started
There's a joke about writing Views plugins - 10% of the time is spent copying and pasting code, 10% is spent changing class names and array keys, and 80% is spent finding the typo. While probably not strictly true, it does highlight the fact that when you're writing a Views plugin, naming is everything and a single typo in a class name can cause endless grief. In fact, I've actually moved away from a copy/paste paradigm for these plugins, instead building up all the structures by hand with the help of some snippets in my editor. It forces you to actually think through all the keys involved, and vastly reduces the time spent finding all the keys to replace, and the debugging time spent when you miss one somewhere.
There are a lot of steps here, and keep in mind that you will need to do them all in order to begin to actually use your plugin or see any results. This is one of the reasons that writing a plugin can be frustrating - you have to write a ton of very interdependent code before you can even start testing it.
### Step 1: Implement hook\_views\_api()
All that we are doing here is declaring what version of Views we are coding to. Note that this is the only code that is going in our .module file, everything else is loaded on-demand through includes. This is what my hook implementation looks like and yours will be exactly the same, except of course using your own module's name instead of flickr\_group\_photos.
```php
/**
* Implementation of hook_views_api().
*/
function flickr_group_photos_views_api() {
return array(
'api' => 3.0
);
}
```
### Step 2: Create a views.inc file
Once hook\_views\_api() is implemented, Views will automatically look for a file named \[module\].views.inc in your module's home directory. Plugins, handlers, and other information are exposed through hooks implemented in this file.
### Step 3: Implement hook\_views\_plugins()
The first thing we need to do is describe our plugin to views. This is done by implementing hook\_views\_plugin() and returning an array of the format $array\[plugin\_type\]\[plugin\_name\]. The 'title' and 'help' keys pretty self-explanatory, and will be used in the Views UI and the plugin settings forms. However the 'handler' deserves some close attention. This is the name of the class that you will eventually create to manage queries to your remote service. It should be descriptive, and it should contain the name of the implementing module. We have chosen 'flickr\_group\_photos\_plugin\_query' here, and also used this as the plugin name in our array just for consistency. *Remember this name*, you'll be using it a lot in the future.
For more details, check out the [API documentation for hook\_views\_plugins()](https://api.drupal.org/api/views/views.api.php/function/hook_views_plugins/7).
```php
/**
* Implementation of hook_views_plugins().
*/
function flickr_group_photos_views_plugins() {
$plugin = array();
$plugin['query']['flickr_group_photos_plugin_query'] = array(
'title' => t('Flickr Groups Query'),
'help' => t('Flickr Groups query object.'),
'handler' => 'flickr_group_photos_plugin_query',
);
return $plugin;
}
```
### 4) Implement hook\_views\_data()
Usually hook\_views\_data() is used to describe the tables that a module is making available to Views. However in the case of a query plugin it is used to describe the data provided by the external service. The format of the array is usually $array\[table\_name\]\['table'\], but since there is no table I've used the module name instead. This array needs to declare two keys - 'group' and 'base'. 'group' is used as a prefix in the Views UI anywhere this plugin's data is referred to. Then the 'base' key is used to describe this as a base table for views, in that it is a core piece of data that Views can be built around (just like nodes, users, and the like.) This data is essentially the same as you described above in hook\_views\_plugins(), except that it is used in the Views UI whenever you need to choose what kind of data to show. Also pay close attention that the 'query\_class' key is the same name as you used in hook\_views\_plugins() for the 'handler' key. If not, things won't work well (see how those fat fingers can mess you up!)
For more details, check out the [API documentation for hook\_views\_data()](https://api.drupal.org/api/views/views.api.php/function/hook_views_data/7).
```php
/**
* Implementation of hook_views_data().
*/
function flickr_group_photos_views_data() {
$data = array();
// Base data
$data['flickr_group_photos']['table']['group'] = t('Flickr Groups');
$data['flickr_group_photos']['table']['base'] = array(
'title' => t('Flickr Groups'),
'help' => t('Query Flickr groups.'),
'query class' => 'flickr_group_photos_plugin_query'
);
return $data;
}
```
### Step 5: Expose fields
The data you get out of a remote API isn't going to be much use to people unless they have fields they can use to display it. Fields are also exposed in hook\_views\_data(). The declaration is very similar to the one in hook\_views\_plugin() - you provide a title, help text, and the name of a handler class for your field. In an ideal world, you could just use one of the default field classes provided by Views and be on your way. However, when working with remote data, there is a change to the query() method that needs to be made. Therefore, what we we will do is subclass the Views base field class with a new class called flickr\_group\_photos\_handler\_field and make our changes there. This class will be fine for basic text data, and when we need to handle more complex data, we will extend that class.
Just to get started, lets define a simple text field for the title of a photo. We'll add the following to hook\_views\_data(), making sure it is above our existing return statement!
```php
// Fields
$data['flickr_group_photos']['title'] = array(
'title' => t('Title'),
'help' => t('The title of this photo.'),
'field' => array(
'handler' => 'flickr_group_photos_handler_field',
),
);
```
As you can see, this is very similar to our plugin declaration. We have a key of $data\[module\_name\]\[field\_name\], along with some information fields. The title and help fields will be used wherever information about this field is displayed, and we discussed the handler class above.
Next we create our class. This class file should be named \[class\_name\].inc and can live anywhere within the module's root. I like to put plugins and handlers into their own directories, so this is handlers/flickr\_group\_photos\_handler\_field.inc
```php
/**
* @file
* Views field handler for basic Flickr group fields.
*/
/**
* Views field handler for basic Flickr group fields.
*
* The only thing we're doing here is making sure the field_alias
* gets set properly, and that none of the sql-specific query functionality
* gets called.
*/
class flickr_group_photos_handler_field extends views_handler_field {
function query() {
$this->field_alias = $this->real_field;
}
}
```
Finally there is one more very important step. We need to add a reference to this file into our module's .info file, otherwise the autoloader will have no idea how to find it.
```
files[] = handlers/flickr_group_photos_handler_field.inc
```
Forgetting to do this is one of the most common pitfalls I've encountered when building views plugins. It's a very small thing, but very important.
### Step 6:Create a class that extends views\_plugin\_query
After all that setup we're almost ready to finally start interacting with a remote API! We just have one more task to do, and that is to create the class for our query plugin. As mentioned above, I like to put these into a plugins directory. We already named this class above so we need to create plugins/flickr\_group\_photos\_plugin\_query.inc, create a class flickr\_group\_photos\_plugin\_query that extends views\_plugin\_query, and again add it to the files array in our .info file. Here is the shell of our class
```php
/**
* @file
* Views query plugin for Flickr group photos.
*/
/**
* Views query plugin for the Flickr group photos.
*/
class flickr_group_photos_plugin_query extends views_plugin_query {
}
```
and our .info file entry
```
files[] = plugins/flickr_group_photos_plugin_query.inc
```
At this point, if you've done everything right and you clear Drupal's cache, you will be able to create a new View and see your new data type available for Views to use. It won't actually **do** anything, but this is a good place to stop and verify that everything you've done so far is correct. That way if you encounter problems later, you'll at least know that all your setup stuff was good, and it will reduce the potential points of failure.
### Step 7: Override the query() and execute() functions
There are two things we need to do in our query class. First we need to override the query() method with an empty one. In normal views operation this is where SQL queries are constructed, and since that doesn't apply to us, we just eliminate that functionality.
```php
function query($get_count = FALSE) { }
```
That was easy. Now for the fun part! We override the execute() function to retrieve our data and save it into a specific format for views. This format is an array of row objects, with properties named the same as the field name we used as they key when we declared the field in hook\_views\_data(). So in this first example, where we are only returning the photo title, we just need objects with a 'title' property. Here is what the code looks like.
```php
function execute(&$view) {
$flickr = flickrapi_phpFlickr();
$photos = $flickr->groups_pools_getPhotos('2221193@N21', NULL, NULL, NULL, NULL, 20);
foreach ($photos['photos']['photo'] as $photo) {
$row = new stdClass;
$photo_id = $photo['id'];
$info = $flickr->photos_getInfo($photo_id);
$row->title = $info['photo']['title'];
$view->result[] = $row;
}
}
```
The most noteworthy thing about this code is how simple it is after all the setup we did. The first thing we do is create a $row object, where we will store all the data we need for this 'row' of data.
As a reminder, we are using the Flickrapi module to simplify our interaction with the Flickr service. Flickrapi provides a flickr object that defines a function for every Flickr API call, with the dots replaced with underscores. So the groups.pools.getPhotos API function becomes a groups\_pools\_getPhotos() function on the flickr object.
There are a couple of parameters of this call that are worth noting. The first is the ID of the group you are retrieving photos from. In this case, the ID is for the Lullabot Team group, where we store our team photos. The last parameter is the number of photos to retrieve per page. Obviously it would be nice if these parameters could be configured through the Views UI rather than hardcoded, but I set them this way for the sake of simplicity. We will look at how to add query configuration options in the second part of this article.
The groups\_pools\_getPhotos() function returns an array of photos, however as discussed above, this does not contain the data we need for our purposes. In order to get the photo title, we need to call the photos.getInfo API function (or photos\_getInfo() on our flickr object.) This also returns an array of data, which includes our photo's title. This title gets saved in our $row object, and then the row object is saved in the results array of the view object that is passed as a parameter to this function.
That's it! We should now have a simple but fully functioning query plugin that can interact with a flickr group from Views. After installing this module, you should be able to create a new View of type Flickr Group, add a Title field, and get a listing of photo titles.

## Debugging problems
The most common problem you will encounter is seeing the message 'broken or missing handler' when attempting to add a field or other type of handler. This pretty much always points to a class naming problem somewhere. Go through your keys and class definitions and make sure that you've got everything spelled properly, including your include file names and your files\[\] definition in the .info file.
For debugging actual functionality, watchdog() is your best friend. Writing results to the screen with dpr() or the like will play havoc with the ajax calls in the Views UI, but writing to the events log will get you everything you need.
## Summary
Most of the work here actually has nothing to do with interacting with remote services at all - it is all about declaring where your data lives and what its called. Once we get past the numerous steps that are necessary for defining any plugins, the meat of creating a new query plugin is pretty simple.
- Create a class that extends views\_query\_plugin
- Override the query() function to do nothing
- Override the execute() function to retrieve your data into an object with properties named for your fields, and save that object into the results\[\] array on the views object.
In reality, most of your work will be spent investigating the API you are interacting with, and figuring out how to model the data to fit into the array of fields that Views expects.
## Next steps
In the third part of this article, we'll look at the following topics:
- Exposing configuration options for your query object
- Creating advanced field plugins for displaying images
- Creating optional filter plugins
Stay tuned!
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Building Views Query Plugins"
url: "/articles/building-views-query-plugins"
type: article
date: 2013-08-29
updated: 2021-01-12
---
# Building Views Query Plugins
# Building Views Query Plugins
Part 1: Mapping web service data to the Views model
By
[ Greg Dunlap ](/about/greg-dunlap)
August 29, 2013
While we were building [the new Lullabot site](https://www.lullabot.com), we decided that we wanted a slideshow of 'bots out and about doing their thing. Kicking ass and having fun at events is a big part of the Lullabot culture, and we wanted to show it off. We thought it would be fun to show a slideshow of the 20 or 30 most recent photos from [the Lullabot team pictures Flickr group](https://www.flickr.com/groups/lullabot-team/) on our Who We Are page, to give a taste of what its like to be a bot. However, we didn't want to import all those photos into our site, wasting storage and duplicating data. Well, you know what they say: the Views module is the cause of, and solution to, all life's problems!
Beginning with Views 3, you can write your own plugin to replace Views' built-in SQL query engine. This means that you can make Views query against any kind of data source. The most common use case is to create Views that query remote web service; that sounded like a great match for our needs, and it's what we're going to explain.
This topic is pretty deep, so to keep things manageable I've divided it into three parts:
- Planning and modeling your data
- Creating a basic query plugin
- Exposing configuration options and handling arguments and filters
While this is not meant to be an in-depth guide to writing Views plugins, these articles will touch on a variety of different plugin types, and after reading it you will have at least a general understanding of how Views plugins work in addition to all the steps to create a query plugin specifically. [The Drupalize.Me series Coding For Views](https://drupalize.me/course/coding-views-drupal-7) is an excellent resource for a more general understanding of the Views architecture.
Let's get started!
## Modeling your plugin data
One of the first things you need to do before coding your plugin is sit down and think about how the data being returned from your API maps to the data that Views expects. Views is designed to represent tabular data, the basis of which is a row of fields. Many API endpoints do not follow this model. For example, a single request may contain a lot of nested data. When writing a query plugin for the last.fm events API, I found that the results for a single event contain a nested list of places to buy tickets for that event. This obviously doesn't map well to the row/field model. The proper way to handle this would be to create a relationship plugin to link that data to its event. However I didn't want to add the complexity, and the data wasn't that important, so I simply chose not to expose it in my plugin.
With Flickr I had the opposite problem - the data I wanted was scattered across multiple API functions, each one requiring a single request. Lets look at this example in detail, since that is what we're going to build here. In the end, we will want to generate a list of photos from a group, with the following information about each photo
- The photo's title (for alt/title tag use)
- The URL of the source image
- The URL of the photo's Flickr page (for linking each image)
- The photo itself, run through a specified image style
From a Views standpoint, we want to expose each of these pieces of data as a field. So lets see what we need to do to get all these pieces of data from the Flickr API. To get photos from a group pool, we need to use the [flickr.groups.pool.getPhotos API call](https://www.flickr.com/services/api/flickr.groups.pools.getPhotos.html) call. This returns a set of photos for a specified group. However it does not give much data about the photos other than their IDs.
Looking through the API some more, it appears we can use [flickr.photos.getInfo](https://www.flickr.com/services/api/flickr.photos.getInfo.html) to get the photo title and a link to the photo page, but even there we still can't get the URL of the photo itself. In order to get *that* we need to call [flickr.photos.getSizes](https://www.flickr.com/services/api/flickr.photos.getSizes.html) and choose which size we want. It seems like the list of sizes is unpredictable, but assumedly every photo will have an 'Original' size, so we'll use that as a standard choice.
So what does this mean for our views plugin? Well, in order to get the four fields we want, we will have to get a list of photos from flickr.groups.pool.getPhotos, then for each photo we will need to call flickr.photo.GetInfo **and** flickr.photo.getSizes. Once we've done that, we will have the info we need. The downside, of course, is that if we want to grab 20 photos from the group, we need to make 41 API calls (two per photo plus one for the group listing.)
This is pretty typical of the query plugins I've written. The APIs are rarely written to conveniently map data to a single row as neatly as Views might need it. This is why it is worth taking sometime to investigate the API you are using and figuring out how the data it provides maps to your use case **before** sitting down and starting to write any code around it. In some cases you might find the data you want isn't available at all, or that the data needs some significant massaging before it can be used the way you want it.
## Other considerations
Given that we will have to be making a large number of API calls in order to get data about a single photo, we will probably want to investigate a caching strategy to reduce round trips. Another consideration I had at the beginning of the project was that I preferred not to interact with the API directly, but to instead use a module or library that abstracted a lot of that work away for me. In particular, it would be nice not to have to write the Flickr authentication code myself.
Thankfully Drupal contrib provided a solution to both these problems. The [Flickr API module](https://drupal.org/project/flickrapi) provides an object that wraps all the functions in the Flickr API, as well as providing built in caching and easy authentication through the Drupal admin UI. What an enormous amount of code I no longer have to worry about! It's really great when you find a module like this that does exactly what you need.
**Note**: in order to use Flickr APIs and the flickrapi module you need to [create a Flickr API key](https://www.flickr.com/services/api/).
## Conclusion
When writing any piece of sufficiently complex code, taking the time to think about your problem and the best way to solve it will pay great dividends down the road. Writing a set of Views plugins is no exception. You need to think about the data you have, the data Views expects, and how to deal with the complications that arise when the two don't fit together perfectly. If you're designing your own system from scratch, you have the luxury of building the APIs just so to fit your desired use case. Sadly, life is rarely so neat. Resist the temptation to dive straight into code, and first figure out what it is you need to build.
In part 2 of this series, we'll go through the steps of building our plugin, ending up with the simple use case of displaying a list of photo titles. Stay tuned!
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Views Query Plugins, Part 4"
url: "/articles/views-query-plugins-part-4"
type: article
date: 2014-03-20
updated: 2014-05-15
---
# Views Query Plugins, Part 4
# Views Query Plugins, Part 4
Building custom pager plugins
By
[ Greg Dunlap ](/about/greg-dunlap)
March 20, 2014
Welcome to the fourth part of our series on writing Views query plugins! I hadn't planned on writing a fourth installment, but after a couple of people asked to see how pagers could be implemented, I couldn't resist putting an example together. As a reminder, we have been building a query plugin to interact with Flickr groups. You can read the [first](https://www.lullabot.com/articles/building-views-query-plugins), [second](https://www.lullabot.com/articles/building-views-query-plugins-part-2), and [third installments](https://www.lullabot.com/articles/building-views-query-plugins-part-3) in the series and [find the code on GitHub](https://github.com/heyrocker/flickr_group_photos).
## Before we start
There are three pieces of data you need in order to implement a pager:
- The number of items to display per page
- The total number of items available
- What page of items you are currently displaying
Most modern APIs support paginated queries, and in general this shouldn't be a problem. If the API you are working with does not support paging, it's *possible* to retrieve the full set of results and handle paging yourself in the plugin. That approach takes a lot more work, and careful implementation to avoid performance problems: Building the pager *that* way is outside the scope of this article.
Thankfully, the Flickr API which we have been working with supports paging. We are good to go!
## Implementing the pager
Once you know that your API supports paging, adding the code to your query plugin is surprisingly easy. You need to implement the following steps:
- Initialize the pager
- Retrieve the settings for items per page and current page
- Perform your query
- Save the total number of results back to the pager
Here is how it looks in our plugin
```
// Setup pager
$view->init_pager();
$flickr = flickrapi_phpFlickr();
$photos = $flickr->groups_pools_getPhotos($this->options['group_id'], NULL, NULL, NULL, NULL, $this->pager->options['items_per_page'], $this->pager->current_page + 1);
$this->pager->total_items = $photos['photos']['total'];
$this->pager->update_page_info();
```
The first thing we do is call `init_pager()` on our view. This not only does setup work on our pager, but gives us a copy of it in our query plugin at `$this->pager`.
Now that the pager is setup, we can use its settings in our query. In the Flickr API call for `groups.pools.getPhotos`, the sixth and seventh parameters are the number of items per page and the current page. So for those parameters we have specified `$this->pager->options['items_per_page']` and `$this->pager->current_page + 1`. Note that +1 for the current page, we need that because Drupal's pages are zero-based but Flickr's are not.
Once this query is executed, we can retrieve the total number of items from the result and save that back to the pager by setting the total\_items property and calling `update_page_info()`.
Amazingly, that is all you have to do! You should now have a nice pager when you view your results.

As an added bonus, back in Part 3 of the series, we implemented a configuration option which controls how many photos are displayed. Now that we have the pager setup, we can remove that configuration option, because the pager option "Display a specified number of items" will handle that for us.
## Gotcha
I know what some of you are saying. "What about the offset option?" Unfortunately the Flickr API does not support this option. If I really wanted to, I could override the existing pager plugins and remove the option from the form so that users would not be confused by it. However, I will leave that as an exercise for the reader. In the meantime, any entry of an offset will simply be ignored.
## Looking for more?
What else would you like to see implemented in our query plugin? Let me know, and maybe the series will continue!
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal 5: Making forms that display their own results"
url: "/articles/drupal-5-making-forms-that-display-their-own-results"
type: article
date: 2006-11-22
updated: 2014-05-15
---
# Drupal 5: Making forms that display their own results
# Drupal 5: Making forms that display their own results
By
[ Jeff Eaton ](/about/jeff-eaton)
November 22, 2006
Drupal's Form API offers advanced features for validation, processing, and (in version 5) multi-part forms that span several pages. Sometimes, though, you need to do something simple: write a form that does nothing but display some formatted information based on the data that was submitted.
Here's a short snippet of code that demonstrates how Drupal 5's new #multistep flag (used for complex, multi-page forms) can also simplify the little problems.
```php
function multistep_example_form($form_values = NULL) {
$form = array();
// Setting #multistep to true activates some special form
// handling code for Drupal. First, the FormAPI makes sure
// that the results of the form's submission are passed back
// into the builder function when the page loads for the second
// time. That gives it a chance to build a different version of
// the form (with additional options, for example). It also
// does magickal voodoo that keeps validation working properly
// in those complex multi-page forms. For now, we won't
// worry about that.
$form['#multistep'] = TRUE;
// Setting #redirect to false means that Drupal *won't* attempt
// to reload a clean copy of the form (by redirecting to the
// current page, and reloading) when it's submitted. In a multistep
// form, we want to keep the data around so it can be displayed,
// or passed on to a subsequent step.
$form['#redirect'] = FALSE;
// Remember: in a #multistep form, Drupal makes sure that the
// $form_values array is passed into this builder function once
// you submit the form. That gives us a chance to display different
// form fields based on what was previously submitted.
if ($form_values === NULL) {
// We're entering the form for the first time. Display the form!
$form['text'] = array(
'#type' => 'textfield',
'#title' => t('Text field'),
'#required' => TRUE,
);
$form['more_text'] = array(
'#type' => 'textarea',
'#title' => t('A bigger text field'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
}
else {
// $form_values is populated, which means we're coming in a second
// time. Let's display the results instead of the original form fields.
$form['results'] = array(
'#type' => 'item',
'#title' => t('The results of your form submission'),
'#value' => _multistep_example_format_values($form_values),
);
}
return $form;
}
function _multistep_example_format_values($form_values = array()) {
$header = array(t('Key'), t('Value'));
$rows = array();
foreach ($form_values as $key => $value) {
$row = array();
$row[] = $key;
$row[] = check_plain($value);
$rows[] = $row;
}
return theme('table', $header, $rows);
}
```
That's all there is to it! Obviously, most modules will need to do some more complex formatting than that when they display their results. But the skeleton is there, and should serve you well.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Image and Image Exact Sizes vs. Imagefield and ImageCache"
url: "/articles/image-and-image-exact-sizes-vs-imagefield-and-imagecache"
type: article
date: 2006-09-28
updated: 2016-04-07
---
# Image and Image Exact Sizes vs. Imagefield and ImageCache
# Image and Image Exact Sizes vs. Imagefield and ImageCache
Sizing images with Drupal
By
[ Angie Byron ](/about/angie-byron)
September 28, 2006
### Introduction
Suppose you have a site such as an e-commerce site, and you want to upload an image for each product. Suppose further that you have several sizes you want that image to be displayed in, depending on where you are in the site. For example, you might want to have a thumbnail displayed in a product listing, a larger version on the product page itself, and a middle size for displaying in a custom View. And finally, suppose that you'd really rather have Drupal take care of this resizing for you and not have to upload 3+ images for each product you create, so you can spend more time slacking off at work and less time clicking buttons. ;)
There essentially are two different ways to have Drupal do this:
1\. Use the [Image](http://drupal.org/project/image) and [Image Exact Sizes](http://drupal.org/node/51818) modules.
2\. Use the [imagefield](http://drupal.org/project/imagefield) and [imagecache](http://drupal.org/project/imagecache) modules.
This article will compare and contrast these methods, so you can pick the one most appropriate to your needs (or both!). Read on to find out more!
### Image and Image Exact Sizes
The main difference between Image and imagefield module is that Image module stores images as nodes. That means that each product you create will have two nodes created for it: the product itself and the image attached to the product.
The first step (after enabling the **image**, **image\_attach**, and **image\_exact** modules is to go to **administer >> settings >> image** and enter the sizes you want for each image:

Next, you'll want to go to **administer >> settings >> image\_exact** and check off the options for which sizes you wish image\_exact to enforce:

Note that **these settings will apply to *each* image that you create**, regardless if they belong to a product or not.
Creating images first and then creating the product and then linking the two together would be a tedious process. Fortunately, Image module comes with a great little helper module, **image\_attach** which allows you to attach an image to any existing node type. Here's a screenshot from a CCK "product" node's configuration page when image\_attach is enabled:

When enabled, this causes an upload field to appear when you create a node:

After you submit the node, the system will automatically create your image node and resize the images appropriately in the background for you. This will create a number of images:
- **example.png** - the original image
- **example.thumbnail.png** - the thumbnail-size image
- **example.preview.png** - the preview-size image
- **example.XXX.png** - an additional image for each size you have defined.
Note that currently, **image\_attach only supports attaching one image per node**.
Finally, Image module's Views integration allows you to display the image at whatever size:

### imagefield and imagecache
imagefield module is different, in that it allows you to add an "image" type field to any CCK node type:

This aspect gives imagefield **two important advantages**:
1\. As many different images can be attached to the node as you want; you could create an image field for product image, another one for "action shot," etc. 2. Each image field may also have multiple images attached to it. So instead of being limited to one image per product, you may now attach 3-4 images that show different views of the product.
The disadvantage is that **this module may only be used with CCK types**, unlike image module which may be used for any node type.
Unlike Image Exact Sizes which supports resize action only, imagecache supports cropping, scaling, and resizing, so its interface is a bit more complex than that of Image Exact Sizes.
The action starts at **administer >> imagecache**, where you create a **namespace** for a set of rules, and then apply one or more **actions** to each namespace. Actions may be weighted, so for example you could crop an image to 500x500 before resizing it to 200x200.
Here's a screenshot of some actions as an example:

And here's a screenshot of the node/add/content\_product page:

Now. The thing to watch is that by default, the original sized image is shown when you view a product node. This can be HUGE. ;) The answer is to put some custom code in your theme to take advantage of the resizing. Here's a sample node-content\_product.tpl.php:
```
">
// Rather than printing $content, we can print fields individually.
print '
'. $node->field_description[0]['view'] .'
';
// Here we're printing out the imagecache-manipulated image.
print theme('image', 'files/imagecache/product_images/'. $node->field_product_image[0]['filepath']);
```
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Building Views with Fivestar and VotingAPI"
url: "/articles/building-views-with-fivestar-and-votingapi"
type: article
date: 2008-09-30
updated: 2014-05-15
---
# Building Views with Fivestar and VotingAPI
# Building Views with Fivestar and VotingAPI
By
[ Nate Lampton ](/about/nate-lampton)
September 30, 2008
\[embed\]http://blip.tv/file/2512306\[/embed\]
This videocast covers three modules, wrapped together to provide a flexible solution for displaying information about content ratings in a list.
**VotingAPI**: Central storage of votes and rating information.
**Fivestar**: A flexible widget for registering votes on a 1-10 star basis.
**Views**: The ultimate Drupal query builder, capable of pulling out lists of information from the database.
In Drupal 6, the options in configuring views has become drastically more complex. This videocast helps understand how to setup views that display information about the current average rating for piece of content and also how to pull in an individual users results, each displayed as Fivestar widgets.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Analyze This! Using the Google Analytics API"
url: "/articles/analyze-this-using-the-google-analytics-api"
type: article
date: 2010-01-11
updated: 2023-11-02
---
# Analyze This! Using the Google Analytics API
# Analyze This! Using the Google Analytics API
Karen Stevenson gives a detailed overview of the Google Analytics API
By
[ Karen Stevenson ](/about/karen-stevenson)
January 11, 2010
[Google Analytics](https://marketingplatform.google.com/about/analytics/) is a great way to monitor site usage and traffic. You add Google Analytics to your site using the [Google Analytics](http://drupal.org/project/google_analytics) module, which is super simple to set up. After it's in place, you can go to the Google Analytics site and dig into a ton of data, create custom reports, etc. But you can also use the Google Analytics API to pull Google statistics into your own site and display them there. There is a Drupal module, [Google Analytics API](http://drupal.org/project/google_analytics_api) that was created by Joel Kitching as a Google Summer of Code project. It provides a wrapper you can use to create tailored queries of your analytic data. You can turn on the included 'Google Analytics API Reports' module to display Google statistics in blocks or pages right on your site, and/or create custom code to suck in specific statistical data and do any Drupally thing you like with the results.

Turning on the reports module gives you a taste of the things you can do. It requires that you install the [Chart](http://drupal.org/project/chart) and [Country API](http://drupal.org/project/country_api) modules. Once turned on, you will see a tab on all your nodes called 'Statistics', which will display several charts of recent Google Analytics data for that node. You also will see a new Statistics block that you can add to your site which will display charts of Google Analytics data for whatever page the block is displayed on. So that much is cool, but you can do much much more. To do much with it, you will need to create some code customized for the way your site is designed, and you will want to dive into the Google Analytics API documentation. One really great tool Google provides is a [Data Feed Query Explorer](http://ga-dev-tools.appspot.com/explorer/?csw=1) where you can sign into your own account and select metrics and filters to pull out any kind of custom data you like. So, say that I want to create a block of the most popular links in the last 24 hours that I can feature on the front page of the site. I start by creating a simple request array that looks like this:
```
// Build the data request.
$request = array(
'#dimensions' => array('pagePath'),
'#metrics' => array('pageviews'),
'#filter' => 'pagePath!=/',
'#sort_metric' => array('-pageviews'),
'#start_date' => date('Y-m-d', time() - 86400),
'#max_results' => 10,
);
```
In this code I'm requesting the number of page views grouped by page path, filtering out the home page, sorted by page views (descending), starting 24 hours ago and limiting my results to the first 10 items that match my request. Once I construct my request, I pass it to the API, which will return me an array of result objects which I can manipulate to get the dimensions and metrics I requested.
```
$items = array();
$data = google_analytics_api_report_data($request);
foreach ($data as $page) {
$dimensions = $page->getDimensions;
$metrics = $page->getMetrics;
$items[] = $dimensions['pagePath'] .' ('. $metrics['pageviews'] .')';
}
print theme('item_list', $items);
```
Obviously, if I want to make these into nice links, I need the page titles. I can use some Drupal functions to get more information about those paths. Let's say I only want to create links to these items if they are nodes, and in that case I need to get the page title for the link. ` // Strip the leading slash or base_path so we have a // normal-looking Drupal alias. $alias = substr($dimensions['pagePath'], strlen(base_path())); // Get the 'real' Drupal path for this item. $path = drupal_lookup_path('source', $alias); // If it's a node, get the title. if (arg(0, $path) == 'node' && is_numeric(arg(1, $path))) { $id = arg(1, $path); $title = db_result(db_query("SELECT title FROM {node} WHERE nid = %d", $id)); $items[] = l($title.' ('. $metrics['pageviews'] .')', $alias); } ` The API allows for simple regex filters, so I can search for statistics for only paths that start with /taxonomy/ (the tilde (~) means it is a regex):
```
// Build the data request.
$request = array(
'#dimensions' => array('pagePath'),
'#metrics' => array('pageviews'),
'#filter' => 'pagePath=~^/taxonomy/',
'#sort_metric' => array('-pageviews'),
'#start_date' => date('Y-m-d', time() - 86400),
'#max_results' => 10,
);
```
Or I can find the top pages visited by people from the United States, limiting the results to those that had the substring 'American' in the title:
```
// Build the data request.
$request = array(
'#dimensions' => array('pagePath'),
'#metrics' => array('visits'),
'#filter' => 'pageTitle=@American && country==United States',
'#sort_metric' => array('-visits'),
'#start_date' => date('Y-m-d', time() - 86400),
'#max_results' => 10,
);
```
Once you get started you will find it helps to have an easy way to play with your queries to make sure you are getting the results you expect. This is where Google's [Data Feed Query Explorer](http://ga-dev-tools.appspot.com/explorer/?csw=1) really helps. You can create a query in the explorer, and then use it to set up the right values in your request.

Note! The Drupal API makes a few changes to the raw Google API that confused me for a while. The Google API prefixes 'ga:' to each data element. When using the Drupal module you leave that off, the module adds it to each element before sending the request to Google. The Google API uses a semicolon for AND and a comma for OR, and the Drupal module uses && for AND and || for OR. Once I figured that out, I was able to use the Google tool to model a custom query and then adapt the values to create a request in Drupal. Also note that there are lots of ecommerce tools here. If you have an ecommerce site that uses Google's ecommerce tracking code, you have dimensions, filters, and metrics available for things like product skus and even revenue. One thing that quickly became apparent is that it is really really important to have meaningful information in your page titles and paths. Google has no information about the source of the data and only knows the pagePath and pageTitle. If you want to look for specific content types and there is nothing in either the path or title that tells you what kind of content it is, you will have no easy way to specify the right information in your query. A perfect partner for Google Analytics API is the [PathAuto](http://drupal.org/project/pathauto) module. With PathAuto, you can create automatic aliases for all your page paths. So you could change 'node/10' into something like '\[type\]/\[title-raw\]', which would give you a path that includes the content type. Then you could create a Google Analytics query that filters out paths that match your desired content type, and that will allow you to get aggregate data, like pageviews, by content type. If the page title is in your aliased path (\[title-raw\]), you can also easily reconstruct the title from the path when you create links without any need to do local queries to find the original item:
```
$title = ucfirst(str_replace('-', ' ', $alias));
print l($title, $alias);
```
There are a few caveats here. The Google Analytics API module is new and still in development, so you'll want to pick up the latest code and check the issue queue for possible patches. If you find this interesting (I sure do!) jump in and help polish this useful module.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal's search module and scoring factors"
url: "/articles/drupals-search-module-and-scoring-factors"
type: article
date: 2007-03-29
updated: 2016-04-07
---
# Drupal's search module and scoring factors
# Drupal's search module and scoring factors
Ranking search results
By
[ Robert Douglass ](/about/robert-douglass)
March 29, 2007
This article applies to Drupal 5.x.
In this article I will show how the results of the search module can be fine tuned using controls available to Drupal site administrators. The search module's configuration options include up to four extra parameters called scoring factors for weighting search results based on keyword relevance, recency, number of comments, and the number of page views. It will be shown that adjusting these values can dramatically alter and improve the order of search results. We will then add a theme function to enhance the themed search items by displaying their score. Finally, we will extend the advanced search form to include the scoring factor controls so that every search can be custom tailored with regards to the scoring algorithm.
## Scoring factors
Four types of scoring factors are available to Drupal administrators:
- relevance of keyword
- recency (created, changed, last comment)
- number of comments (if comment module is turned on)
- number of page views (if statistics module is turned on AND if *Count content views* is enabled. See admin/logs/settings)

*The search module's scoring factors (admin/settings/search)*
If you don't see the page views scoring factor, it means you don't have the statistics module enabled and configured properly. Enable the statistics module and make sure that *Count content views* is also enabled.

*The statistics module needs to be enabled and Count content views turned on in order for the page view scoring factor to work.*
The weights given to each scoring factor have a profound effect on the order of search results, and it is well worth your while testing different values in order to achieve the best possible search result ranking. The scoring factors can be changed at any time and take effect immediately. There is no need to re-index your site.
## Four different nodes
In order to demonstrate the affect of scoring factors on scoring I have created four nodes, each of which scores especially high with one scoring factor. The first node has the word *Drupal* in both the Title and in the Body. Since the Title field gets extra weight (due to being wrapped in an <h1> tag), and also due to the fact that Drupal appears twice in the node, this node will score very high for the keyword relevance scoring factor when searching for Drupal.
The second node contains the word Drupal in the Body, and also has a comment. As it is the only node that has a comment, it will score the highest for the comment count scoring factor.
The third node has been viewed 50 times, whereas the others have each been viewed only once. Node #3 will score the highest for the page view scoring factor.
Finally, the fourth node is the newest, being created after all of the others. Thus, node #4 will score highest for the recency scoring factor.
In summary, there are four nodes, each of which is designed to have a special advantage over the others in one scoring factor.

*With four nodes and the default scoring factor weights, searching for "Drupal" favors the node with a comment over the others.*
## Displaying the score
In order to better observe how search results are ranked, we will now override the theme\_search\_item function and extend it to output each search item's score. Seeing the scores of items and watching them change in response to various score factor weights will help you decide which settings are optimal for your site.
To display the score on each themed search item, add this function to the template.php file in your theme's directory. If you are using the Garland theme, for example, this function should be added to /themes/garland/template.php.
/\*\* \* Format a single result entry of a search query. This function is normally \* called by theme\_search\_page() or hook\_search\_page(). \* \* @param $item \* A single search result as returned by hook\_search(). The result should be \* an array with keys "link", "title", "type", "user", "date", and "snippet". \* Optionally, "extra" can be an array of extra info to show along with the \* result. \* @param $type \* The type of item found, such as "user" or "node". \* \* @ingroup themeable \*/ function phptemplate\_search\_item($item, $type) { $output = ' ['. check\_plain($item\['title'\]) .']('.%20check_url(%24item%5B'link'%5D)%20.')'; $info = array(); if ($item\['type'\]) { $info\[\] = $item\['type'\]; } if ($item\['user'\]) { $info\[\] = $item\['user'\]; } if ($item\['date'\]) { $info\[\] = format\_date($item\['date'\], 'small'); } if (is\_array($item\['extra'\])) { $info = array\_merge($info, $item\['extra'\]); }
// Add the score to the list of items displayed in search results $info\[\] = $item\['score'\];
$output .= ' '. ($item\['snippet'\] ? '
'. $item\['snippet'\] . '
' : '') . '
' . implode(' - ', $info) .'
'; return $output; }

*Two lines have been added to the theme\_search\_item function.*
Now when you search, each search result will display its score. Here is the search results page for a search on *Drupal* with the four nodes I have created and default values for all of the score factors.

*Overriding theme\_search\_item allows us to see how each node has scored in the ranking algorithm.*
## Boosting keyword relevancy
When looking at the search results for *Drupal* using the default scoring factors, it is noteworthy that node #1 ranks second in the results. Why? Because it has the word *Drupal* in the title and in the body. While this guarantees that node #1 will score highest in the keyword relevancy factor, it seems that overall, the comment count factor (or some other aspect of the scoring algorithm) favors comments more than keywords. Lets boost the keyword relevancy scoring factor by +2 and repeat the search.

*By boosting the keyword relevancy scoring factor, the node with Drupal in the title now ranks first in the results.*
## Adding the scoring factor widget to advanced search
Drupal's advanced search feature lets you construct many specific and interesting search queries. You can, for example, search for all Page nodes that have the taxonomy term *Politics* but not the word *Bush*. This is one realm where Drupal consistently beats the search results delivered by external search engines such as Yahoo! or Google. Drupal simply knows more about its own content and is thus more capable of searching through it in a structured manner.
Drupal doesn't give you any options for how to sort or score the search results. Since the score factor weights are only used during the actual searching, and not during indexing, there is nothing stopping us from applying custom factor weights to every search. We will now add the score factor weight controls currently found in the search administration section to the advanced search form so that any user can tweak the weights to get the search results they are most interested in.
The node module uses the HTML Analyzer and Indexer provided by the search module to implement Drupal content searches. The node module adds the advanced search form to the basic search form in its implementation of hook\_form\_alter. Thus we turn to node\_form\_alter to add the score factor controls to the advanced search form.
```php
// Grab the administration form from node_search
$factors = node_search('admin');
// Get rid of the help text because it takes up too much space
unset($factors['content_ranking']['info']);
// Get rid of the fieldset
$form['advanced']['factors'] = $factors['content_ranking']['factors'];
// Wrap the form elements in a div to hold them together.
$form['advanced']['factors']['#prefix'] = '';
$form['advanced']['factors']['#suffix'] = '';
```
*Code added to node\_form\_alter to add scoring factor controls to advanced search.*
The node module handles the validation of the advanced search form in the node\_search\_validate function. This is where all of the various conditions, such as taxonomy terms, node types and NOT keywords are turned into a keyword query that is usable by the search module. We will extend node\_search\_validate to also store information about the user's scoring factor preferences in the session.
```php
if (isset($form_values['node_rank_comments'])) {
$_SESSION['node_rank_comments'] = $form_values['node_rank_comments'];
}
if (isset($form_values['node_rank_relevance'])) {
$_SESSION['node_rank_recent'] = $form_values['node_rank_recent'];
}
if (isset($form_values['node_rank_views'])) {
$_SESSION['node_rank_relevance'] = $form_values['node_rank_relevance'];
}
if (isset($form_values['node_rank_recent'])) {
$_SESSION['node_rank_views'] = $form_values['node_rank_views'];
}
```
*Code added to node\_search\_validate to store scoring factor preferences during searhing.*
The need to store these preferences stems from the fact that the search module accepts a POST request from the search form and then resubmits the form resulting in a GET request with the keyword query in the URL. It is on the second GET request that the search is actually executed and the initial POST values are not available. The POST-to-GET redirect is to enable bookmarking of searches and is one of Drupal's nice features. It means, however, that the POST values for the scoring factor are not available at the time the search query is built. The solution chosen here is to put them into the $\_SESSION variable until the are used, at which point they are removed from the $\_SESSION. The alternative would have been to make them actual search query terms, as is done with all of the other advanced search form elements. This option resulted in long search queries. The merits of both approaches can be discussed further, but the approach using the $\_SESSION is the one being used for this article.
Upon the GET redirect, the node module builds a specific search query in node\_search. Here is a sample of the code from that function which make use of the scoring factor values stored in the $\_SESSION.
```php
$weight = $_SESSION['node_rank_relevance'];
unset($_SESSION['node_rank_relevance']);
$weight = empty($weight) ? (int)variable_get('node_rank_relevance', 5) : $weight;
if ($weight) {
// Average relevance values hover around 0.15
$ranking[] = '%d * i.relevance';
$arguments2[] = $weight;
$total += $weight;
}
```
*Code from node\_search which takes $weight first from the $\_SESSION, and otherwise from the default variable\_get().*
In the code above, $weight is the scoring factor. It is first taken from the session variable. If that has not been set, then the traditional value is taken from variable\_get(). The weight is then used to construct a SQL snipped which is used in the final search query.
The [patch containing all of the code for this feature](https://www.lullabot.com/files/advanced-search.patch) is attached. It applies to Drupal 5.1.

*The advanced search form with the scoring factor controls added.*
One goal of this article is to encourage Drupal administrators to experiment with the scoring factor controls. It would be interesting to hear from others which combination of values works best. Another goal of the article is to introduce the idea of having the scoring factor controls present in the advanced search form. Feedback on this idea, its implementation, and the results is very welcome. Drupal's built-in search module has a lot of potential, but some configuration may be needed before it returns optimal results.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Private forums in Drupal: Forum Access vs. Taxonomy Access vs. Taxonomy Access Control Lite"
url: "/articles/private-forums-in-drupal-forum-access-vs-taxonomy-access-vs-taxonomy-access-control-lite"
type: article
date: 2007-03-12
updated: 2016-04-07
---
# Private forums in Drupal: Forum Access vs. Taxonomy Access vs. Taxonomy Access Control Lite
# Private forums in Drupal: Forum Access vs. Taxonomy Access vs. Taxonomy Access Control Lite
Which module should you use for creating private forums in Drupal?
By
[ Angie Byron ](/about/angie-byron)
March 12, 2007
## Introduction
Most people who use forum systems such as vBulletin or PHPBB are used to having lots of extra features that Drupal core's forums don't contain by default, including private messages, smilies, and BBcode. While all of those are available as contributed modules, there are two "must-have" forum features that are a bit trickier, since they deal with access control: private forums, and forum moderators.
Drupal core tends to have an "all or nothing" approach to these issues. Either a particular role can access *all* content on the site, or they can access none of it. Either a particular role can administer *all* forums, or they can administer none of them. Luckily, though, Drupal provides a number of hooks so that contributed modules can add in their own robust access handling.
This article will look at three modules that enhance forum privileges, and compare and contrast them: [Forum Access](http://drupal.org/project/forum_access), [Taxonomy Access Control](http://drupal.org/project/taxonomy_access), and [Taxonomy Access Control Lite](http://drupal.org/project/tac_lite).
## Use case
Our use case is as follows:
We have four roles: anonymous user (users who aren't logged in), authenticated user (users who are logged in), moderator (users who moderate forums), and administrator (users who administer the site as a whole).
We also have three tiers of forums: administrative forums, that only members in the 'administrator' or 'moderator' roles can view and post to, member forums, which both anonymous and authenticated users can see, but only authenticated users can post to, and guest forums, which anyone can see and post to.
The 'moderator' role should be able to administer the member or guest forums, but only members in the 'administrator' role can moderate the administrative forums.
Here's a summary ([click for larger view](https://www.lullabot.com/files/forum-access-use-case.png)):
[ ](https://www.lullabot.com/files/forum-access-use-case.png)

## Primer on Drupal forum terminology
A quick vocabulary lesson: In Drupal, the forums are made up of three things:
- **Forums** themselves, which translate into technical terms as **taxonomy terms** in a **taxonomy vocabulary**
- **Forum topics**, which in Drupal-speak are **nodes**
- **Forum replies**, which "under the hood" are **comments**.
Therefore, when we talk about modules that can provide private forums, we refer to modules that can **restrict access to forum topics and their replies** based on **the forum in which the topic was posted**. In technical terms, we are talking about **node access modules** that use **taxonomy** in order to determine who has access to do what.
Just throwing this at you now, so when you see these words appear again in places below, it's not as scary. ;)
## Forum Access
Forum Access is a module specifically designed for the problem at hand. It requires the [ACL (Access Control List)](http://drupal.org/project/acl) module, and is for 5.x only. As of this writing, the newest versions are Forum Access 5.x-1.7 and ACL 5.x-1.3. The ACL module installs three tables (acl, acl\_node, acl\_user), and the Forum Access module one (forum\_access).
You modify Forum Access settings by clicking "edit forum"/"edit container" from **Administer >> Content management >> Forum** (admin/content/forum).
For containers, you can specify which roles have permission to view the container (which affects the visibility of all forums beneath it), as well as a list of users who may act as moderators (this doesn't appear to have any effect at this level):

For forums, you can specify which roles have view, post, edit, and delete permissions, as well as the users who may act as moderators. You need to configure both the container and all sub-forum permissions, or else your users will receive an "access denied" message when they go to look at an individual forum ([click for larger view](https://www.lullabot.com/files/forum-access-forum.png)):
[ ](https://www.lullabot.com/files/forum-access-forum.png)

Forum Access restricts the list of forums to only those the user has access to:

**Pros**: Forum Access is an easy to use module that does exactly what it says it should. The terminology it uses is specific to forums, making it an easy jump for people used to maintaining bulletin board systems. It supports moderation both by role (by giving a role edit and delete permissions on a forum) and by username.
**Cons**: Configuring permissions can be tedious if there are many forums, as there is no global default setting, and permissions made at the container level don't appear to cascade down to sub-forums. Forum Access does not support controlling access through taxonomy other than the Forums vocabulary; if your needs are more advanced, one of the Taxonomy Access modules may be a better fit.
## Taxonomy Access Control
Taxonomy Access Control (TAC) provides extremely fine-grained permissions over any taxonomy vocabulary, including Forums. There are both 4.7.x and 5.x versions. At the time of this writing, there were no official releases of TAC. It installs two database tables: term\_access and term\_access\_defaults. It also includes an uninstall routine, so that you can remove the module after experimenting with it if you need to.
You configure permissions at **Administer >> Users >> Taxonomy Access permissions** (admin/user/taxonomy\_access). Each role has its own permission screen, containing a fieldset for each taxonomy vocabulary.
There's a lot going on with this screen. Essentially, you are describing the contexts in which a given role can **View** forum topics, **Update** (all) forum topics, **Delete** (all) forum topics, **Create** (post new) forum topics, or **List** the containers and forums from the main Forum screen.
Here's an annotated screenshot of how the anonymous role is configured for the above use case ([click for larger view](https://www.lullabot.com/files/taxonomy-access-permissions.png)):
[ ](https://www.lullabot.com/files/taxonomy-access-permissions.png)

Because Taxonomy Access permissions cascade, if the forum container is not enabled for Listing or Creating, any sub-forums will also be inaccessible. Therefore, both the container and its sub-forums will appear when a new forum topic is posted:

**Pros**: Extremely powerful: fine-grained permissions, and applies to any vocabulary in the system, so can have totally separate access permissions for forums vs. stories, etc. Allows bulk editing of forum permissions (once per role), unlike Forum Access which requires editing forum permissions once per forum.
**Cons**: User interface is extremely complicated; not easy for people who just want to maintain private forums. Unlike Forum Access, containers are in the Forum list when a new forum topic is created. Not possible to create moderators by username; only roles.
## Taxonomy Access Control Lite
Taxonomy Access Control Lite (TAC Lite) was written in response to the 'heavy' nature of Taxonomy Access Control, and aims to simplify the task of restricting access to nodes by taxonomy. At the time of writing, there were no official releases of TAC Lite, which has both 5.x and 4.7.x versions available. True to its name, it installs no database tables, and instead uses existing Drupal data to handle its access control.
This module's interface was a little hard to find: it's hidden under **Administer >> User management >> Access control >> Access control by taxonomy**. First, select the vocabulary/vocabularies you'd like to enable for access control. Then click the "Role-based privileges" tab to view a screen listing each role, along with the selection of forums:

Like TAC, forum access cascades, so both the container and the forum must be accessible to a given role, so when posting, the containers are in the forum list.
One advantage TAC Lite has over the other two solutions is the ability to base access off of individual users rather than roles. Each user will have a "tac\_lite" tab under their user profile (for example, user/1/edit/tac\_lite).
TAC Lite suffers from one fatal flaw for our use case, however: **it only operates on view permissions**, not update/delete, etc. Therefore, while it can create private forums, it can't provide forum moderators. In addition, it can't let anonymous users view the Member forums but not post in them, while still allowing them to post to the Guest forums.
**Pros:** Very simple interface. Allows permissions by user as well as by role. Allows selecting which vocabularies should be access-based. Can work for use cases other than forums.
**Cons:** Only supports restricting view permissions; can't be used as a forum moderator tool. When posting, shows both the container and forum.
## Summary
So which one comes out on top?
Because it was built specifically to solve this problem, Forum Access is hands-down the easiest and most intuitive tool for managing forum permissions. Setting up those permissions, however, was rather tedious. It also is intended only for managing forum permissions, so it won't work on other vocabularies.
Taxonomy Access Control gives you immense power over every aspect of taxonomy-based permissions. You can control everything from viewing posts to showing the forum name in a list. This power comes at a steep price in usability, however; most people used to managing a forum such as PHPBB wouldn't be able to use this tool without a lot of help; the multitude of possible combinations of permissions mean there are lots of opportunities to make mistakes.
Taxonomy Access Control Lite isn't the right tool for solving our particular forum problem, as it only restricts view access to topics, not update and delete (moderators). However, if a use case came up where you only needed the private forum component, this could be a useful tool. Editing permissions is quick and easy, and you can make view exceptions for individual users, which is a feature neither of the other two modules have.
**Recommendation**: Use Forum Access if your access restriction needs are strictly related to forums. Use Taxonomy Access Control Lite if you have simple needs, but want to be able to control more vocabularies than just forums. And use Taxonomy Access Control if you need the ultimate power and flexibility over your content.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal: Exposed"
url: "/articles/drupal-exposed"
type: article
date: 2010-11-01
updated: 2016-04-07
---
# Drupal: Exposed
# Drupal: Exposed
Peeking behind the curtain at what makes Drupal tick
By
[ Angie Byron ](/about/angie-byron)
November 1, 2010
Here at Lullabot, we've trained hundreds of new Drupalistas in public workshops and on-site private trainings over the years. One of the primary things that people new to Drupal struggle with, once they grasp the basic concept of the hook system and the theme system, is figuring out the "big picture" of what all they actually have to work with to customize their own Drupal sites.
This article will expose a couple of quick core hacks\* that we sometimes use in class to help gain insight into *all* of the hooks, template files, and theme functions that Drupal makes available on a specific page request.
\* Note: Under normal circumstances, we would [*never* recommend that you "hack core"](https://heyrocker.com/hack_core.jpg) (modify core files). However, temporarily, on a local test site, and for educational purposes only, *sometimes* it's okay. Maybe. ;)
## Hooks
The [Drupal API site](http://api.drupal.org/) offers a comprehensive list of [all core hooks](http://api.drupal.org/api/group/hooks/7) that Drupal registers. But unless you have the very simplest of sites, chances are you have one or more contributed modules as well which aren't (currently) displayed on that site. Furthermore, a huge list of what hooks exist doesn't help you when you really want to know what hooks *your* particular Drupal site exposes so that you know where you can cut in and customize Drupal's behaviour.
Fortunately, all hooks in Drupal run through a function called module\_implements(). So you can do a quick hack there to gain insight into what hooks fire on a given page.
In includes/module.inc, find the module\_implements() function (around line 595 in Drupal 7, and 415 in Drupal 6). At the top of the function, just after the line:
```php
function module_implements($hook, $sort = FALSE, $reset = FALSE) {
```
add:
```php
drupal_set_message("hook_$hook");
```
This gives you output similar to this (click for full version):
[ ](https://www.lullabot.com/sites/lullabot.com/files/hooks-exposed.png)

Note that there are also hooks that fire *after* the page is displayed, which won't be visible until the next page load.
## Theme system
The [Theme Developer](http://drupal.org/project/devel_themer) module lets you highlight any section of a Drupal page in a [Firebug](https://getfirebug.com/)-esque manner and find out where it comes from. This is incredibly useful when you want to override one specific part of the page.
But sometimes, it can be useful to get a "bird's eye" picture of *all* of the template files and theme functions that make up a given page.
All output on the page is routed through a function called theme() before being presented to the browser. This makes it a nifty place to add some small hacks to gain insight as to what's going on when a page is rendered.
### Template files
In includes/theme.inc in the theme() function, around line 925 (Drupal 7) or 730 (Drupal 6), change:
```php
$output = $render_function($template_file, $variables);
```
to:
```php
$output = '' . $hook . $extension;
$output .= $render_function($template_file, $variables);
$output .= '';
```
This simple hack will give you a picture such as this when you reload the page (click for full version):
[ ](https://www.lullabot.com/sites/lullabot.com/files/template-files-exposed.png)

### Theme functions
A similar hack can show you all the theme functions that create a page. This is not recommended to run with the previous hack or you will have a mess. :)
In includes/theme.inc in the Drupal 7 theme() function, around line 880, change:
```php
$output = $info['function'](#);
```
to:
```php
$output = '' . $info['function'];
$output .= $info['function'](#);
$output .= '';
```
The same hack in Drupal 6's theme() function is around line 655, change:
```php
$output = call_user_func_array($info['function'], $args);
```
to:
```php
$output = '' . $info['function'];
$output .= call_user_func_array($info['function'], $args);
$output .= '';
```
This gives you output similar to this (click for full version):
[ ](https://www.lullabot.com/sites/lullabot.com/files/theme-functions-exposed.png)

## Other suggestions?
Are there any other quick hacks you can suggest for gaining insight into what's going on under Drupal's hood?
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Responsive & Adaptive Web Design"
url: "/articles/responsive-adaptive-web-design"
type: article
date: 2011-09-07
updated: 2014-05-15
---
# Responsive & Adaptive Web Design
# Responsive & Adaptive Web Design
What does it all mean?
By
[ Jared Ponchot ](/about/jared-ponchot)
September 7, 2011
If you work in or with the web and make even a modicum of effort to remain buzzword compliant, you're probably uber-familiar with the term "responsive web design." Perhaps you've also heard of "adaptive web design" and "progressive enhancement"? If you're like me, you may have found yourself wondering what exactly these words mean, what the differences are, and why everyone seems so giddy to use them in a sentence.
### Humble Beginnings
Let's start by acknowledging that the web, by its very nature, began as a rather "responsive" thing. In 1991, HTML itself provided a way to make documents accessible for the masses across a "word wide web." By 1996 we had Cascading Style Sheets furthering this idea of the separation of content from its presentation. By 1998 CSS2 came along and we even had "media types," making the web even MORE "responsive" to varying contexts and uses. Finally, in 2001 Jeffrey Zeldman's *[To Hell with Bad Browsers](https://alistapart.com/articles/tohell/)* article on A List Apart put some real energy behind designing in this way, forcing browser makers to begin making browsers that more fully adopt these standards. By this point you would think that the web would have reached its pinnacle, and all websites would have embodied some glorious syndication of clean and sensibly marked-up content, digestable in an infinite number of ways by a growing number of devices. But wait ...
### Designers Are Control Freaks
As the web evolved into something that more and more businesses were using, more and more designers could get paid to work on making websites instead of brochures, annual reports, business cards and the like (not that there's anything wrong with those). Print designers (myself included) began diving into this new medium and trying desperately to bend it to their will, manufacturing an ever-more controlled and fixed medium like we were accustomed to designing for. Designers like myself, who began crafting websites without a clear understanding of the medium, created painfully horrible mark-up full of tables and spacer gifs in an attempt to achieve the printed-page-looking layouts we dreamed up. By the time we figured out CSS, the goals were still the same. Designers would brag about their ability to achieve "pixel perfection" via CSS, essentially boasting about their ability to make a fluid and flexible medium exactly match one that is completely fixed. This mindset within the design community may not have changed all that much yet. Even our popular present-day community tools like dribbble are designed for showing off a fixed medium snapshot of our work (perhaps someone will create vibbbble, the video dribbble?). But enough designer bashing (I'm allowed, I'm a designer).
### Tiny Screens & Slow Internet to the Rescue!
As usual, we go where the money is, and as smart phones moved from being pocket-weight for the cool kids to what many businesses wanted to invest their dollars in, designers came along for the ride. However, in a world filled with websites designed and developed with increasingly ubiquitous high-speed broadband in mind, we now began dealing with 3g or even Edge Network (gag) and worse internet speeds on tiny displays. So we did what any good designers would do. We developed native apps where we could tightly control the visuals and we created wholly separate mobile versions of sites that limited users to only the tasks we imagined them doing on their smart phones. We were the beneficent dictators of the mobile web. And we had it all covered. Desktop displays ... check. Tiny mobile device displays ... check. After all, who would ever be using the internet on something *in between* the size of say a smart phone and a desktop computer display?
### And then there were iPads
I was going to title this "And then there were tablets," but let's face it, every other tablet is essentially trying to be an iPad at this point. But I digress. The truth is that there are a large number of devices with screen sizes in between that of a smart phone and a desktop computer. Desktop computer displays keep getting larger as well, thereby increasing the discrepancy between, and array of possible sizes. Oh, what to do?
### A responsive response
I believe (readers can correct me if I'm wrong) that Ethan Marcotte essentially coined the phrase "responsive web design" with his [article by that name](https://alistapart.com/articles/responsive-web-design/) in A List Apart back in May of 2010. In his article, Ethan laid out both the problem that is facing us as web designers as well as a very specific method for solving it. He called this method "responsive web design," and it included three specific tools.
- Fluid Grids
- Flexible Images
- Media Queries
Ethan then one-upped himself by writing a fantastic book on the subject with the creative title of *Responsive Web Design*. In his book he laid out in great detail a process and methods for achieving responsive web design. From that point forward, Ethan's three-pronged approach became the official meaning behind the term responsive web design.
I have to admit, when I first heard people referring to responsive web design I'd not yet read Ethan's article (I know, shame on me) and I assumed responsive web design was ⦠well ⦠web design that was responsive. Responsive to what? I assumed varying display sizes, browsers, etc. I also assumed that HOW these web designs responded to said contextual variants was up to the whim of each designer and that it certainly wouldn't matter whether a site was built upon a flexible grid or dynamically shifted layouts on a fluid grid as a screen resizes. Boy was I wrong. We web designers sure do love our semantics, and apparently simply responding by changing layouts rather than via a fluid grid with flexible images is not responsive web design at all. Apparently, it's called "adaptive web design" when your web site responds to these varying contexts *without* fluid grids and flexible images.
### An adaptive response
You may not have noticed, but the internet seems to have a LOT of websites and applications that are already built. For many designers and developers working on and managing these websites, the idea of starting from the ground up to rebuild their output mark-up, images, and CSS is daunting at best. In many of these cases, it is preferable to keep the current design built for desktop displays and simply "adapt" it for varying contexts. For a peek into a real-world example of why you might "adapt" your design, read Dan Cederholm's [recent write-up about adapting the dribbble design](https://simplebits.com/notebook/2011/08/19/adapted/). It explains this very scenario of dealing with a complex existing site with lots of users and lacking the resources to start from the ground up "responsively." This alternate (perhaps more limited scope) approach of adapting a design to varying contexts is what I then came to understand "adaptive web design" to mean.
Then Aaron Gustafson came out with a book with the moniker *Adaptive Web Design*, which confused this for me a bit. Aaron's book does a nice job of laying out the philosophical approach to web design known as "progressive enhancement", and also provides some practical knowledge for applying this approach in your HTML, CSS and JavaScript. I've yet to completely understand if Aaron's book is suggesting that "progressive enhancement" equals "adaptive web design" and we should all begin referring to it as such, or whether he just didn't want to title his book something that sounded like a biography of Woodrow Wilson. So, I've continued believing that "adaptive web design" refers more to the secondary and less fluid approach of *adapting* existing web designs, or designing for controlled adaptation as opposed to a truly fluid and flexible "responsive" design.
### But, isn't it all so responsive?
Ok, so why can't the word "responsive" just mean what it always does? Why can't it apply to any design approach that aims to be, well ... responsive? Because I'm prone to getting hung up on words, I've been asking that question ever since I first came across Ethan Marcotte's article and book. Thankfully, last month, Jeffrey Zeldman [asked that very question on his blog](https://zeldman.com/2011/07/06/responsive-design-i-dont-think-that-word-means-what-you-think-it-means/), and I feel like that gave me permission to begin using the term in that way from now on (queue the sighs of relief). Zeldman summed it up very well. *"Our understanding of 'responsive design' should be broadened to cover any approach that delivers elegant visual experiences regardless of the size of the userâs display and the limitations or capabilities of the device."*
### One more thing: Progressive Enhancement
I'd be remiss if I didn't quickly explain one more thing, and that's "progressive enhancement." Ok, let's put aside our cynicism and resist the urge to make comments about this being a clever, more positive spin dreamed up to re-brand "graceful degradation." The key to understanding progressive enhancement lies in the starting point. The idea of "graceful degredation," which was popularized in the halcyon days of the semantic web, assumed the ultimate whiz-bang version of something as being our starting point. The goal was then to build my shiny magic whiz-bang in a way that steadily had less whiz-bang for those unfortunate chums using inferior browsers, or with JavaScript turned off. I bet you can guess where I'm going with this. Progressive Enhancement, fundamentally, is about starting from the simplest form and working your way out. It's about designing for the lowest common denominator and then progressively "enhancing" the experience for those fortunate techno-geek designers with their 27 inch iMac displays, the latest version of webkit, and lightning-fast broadband. You may have been paying attention to the popular cries for "mobile first" and "content first" within the web design community of late, and these also spring from a similar response to the problems facing modern web designers. I won't go into more length about the valid reasons for this approach, I'll leave that to some of the great books and articles already written on the topic.
### Homework
If you've been hearing about responsive web design, adaptive web design or progressive enhancement (or if you've not heard of them) and have wondered what it all really means, hopefully I've begun to take the wrapper off for you a bit. If you wish to become a total guru on all things responsive, adaptive and otherwise, here's a quick list of reading material to get you on your way.
- [Responsive Web Design](https://alistapart.com/articles/responsive-web-design/) (article by Ethan Marcotte from May, 2010)
- *[Responsive Web Design](https://abookapart.com/products/responsive-web-design)* (book by Ethan Marcotte)
- [Adapted](https://simplebits.com/notebook/2011/08/19/adapted) (article by Dan Cederholm from August, 2011)
- *[Adaptive Web Design: Crafting Rich Experiences with Progressive Enhancement](http://easy-readers.net/) (book by Aaron Gustafson)*
### Hey, wait a minute â¦
You may be asking yourself, if this guy knows so much about all this responsive adaptive mumbo jumbo, how come this site doesn't seem to be responsive? Patience my friend, patience :-)
Published in:
- [ UX & Design ](/topics/design-and-ux)
- [ Mobile ](/topics/mobile)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "The Features Module"
url: "/articles/the-features-module"
type: article
date: 2011-06-10
updated: 2023-10-26
---
# The Features Module
# The Features Module
A Look at the History Behind the Tool
By
[ James Sansbury ](/about/james-sansbury)
June 10, 2011
**The Features module is a module that creates modules called features. The End.** You're still here? Wasn't that crystal clear for you? Ok, I suppose I'll go ahead and elaborate.
I've been waist deep in Features module working on a [new video series](https://drupalize.me/course/drupal-deployment-features-and-drush) for [Drupalize.me](https://drupalize.me/) and have been thinking it would be valuable to provide a bit of a retrospective on the tool, what it was created for, and how the Drupal community has been using it.
## The Back Story
It is March 2009, and I am waiting for a session to begin at DrupalCon DC, prepared to slip out if it turns out to be uninteresting. It's entitled [A Paradigm For Reusable Drupal Features](http://www.archive.org/details/DrupalconDc2009-AParadigmForReusableDrupalFeatures), and it's being led by [a bunch of guys with these orange stickers on their MacBooks](https://developmentseed.org/team/). I'm already skeptical because I'm not sure why anyone would willingly place a sticker on something they like. Apparently, a lot of people think this is a good idea, so I decided to give them a chance to redeem themselves. They did.
Drupal had made giant leaps in terms of being able to administer a very complex and intricate system right from the comfort (ahem) of your favorite browser. It used to be that if you wanted a new node type, you had to write a module to do that. Want to add a field to that node type? Time to brush up on your SQL chops, because you're going to have to do that all by hand in code. Want to display a customized listing of that content? Well, don't set down that SQL handbook just yet, you'll need to write all those queries yourself, and write the markup to go with it. All of that had changed with the awesome work that was being done with the [Views](http://drupal.org/project/views) and [CCK](http://drupal.org/project/cck) modules, and it seemed to just get better every day.
This was all great for the recipients of these sites, the people that work on the 'Content Management' part of a CMS. But what about the developers? What about those souls in the Drupal trenches day after day? Create node type. Add CCK fields. Create view. Rinse. Repeat. Everyone had been all "Yay CCK!" and "Yay Views!" for a good time now, but for those of us actually using these tools every day, it frankly was getting to be the developer equivalent of data entry. Oh, this client wants a blog too? Ok, click here, type here, click here, type here, click here, type here. Isn't this awesome? You can do it all in your browser! **Boo**.
Most of us had already been working on ways out. If you are familiar with Views module, you've probably seen how it has the ability to export a view to PHP code. You can then import that view on another site. This ability began a movement in the Drupal community to start making things 'exportable', or in other words, creating the ability for configuration to live in code, not just in the database. It wasn't long before all sorts of Drupal Stuff⢠was exportable: CCK Fields, Blocks, Contexts, Panels, Variables... The list goes on and on.
I learned quickly in this session that Development Seed has some super smart people. The awesome thing about super smart people is that they often do super smart stuff, and there was no exception here. Development Seed began to show in detail their solution to some of these problems. They had a good workflow for making Drupal development faster, and Drupal deployment possible. Surprise surprise, it was in part by going back to the old-fashioned way of doing things. Want a new node type? Do it in code. Want CCK fields on that node type? Code. Want a View? Export it to code. Like I said, lots of us had already been doing that. We had these monolithic modules that contained bits and pieces of what made our website tick, and filled in the gaps with update hooks and even (gasp) SQL queries to do iterative deployments. The difference was, Development Seed had created a pattern for building these modules, and a big distinguishing factor was that their modules were focused on accomplishing very specific, very concrete tasks.
## Did You Just Say Concrete?
Yes, yes I did. So often in the Drupal development world, we're thinking about how to make things more abstract, moving away from the specific to the generic. Because of this, we have loads of modules that sit like tools in our toolboxes. You go to the module administration page and start clicking things, hit submit, and what have you done? Nothing! All you've done is load up your toolbox to the point that it's probably pretty heavy (read slow). You've got enough tools to make [Norm Abram](https://www.newyankee.com/) jealous, but haven't actually built anything worth showing your friends.
Development Seed had begun changing that by spear-heading a movement to create modules to accomplish specific things. Need a blog? Don't just start clicking things willy-nilly till you get what you want. Create a module for that 'feature' that you want, and start adding things to it. Create a blog content type, add a Subtitle field to it, add that blog view, and get it all in code into a module. All that work pays off very quickly when you want a blog on another site. Click the checkbox next to your new module, hit submit, and that's it. Really. None of that clicky-type-clicky-type stuff.
I remember coming out of the session like it was January 1 and I had just bought a treadmill, ready to take on the world with fresh legs. At the time I had been working daily on a Drupal platform called WebGear that was trying to be all things Drupal wasn't: Pretty, Easy to Use, Simple. Stuff like that. I left DrupalCon re-thinking the entire architecture of what we were building. I was picturing nice little boxes in my head, each containing just the code specific to accomplishing the task associated with it. A Blog module. A Gallery module. A FAQ module.
## Enter the Features Module
It felt like it wasn't a week later that Development Seed [announced](https://developmentseed.org/blog/2009/may/29/making-and-using-features-drupal) a new module that they were working on called [the Features module](http://drupal.org/project/features). Ok, maybe it was a few months, but still.
What did this module do? Well, it actually wrote modules for you, just like the ones they had described in their session. Using the example of the Blog again, you create your node type, add fields, create a view, etc. Then you go to this "Features" interface, and just by clicking checkboxes, you can the turn all that clicky-type-clicky-type work into a nice pretty Drupal module that you can turn on and off at will.
I wet myself.
And then I converted all of my site-specific modules into these new 'feature' modules.
It wasn't long after this that the Features module really started taking off in the Drupal community. If they weren't using it, Drupal developers were at least talking about it. It made deployment of new functionality super fast. It made maintaining that functionality easier. It made it so that you could have version control on every little tweak to your functionality (I know you are all pretending like you haven't spent 2 hours tweaking a new display on a view to having lost all of your work by accidentally deleting that display moments later), which in turn made consistent debugging possible (git-bisect anyone?).
## Using Features as a Deployment Tool?
We started using Features on every project we did after that, and I have to say it made so many things so much easier. Sure, [it had its share of problems](http://drupal.org/project/issues/features?categories=bug), but for the most part, it did what we needed it to do. If there wasn't a feature for something we needed, we created it. I wish I could say, "and then everyone lived happily ever after," but I can't.
Features was created with a lot of assumptions. I had an advantage being a part of the discussion early on, so I knew why and how Features made these assumptions, but I quickly realized that others coming in didn't have this background. It didn't come as naturally to them to think of things in terms of "use cases", so you'd see features being created like "All Variables" or "All Contexts"âsolitary modules that contained all the variables that were being Strongarmed, or all the Contexts, or all the Views.
Why were people doing this? The reason is that they were using the Features module as a Deployment tool. They wanted to get their configuration into the code, and then wanted a way to deploy that configuration to a live site. They also wanted a shortcut from having to do that work by hand, from having to write that PHP code into a module. This seems natural enough, right? But if we start using a tool before we understand what it was built for, and maybe even a bit of history behind it, we will surely be in for some frustration.
Features was built by a team of developers (you know, those guys with orange stickers on their MacBooks) working on the Drupal distribution called [Open Atrium](http://openatrium.com/). As the authors put it, it exists to help you write modules that will "satisfy...certain use-case\[s\]." That's it. It wasn't built as a deployment tool, even though it is often used for that. It wasn't built as a productivity tool, although it can make you more productive. It was built to help you create little self-contained, packaged modules, oh, and by the way, for-the-love-of-Pete-please-don't-let-the-modules-touch-each-other.
## Excuse Me, Your Feature is Touching My Feature
As soon as you create an "All Contexts" feature, you have just begun down the road toward Dependency Hell. Follow up that feature with a feature of all your views, and you may end up with a circular dependency, where the "All Contexts" feature depends on the "All Views" feature which depends on the "All Contexts" feature. Whee! This is a lot less likely to happen in newer versions of the Features module, but I make no promises.
Even if you are creating Features [by the book](http://drupal.org/project/kit), you've probably run into similar problems with dependencies. Features module starts with the assumption that there can somehow be this atomic (stand-alone) sort of module that just does X and that's all it does and any configuration or functionality it provides will notâand therefore cannotâbe touched by any other module. Unfortunately, software development doesn't work that way, as [Victor Kane so eloquently affirms](http://awebfactory.com.ar/node/458). In the real world, things can't be isolated into tidy boxes that never touch. We've created a Blog feature, assuming it is an atomic piece of functionality. Then the software requirements change and we find we need to display some biographical information about the author of a blog post in the sidebar. Great, except the field that stores that data happens to exist in a completely separate feature. Whoops.
The problem is twofold. 1) Dependencies exist **between** modules only, and 2) exported configuration exists **within** modules only. We export a view and put it in a module using Features. Anything that depends on that view must depend on the module that contains the view, not the view itself. For instance, going back to the Blog feature, when we need to display the author's bio in the sidebar we might configure the blog Context to display the view of the author bio in the sidebar. Instead of the Context getting a new dependency on the existence of that view, the Blog feature now has a dependency on the User Profile feature.
## Use, Don't Abuse
The path of least resistance in using Features module is to work with it: realize it is a tool created for a very specific task, and use it in that context. The Drupal community by and large (myself included) has been using Features module to try to fill the need for a deployment tool. It feels a lot like pounding in a screw with a hammer. It works, but it's certainly not ideal, and since we don't have a screwdriver, [we don't have an easy way to get the screw out](http://drupal.org/node/1014522) now that we've pounded it in.
I'm not saying don't use Features. I use it every day. I still love it. But I try to remind myself often that I am holding a hammer in my hand, not a screwdriver. I've got my share of bruised thumbs and broken screws, but it sure beats screwing these in with my fingers. Understanding the history behind it and the use case it is primarily trying to solve will go a long way in helping you experience the least amount of pain in working with Features.
## Links and Resources
If you're interested in learning more about Features, check out these links:
- The [Features module](http://drupal.org/project/features) project page, which contains lots of helpful information
- 4-hour Drupalize.me Video series on [Drupal Deployment with Features & Drush](https://drupalize.me/course/drupal-deployment-features-and-drush)
- [The Kit Specification](http://drupal.org/project/kit), which describes best practices for creating features
- [MustardSeed Media Video](https://mustardseedmedia.com/podcast/episode43) on creating a quick Feature in Drupal 6
- [Features Plumber](http://drupal.org/project/features_plumber) for resolving nasty conflicts
- [Features Override module](http://drupal.org/project/features_override) for altering pre-existing features
- [DrupalCon presentation](http://chicago2011.drupal.org/sessions/zero-distribution-using-features-profiler-and-drush-make) on creating a Drupal distribution with Features by [Dmitri Gasken](http://drupal.org/user/47566)
- [Debut](http://drupal.org/project/debut), a set of baseline features
- [Modules](http://drupal.org/project/modules?filters=tid%3A11478%20bs_project_sandbox%3A0&solrsort=sis_project_release_usage%20desc) on Drupal.org that are features or integrate with Features module somehow
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "LESS is More"
url: "/articles/less-is-more"
type: article
date: 2012-09-19
updated: 2014-05-15
---
# LESS is More
# LESS is More
The New CSS Tool that Enables Easier CSS Organization and Editing
By
[ Sean Lange ](/about/sean-lange)
September 19, 2012
If youâve been watching the web design community for the past year or two, youâve probably heard of LESS. Basically, itâs an enhanced style of CSS that lets you build complex CSS faster and easier. Run that file through the LESS program, and with just a dose of 'wiz' and a dash of 'bang,' the result is a normal CSS document that youâre already familiar with. Once you have the hang of it you will never go back. LESS is to CSS, what CSS is to HTML⦠it's that good.
### A little about CSS
CSS stands for Cascading Style Sheets. Style sheets allow us to separate visual presentation instructions from the HTML markup itself. Thanks to this revolution we said goodbye to many, many lines of HTML markup that were placed directly into the DOM structure, and to table layouts⦠nice!
CSS brought plenty of advantages. For example, before CSS presentation code wasâ¦
- Intertwined directly with the page elements and content⦠messy.
- Repeated multiple times on the same page⦠redundant.
- Implemented in different ways within the same document and site⦠inconsistent.
### So who's the new kid on the block?
It's LESS. How does it work? LESS allows you to write CSS using nesting, simple variables, and other things that normally arenât supported. That lets you organize your CSS rules in hierarchies, group things logically, and avoid repeating the same CSS over and over. Then, the LESS script turns the file into normal CSS files for the browser. I say 'LESS is more' because it lets you write less CSS code, while accomplishing more!
### I can be a better Drupal Themer
I'm a Drupal front end developer, and what was important to me is how this could improve my Drupal theming. When I decided to commit to using LESS, I was quickly able to see many benefits for my Drupal theming?
LESS improved my Drupal theming process/workflow by allowing me toâ¦
- create reusable css structures that speed up time writing css.
- create variables, so that if I change a color, I change the variable once, and don't need to 'find and replace' for several minutes.
- create reusable css that you can insert values into⦠i.e. write your ârounded cornersâ CSS once, then reuse it with different sizes as needed.
Let me explain a little deeper why I think LESS is superior to writing CSS. Web browsers read CSS, so ultimately you have to end up with a .css file to render the page. Normally when writing .css we just make a long list of display rules, then try to keep them 'together,' 'in order,' and (hopefully) 'logical'.
**Example 1:**
```
#main-menu {â¦}
#main-menu img {â¦}
#main-menu ul.menu {â¦}
#main-menu ul.menu li {â¦}
#main-menu ul.menu a {â¦}
```
### [A thought... with another thought's hat on.](https://www.tvfanatic.com/quotes/its-a-a-thought-with-another-thoughts-hat-on/)
Let's look at a more extreme example to illustrate a point. If we were creating an address book entry, and were using something like CSS markup, we would write down someone's contact information like thisâ¦
**Example A:**
```
Jon Smith [â¦]
Jon Smith's address [â¦]
Jon Smith's phone (h) [â¦]
Jon Smith's phone (w) [â¦]
Jon Smith's email [â¦]
```
However; we would never do that! (At least, I don't think we would.) Instead, we want to use a simpler, more organized way to document this informationâ¦
**Example B:**
`Jon Smith address: phone (h): (m): email: `
### From LESS to CSS
That nested hierarchy concept is the basis for writing LESS. I can enter something like the following, using LESS hierarchy, and it will automatically generate the same âflatâ CSS from Example 1.
**Example 2:**
```
#main-menu {
img { }
ul.menu {
li { }
a { }
}
}
```
### LESS is more powerful
This is a pretty basic example and is more to demonstrate the mind-set that I use for LESS. If you need to write a dozen lines of CSS, LESS is probably not needed. If youâre working on a CSS file that has hundreds of lines, with dozens of nested divs and selectors, then LESS is a powerful and empowering way to write that CSS code. When you combine the structure of LESS with things like variables, mixins, operations and functions⦠writing CSS just got a whole lot more fun!
### A Bonus Example (variables)
Perhaps you are in a project that has a lot of rounded corners. You have to make small adjustments for each one of the elements, so you keep re-writing the radius properties each time. Stop doing that⦠write LESS! Declare your variable as a class and use it over, and over, and over again.
**LESS Code:**
```
.round-my-corners(@radius: 10px) {
-webkit-border-radius: @radius;
-moz-border-radius: @radius;
border-radius: @radius;
}
#normal_box {
.round-my-corners;
}
#small_box {
.round-my-corners(5px);
}
#large_box {
.round-my-corners(20px);
}
```
**Generated CSS Results:**
```
#normal_box {
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
}
#small_box {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
#large_box {
-webkit-border-radius: 20px;
-moz-border-radius: 20px;
border-radius: 20px;
}
```
### I <3 LESS
There is so much more to LESS. My goal with this post is to hopefully spark your interest. Perhaps engage you to take a chance on it if you have been considering it. If you are interested in more details there a few sites you would want to check out.
- The [official LESS site](https://lesscss.org/) has a lot of good examples and documentation
- The [LESS Mac app](https://codekitapp.com/index.html) is what I prefer
- The [LESS Code plugin](https://codekitapp.com/index.html) looks pretty interesting, though I haven't tried it yet
- The [LESS Windows version](http://wearekiss.com/simpless)
Published in:
- [ UX & Design ](/topics/design-and-ux)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Install a Local Web Server on Ubuntu"
url: "/articles/install-a-local-web-server-on-ubuntu"
type: article
date: 2007-11-14
updated: 2014-05-15
---
# Install a Local Web Server on Ubuntu
# Install a Local Web Server on Ubuntu
By
[ Addison Berry ](/about/addison-berry)
November 14, 2007
**NOTE: This video is no longer available as it contains outdated content. There is a newer version of this video, [Installing a Web Server on Ubuntu](https://drupalize.me/videos/installing-web-server-ubuntu) available on [Drupalize.Me](https://drupalize.me/)**
This video will show you how to set up a local web server on the Ubuntu desktop version. It walks through most of the process using a GUI and uses just a little bit of command line to set some things up. I did it this way to make it the most accessible to even new users of Ubuntu. It walks you through installing the needed packages, setting it up for clean URLs and getting Drupal started.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "IE iFrame Insanity"
url: "/articles/ie-iframe-insanity"
type: article
date: 2010-02-09
updated: 2014-05-15
---
# IE iFrame Insanity
# IE iFrame Insanity
By
[ Karen Stevenson ](/about/karen-stevenson)
February 9, 2010
After I spent THREE HOURS trying to figure out why IE insists on rendering a white background for an empty iframe, Nate pointed this little gem out to me.
IE has default values for iframes. Yes they do. And the default is to put an opaque background and an inset border on iframes that will ignore any attempt you make to change the iframe background color or border using css. So if you put 'background-color:transparent' into your css for the iframe element it will have no effect. That's right IT WILL IGNORE YOUR CSS!
To fix it you have to do this:
```
```
See the documentation for this stupid behavior here
\- http://msdn.microsoft.com/en-us/library/ms533072%28VS.85%29.aspx
\- http://msdn.microsoft.com/en-us/library/ms533770%28VS.85%29.aspx
Published in:
- [ Front-end Development ](/topics/frontend-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal best practice: Document your way to understanding"
url: "/articles/drupal-best-practice-document-your-way-to-understanding"
type: article
date: 2010-09-10
updated: 2016-04-07
---
# Drupal best practice: Document your way to understanding
# Drupal best practice: Document your way to understanding
Drupal documentation
By
[ Angie Byron ](/about/angie-byron)
September 10, 2010
Drupal is an ever-changing landscape, so it happens fairly often that you come upon a challenge you've never had to do before. Perhaps it's [adding Views integration for a contributed module](http://views.doc.logrus.com/group__views__hooks.html#g227057901681e4a33e33c199c7a8c989). Maybe it's [trying to figure out how the heck to CCK works internally](http://drupal.org/node/82661). Or possibly you've been tasked with [adding recurring billing to an Ubercart store](http://drupal.org/handbook/modules/uc_recurring). These are all challenges I've run across in my day job and needed to figure out.
Whatever the challenge, you're going to have to dive in and figure out how some module or feature works. This usually is some combination of playing around and, when that fails, reading the documentation. And, because Drupal is a largely volunteer-driven open source community, you might find that documentation to be... sub-par (or even non-existent).
What to do? Well, here are some options...
## The Loner
Spend the next several hours smashing your face into a wall figuring out how the darn thing works. Curse and spit at Drupal, at the module maintainer, at the world! Fix your problem, then move on with your life.
This "works", for some values of works. We've all been there. But it does nothing to improve the situation for the next poor schmuck who comes along and has to repeat the process. The community's collective hours spent on face-smashing tends to grow exponentially the more popular and less documented a project is. Everyone loses.
## The Hater
Take your aforementioned cursing and spitting about the lack of documentation to an irate blog post or the developer's issue queue. Make sure they know how totally unacceptable the state of their documentation is and *demand* that they fix the situation.
This doesn't tend to go over well *at all*. Realize that from the module developer's standpoint, you're yelling at them about code that you just got *for free* and that they spent an enormous amount of their own personal (and in most cases unpaid) time on. You're likely to end up with a frowny face against you karma-wise which makes people far less willing to help you in the future. This is *bad*, especially in a project with as much "tribal knowledge" as Drupal.
## The Doer
*Write* the documentation that's missing yourself, as you figure it out. You have to figure it out anyway, so why not? By writing it down for others, you both cement the knowledge in your head since you have to "teach" it, and you also do your Drupal karma good deed to help the next person not have to struggle as much as you did. And If karma isn't a powerful enough incentive, remember that the next person might be *you* again, six months from now. ;)
And often, module maintainers are willing to bend over backwards to help you if you're willing to help with documentation. It's win-win-win!
## "But I don't know what I'm doing yet!"
***Perfect!*** You are at *exactly* the right spot in your learning curve to write and fix documentation! Why? Because *you know what someone in your shoes finds confusing!*
If the module developer writes documentation, it's probably going to end up something like this:
> Backreference Module provides a nodeapi interface to maintain 1-1 relationships between all shared instances of a nodereference field. This means that given a field instance of field\_reference1, if you add a reference to NodeBeta to NodeAlpha's field\_reference1 and NodeBeta has an instance of field\_reference1, then NodeAlpha will be added to NodeBeta's instance of field\_reference1.
If *you* write documentation, [coming into this project fresh](http://drupal.org/node/679504), it's probably going to end up something more like this:
> BackReference module maintains one-to-one relationships between node reference fields.
>
> For example, let's say you have two content types: Attendee and Event. Event has a node reference field called "Attendees" (field\_attendees) that references Attendee types. When you click on an Event node, you'll see a list of the Attendee nodes that it references; this functionality is built into the core CCK Node Reference field.
>
> BackReference module allows you to have the inverse, where clicking on an Attendee node will show you the Events they are attending.
If you're not sure about something, take a stab and label it with something like "TODO: Is this right?" Someone else can always come along after you and edit your docs to fill in the holes. But laying out the map of what holes need to be filled is something actually best achieved by people who don't understand how things work yet, because they know the right questions to ask.
## Ok, fine. So where do I start?
Documentation usually comes in the following forms:
- **Basic documentation**: Stuff like README.txt and INSTALL.txt that explain how to get the module up and running. These are generally improved by patches in the module's issue queue. Don't know how to patch? Just paste in some text into an issue. Lots of other people know how to make patches out of it. (http://drupal.org/patch has the full skinny if you're curious; it's a great skill to pick up!)
- **Handbook documentation**: For more comprehensive documentation, often modules will have their own set of dedicated [handbook](http://drupal.org/handbook) pages. Look for a "View documentation" link on the project page. Almost all handbook pages can be edited by anyone with a Drupal.org account, so fill your boots! If there isn't already a handbook page, go ahead and browse around sections like the [Administration guide](http://drupal.org/node/627152) or [Site building guide](http://drupal.org/node/257) and create a new page for the module in the most appropriate spot (if you don't know, just pick the closest match you can find; it can always be moved later).
File an issue in the module's issue queue (or general [Documentation queue](http://drupal.org/project/issues/documentation) for "meta" handbook pages) that indicates what you're working on and link over to your stuff for review.
- **API documentation**: For programmers, it's really useful to have salient and relevant API documentation for a given project. The general convention on API docs is to create a *project*.api.php file that explains how the various hooks it exposes functions. If a module has one already, file a patch in the issue queue to clean it up. If not, create one and attach it to a new issue.
- **Crowd-sourcing**: If you're working on some docs, make it a community affair! Hop on IRC / Twitter and announce to everyone that you're working on documentation for X, and see if you can wrangle a few other people to help using a real-time editor such as [Etherpad](https://etherpad.org/). We did this the other week with [Panels 3 documentation](https://dewa-89.com/) and it was a blast!
New to the drupal.org issue queue? Check out Addi's [helpful issue queue tutorial video](https://www.lullabot.com/articles/introduction-to-the-drupalorg-issue-queue).
For more "real-time" help on where to put docs, how to use the issue queue, and so on, hop onto IRC: irc.freenode.net, channel #drupal-contribute.
New to IRC? Check out the docs on Drupal.org: http://drupal.org/irc
## So let's *do* it!
So don't be a loner. Drupal has a *huge* community of people who absolutely love to help people who help others; there's no reason to go it alone!
And don't be a hater. In addition to making life generally miserable for those around you, this has real consequences for you when you find yourself in need of help later.
Instead, *do it!* Let's get out there and clean up some docs! :)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Deblobbing your chunks: Building a flexible content model"
url: "/articles/deblobbing-your-chunks-building-a-flexible-content-model"
type: article
date: 2012-10-09
updated: 2021-01-12
---
# Deblobbing your chunks: Building a flexible content model
# Deblobbing your chunks: Building a flexible content model
Tips for Grouping and Organizing Drupal Content
By
[ Jeff Eaton ](/about/jeff-eaton)
October 9, 2012
There is a temptation and danger to fall into the "Dreamweaver Field" content model. When content types are just wrappers around giant chunks of hand-formatted HTML, editors have lots of flexibility but it's all but impossible to repurpose the hard-coded content for new designs and publishing channels.
In presentations, articles, and her upcoming book, [*Content Strategy for Mobile*](https://abookapart.com/products/content-strategy-for-mobile), Karen McGrane describes the problem as a war of "Blobs" versus "Chunks." The challenge is figuring out how to decompose a site full of inflexible HTML blobs into discrete, bite-sized fields. There's no magic bullet (a model that works for one project can fail miserably for another), but over the past several years we've accumulated a few useful rules of thumb for "deblobbing" a site's content.
### The Basics
**Don't skimp on the [content inventory and auditing process](https://www.lullabot.com/articles/a-toolset-for-enterprise-content-inventories).** Figure out what's there, what's going to be tossed, and what you *want* to have on the site. This is step zero, really: the modeling process is infinitely harder if you're dragging around piles of HTML that don't match what your're trying to build.
**Clump similar content.** If your existing site doesn't have discrete content types, figuring out which pages are similar to each other is the next stage. Product reviews, staff bios, press releases, blog entries, portfolio slideshows⦠You know the drill. Remember to look for pages and content types that are really composites of other, smaller units of content. Often, some of the most complex pages and content types can be implemented as rule-based or curated collections of smaller, more management content types.
**Look for common "chunk" types.** Once you've grouped your blobby content types into similar pools, zoom in and look for patterns that are unique to each content type. These are are potential candidates for dedicated fields. Some of the common field types we encounter include:
- Links to related content
- Links to downloadable files and embedded media that occur at consistent locations
- Publication or event dates
- Pull quotes, hand-written taglines, author bios, and summaries
- Business and event addresses
- Geographical locations and maps
- Lists of information like features or rules and requirements
- Ratings, prices, and product codes
Most CMS's support multi-value fields that can be used to model repeating elements like feature lists or multiple file attachments. Be sure to note which elements occur once, and which ones repeat.
**Rinse and Repeat.** Once you've broken things into multiple content types and identified the discrete fields on each one, look for overlaps. Are there several content types that share the same list of fields? Consolidating them into a single type might simplify things. Is there one "Godzilla" content type with dozens and dozens fields? It might really be several types that should be teased apart. The first pass of a content model is a lot like the first draft of an essay: there are *always* rough edges and awkward parts that need work.
### The Tricky Bits
After identifying all of that *easy* stuff, large and complex sites usually have quite a few ugly blobs that still need to be broken down.
**Identify composite content.** Sometimes, elements of one content type need to be broken out into their own sub-content-types, with simple parent-child relationships connecting them. Galleries that contain multiple photos, albums that contain multiple songs, and curated pages that include teasers for other content are common examples. If several content types in your model contain the same cluster of fields (like photo, caption, byline, and link, consider splitting out the cluster into a its own dedicated content type. Treating those scenerios as relationships between discrete elements can often simplify complex models.
**Look for common formatting complexities.** If you have wireframes or existing pages, look for complex visual formatting around certain elements, in particular the stuff that requires lots of hand-written HTML to implement in a "content blob." Comparison tables are a common offender here. Breaking these out into dedicated fields whenever possible can help prevent massive pain when a piece of content needs to be displayed differently in new channels.
**Watch for design elements that change based on context.** If you're building a responsive or adaptive site, or have access to designs for mobile apps or other output channels, keep an eye out for elements that appear differently or conditionally based on breakpoints, target device, and so on. It seems obvious, but controlling small elements is infinitely easier when they're broken out as discrete fields.
**Plan for searching and filtering.** Try to identify as many different filtered lists of content as possible. Faceted search screens, topical landing pages, author-based blogs, product lists, and so on can't be built efficiently without the right data. If the lists and search indexes that you need don't correspond to fields you've already broken out, remember to add additional ones for the required metadata.
**Isolate the crazy.** Inevitably, complex designs end up requiring "helper" content that doesn't seem to fit the well-understood content types the site's stakeholders imagine. Slides for promotional rotators, free-floating promotional microcontent for landing pages⦠These tend to be highly variable and often need the kind of raw-HTML flexibility that we're trying to avoid. Isolating them in their own content types and living with the cordoned-off craziness can help simplify models with overloaded, field-heavy primary types.
**Recognize when markup is good enough.** Despite all the talk about the dangers of blobs, it is possible to go too far. Replacing every HTML div and span with a dedicated field simply to avoid raw markup is overkill, and can easily result in 'Edit Screens of Doom.' Modern WYSIWYG editors generally support plug-in systems, and developing a button to "insert caption here" or "style paragraph as warning" *can* be a simpler solution. This is where I repeat the warning: There's no *perfect* content model, only the one that works for your project.
### Test the Model
The long-term impact of a *bad* model on a site's maintainability can be frustrating, but it's also impossible to predict every future application the content will be used for. Iteratively testing the model against real-world content and potential applications is critical.
**Put real content into the model.** It seems obvious, but it's easy to go down the structural rabbit hole and forget the existing pool of content. Circle around frequently and ask, "How does the content we have in hand fit into these content types and fields?" Look for odd mismatches, required fields that the existing content will leave unpopulated, and so on. Sometimes, the design and the model have to change for practical reasons. Other times, clients or your team will have to update the content to close the gap.
**Plan for three channels.** When building a model (or a software API), it's easy to imagine you're creating a reusable system while unintentionally baking in assumptions that make real reuse difficult. If you need content that will adapt to reuse in new channels, be sure that you keep at least three in mind -- think of them as user personas for the model. Desktop web, small-display devices, and rich HTML newsletters are common answers for some businesses. Even if you're only *building* one of them at first, proposed approaches can be compared against them to ensure you aren't painting yourself into any corners.
**Social sharing is a publishing channel, too**. [Twitter](https://dev.twitter.com/docs/cards) and [Facebook](https://developers.facebook.com/docs/plugins) can automatically embed headlines, summaries, and preview images when users paste one of your site's links -- *if* you provide the metadata that they're looking for. If your model doens't account for those, it will be much tougher.
**Let real users work with it.** If you're using a web framework that allows rapid creation of a content model before the full site is finalized, or you can produce wireframes of some sample content input and editing screens, *get user feedback sooner rather than later.* The people who spend their time creating and maintaining the content can often spot problems and inconsistencies that would otherwise remain undiscovered until launch.
### No Rules, Just Lessons
None of above ideas are hard-and-fast rules. At Lullabot, we've spent years building out complex sites (and the underlying content models) for media publishers, government agencies, corporate intranets, ecommerce sites, and more. And yet, every new client comes with surprises and challenges. What useful heuristics do *you* use when breaking down ugly "content blobs" into reusable chunks? Feel free to chime in with comments!
Published in:
- [ Digital & Content Strategy ](/topics/content-strategy)
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Install a Local Web Server on Mac OSX"
url: "/articles/install-a-local-web-server-on-mac-osx"
type: article
date: 2007-07-15
updated: 2014-05-15
---
# Install a Local Web Server on Mac OSX
# Install a Local Web Server on Mac OSX
By
[ Addison Berry ](/about/addison-berry)
July 15, 2007
**NOTE: This video is no longer available as it contains outdated content. There is a newer version of this video, [Installing MAMP web server](https://drupalize.me/videos/installing-mamp-web-server) available on [Drupalize.Me](https://drupalize.me/)**
Need a place to test your website before you show it to the whole world? Don't always have an internet connection but you'd love to spend that time tinkering with your site? A great way to work and test things out is to install a web server right on your own computer. This way you work offline and if you mess things up you can just start over again without taking your site down or futzing with FTP and/or SSH.
This video will show you how to easily install a web server on your Mac using MAMP. MAMP is a bundle of all the tools you will need in one package: Apache, MySQL and PHP. We'll walk you through downloading and installing it and then we'll go through some basic set up to get you up and running.
MAMP is a Mac-only application but the plan is to create videos with similar packages for other operating systems.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "A Quick Guide for Code Reviews"
url: "/articles/a-quick-guide-for-code-reviews"
type: article
date: 2012-01-04
updated: 2014-05-15
---
# A Quick Guide for Code Reviews
# A Quick Guide for Code Reviews
By
[ Andrew Berry ](/about/andrew-berry)
January 4, 2012
Code reviews are an essential part of the software development process. Often a code review is considered to be a distinct process and kept separate from day-to-day development. At Lullabot, we consider code review to be a critical component of any development - just like QA, automated testing, and documentation. Code reviews are an acknowledgement that every developer is a human being, and humans make mistakes. No matter the skill or background of a developer, reviewing their code can only improve the final product.
Of course, code review is an integral part of Drupal development as well. The community convention is that at least two people (and often many more) should read and understand code for it to be considered for inclusion in Drupal. While contributed modules don't often have the resources for full code reviews of every patch, be sure that any module author would *love* reviews of their code.
What are the key components of a code review? While this is by no means a comprehensive list, here are some of the items I look for when reviewing code. Feel free to post your favourite code review tactics in the comments below.
## The Story of the Code
All code committed to a project should be an atomic unit that describes:
- Why the change was made.
- What lines of code were changed, and how the new code works.
- How to verify that the change actually worked.
An easy way to figure out if code meets this criteria is to read through a changeset, and answer these questions as if you've never run the code and never talked to the developer who wrote it. Verify that the code does what it says it does (at least by reading it), and that additional functionality doesn't hitch along for the ride.
Much of this information might live in ticketing systems, which is fine as long as the commit messages reference the associated ticket number. I often use commit messages similar to the following:
> Ticket #1071: Convert all tabs to spaces in template.php.
Of course, with commit-friendly systems like Git there might be many small commits, and the "atomic unit" becomes a merge, and not a single commit.
## Appropriate use of system APIs
Websites and applications built with Drupal have a wide array of APIs and configuration options available at many layers of the system. Many of options available at each layer of the stack can be used to achieve the same result with varying levels of success. APIs and configuration options are usually available at these components in the system stack:
- Server software such as Apache, MySQL, and Varnish.
- PHP and any installed PHP modules.
- Drupal and any installed contributed modules.
- JavaScript APIs provided by browsers or JavaScript libraries.
- CSS for controlling visual aspects of a page.
It's important that code solve a problem at the right layer in a stack. For example, imagine you need to sort rows in a paged table. The sort could be executed:
- As a stored procedure in MySQL (but please, please don't do this).
- With ORDER BY and LIMIT statements manually added in the MySQL query that is executed.
- By fetching the unordered results and sorting them manually in PHP.
- As a call to [db\_query\_range()](http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_query_range/7) that is provided by the Drupal API.
- By sorting and filtering the actual table rows in the browser with JavaScript.
The best solution for a given use case may not be clear. Novice developers often don't understand or aren't aware of all of the layers, and code reviews can help ensure that the right changes are made in the right layer of the stack.
## Security
The security of a Drupal installation requires thought throughout each part of the system stack. Server configuration and access controls are critical components, but for code reviews it's important to focus just on the code itself. Items to watch for include:
- Any potential SQL injection exploits. Generally this is mitigated by properly using the Drupal Database API. It's still possible to misuse the API or ignore it entirely.
- Any potential cross-site scripting (XSS) exploits. Again, using Drupal's text APIs such as [t()](http://api.lullabot.com/t/7), [check\_plain()](http://api.lullabot.com/check_plain/7), and [filter\_xss()](http://api.lullabot.com/filter_xss/7) can mitigate these issues.
- Any potential cross-site request forgery (CSRF) exploits. For example, modifying data based on a GET request could be a security issue. Converting such code to use the Form API, or to use [drupal\_get\_token()](http://api.lullabot.com/drupal_get_token/7) can mitigate these issues.
- Ensuring that user and content access controls are implemented properly. Missing node access grants on node queries is a common issue. Or, trusting user IDs passed from the client can expose data. Mitigating data exposure requires understanding both the logic of the code itself and understanding the minimum amount of data required to complete an operation.
These items are just a brief overview of potential security vulnerabilities. Drupal.org has an excellent guide to [Writing secure code](https://drupal.org/writing-secure-code).
## API-first design
All code should be composed of reusable functions that can be repurposed for other use without extensive refactoring. For example, most websites will have custom code that creates a custom menu item and shows a page or a form. The naïve approach is to implement [hook\_menu()](http://api.lullabot.com/hook_menu/7) and do something like this:
```php
function example_menu() {
$items = array();
$items['account-balance'] = array(
'title' => 'Your account balance',
'description' => 'How much money you owe this awesome website.',
'page callback' => 'example_account_balance',
'access arguments' => array('access content'),
);
return $items;
}
function example_account_balance() {
global $user;
$output = "
Your account balance
";
if ($user->uid > 0) {
$output .= t('Your account balance is %balance.', array('%balance' => $user->balance));
}
else {
$output .= t('You are not authorized to access this page.');
}
return $output;
}
```
There are several issues with this code:
- The example\_account\_balance() function is always tied to the currently logged in user. This means that other code would have to re-implement or refactor this function if they wanted to show the balance for a different user.
- Access control is both done improperly and tied to the page logic. Even if access is "denied," the menu system just sees a string to return. Even though the page text indicates that access is denied, the HTTP status code will still return 200 OK.
- The page logic (checking the account balance) is intertwined with the display itself. By not using the theme system, it's impossible to change the display without hacking the module. At most, this function should build an array of variables to pass into a theme() call.
To be "API first," this code should consist of the following functions:
1. A page callback that accepts an $account parameter. If it's blank, it can fall back to global $user. The page URL itself should probably contain a user ID, just as a URL like user/\[uid\]/edit does.
2. An access callback that checks for access to the given URL. In this case, it could probably just be set to '[user\_is\_logged\_in'](http://api.lullabot.com/user_is_logged_in/7), or a custom access callback if more complex logic is required.
3. A theme function or template that would accept the string to print as a parameter. It would be responsible for setting the page heading and generating most of the HTML markup.
## Documentation
Code isn't done until it's documented. At a basic level, that means that every function should have appropriate PHPDoc headings with information about what the function does, what parameters it accepts, and what it returns. It's just as important to verify that the documentation matches what the function actually does. Inline comments should be added as appropriate to describe particularly tweaky sections of code. Any hacks to work around bugs in other modules should include a comment describing the bug and a link to the upstream ticket or issue. If code is adding any new system variables (with [variable\_set()](http://api.lullabot.com/variable_set/7) and [variable\_get()](http://api.lullabot.com/variable_get/7), those should be documented if they are not exposed through a UI. For new modules, a README.txt should be included to describe the overall purpose of the module as well as any installation instructions.
## Unit and Functional Tests
If a project is using SimpleTest or Selenium, code should never be committed without the associated tests or changes to existing tests. "I'll add tests later" is a common phrase when a project is under a deadline, but those are exactly the times when automated testing is most important. Writing and validating automated tests is beyond the scope of this article, but for more information check out [the SimpleTest tutorial on Drupal.org](https://drupal.org/simpletest-tutorial-drupal7).
## Code Style and Standards
Finally, code should follow whatever code standards have been decided for the project. Typically Drupal projects use Drupal's code standards to simplify integration of code from various sources. Following code standards reduces the effort required to read code and ensures that it's easy to identify components of code. It can also help reduce issues with merging code written by developers on different operating systems. If code is being submitted by developers that frequently breaks code standards, it can usually be resolved with a bit of education and text editor configuration. For more information about code standards, check out the [Coding standards page on drupal.org](https://drupal.org/coding-standards), the [Coder module](https://drupal.org/project/coder), and the [Drupal Code Sniffer module](https://drupal.org/project/drupalcs).
## Next Steps
Code review is an ongoing process, and one that should be integrated into your development workflow. Code reviews don't just make for better code now; they push your team to write better code in the future. For more information about reviewing code, take a look at the [How to review Full Project applications](https://drupal.org/node/894256) page on Drupal.org and the pages it links to.
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ Technical Project Management ](/topics/project-management)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Location, Location, Location: SEO, Google Places, Structured Data, and Geositemaps"
url: "/articles/location-location-location-seo-google-places-structured-data-and-geositemaps"
type: article
date: 2012-02-08
updated: 2014-05-15
---
# Location, Location, Location: SEO, Google Places, Structured Data, and Geositemaps
# Location, Location, Location: SEO, Google Places, Structured Data, and Geositemaps
By
[ Karen Stevenson ](/about/karen-stevenson)
February 8, 2012
A client that has a lot of physical locations asked me how to improve search engine optimization (SEO) for those locations. They have web pages for each of their locations and were concerned about making sure all those individual location pages are getting ranked well. This doesn't seem to be a very well documented subject, but I found a number of ways to make sure that Google and other search engines know more about the physical locations that are related to a web site.
Google, in particular, has been making locative information more important than ever. If Google has any information about where I am located (and it usually does), it will push results in my location to the top of the search results list for any term I search for. For instance, if I search for 'Coffee' into a normal Google search, I get results like this, **even though I didn't add anything about my location in my search terms**. This makes it clear that having accurate location information in my web site must be very important.

## Create a Location Page for Each Location
The obvious starting point is to have at least a page, and perhaps a section of your site, devoted to each location. Each location page should include as much information as possible about the location.
## Add Microformats/RDF information
The next thing you can do is to make sure all the location pages (and for that matter, the other pages on the site) have been marked up with as much structured information as possible. There are various ways to do that, RDF, microformats, rich snippets. But the new method preferred by Google and other key search engines is to use the Schema.org standards.
I wrote an article about how to incorporate structured data into Drupal 7: [How Does RDF Work in Drupal 7?](https://www.lullabot.com/articles/how-does-rdf-work-in-drupal-7) In short, you need to enable the core RDF module along with the contributed Schema.org module, and set your locations up to comply with the right Schema.org standards. I believe you want to use the [Organization](https://schema.org/Organization) standard for the main headquarters page and the [LocalBusiness](https://schema.org/LocalBusiness) standard for the branch pages.
More information about Schema.org specifications is at:
- [Schema.org](https://schema.org/)
- [Schema.org FAQ](https://support.google.com/webmasters/answer/1211158?hl=en)
## Create a Geositemap
The next thing you can do is to create a geositemap and post it on the site. This is a specific form of XML sitemap that contains the geographic information for all your locations. There's not much written about this and it seems to be kind of a sleeper topic. But it makes sense, and it can't hurt.
A geositemap looks like the following:
```
http://www.example.com/download?format=kmlkmlhttp://www.example.com/download?format=georssgeorss
```
More information about geositemaps is at:
- [Google Webmaster Tools: Creating Geo Sitemaps](https://support.google.com/webmasters/answer/94555?hl=en)
- [Local Search Recipe: Making KML Files and GEO Sitemaps Are a Piece of Cake](https://www.searchenginejournal.com/local-search-recipe-making-kml-files-and-geo-sitemaps-are-a-piece-of-cake/20426/)
- [Building a Geositemap and KML file](https://gordoncampbellseo.wordpress.com/2011/02/14/geositemap-kml/)
- [KML and sitemaps for SEO â The definitive guide](http://www.martijnbeijk.com/tutorial/using-kml-for-local-seo/)
## Claim Your Google Places Listings
Google is hot at work trying to make all its searches more localized, and it is trying to create a comprehensive database of *Places*. Sites that have good *Places* information will rank better in Google than sites that do not.
So another task is to work with the Google Places information, which actually has nothing to do with the web site. To see what Google is presenting to users for your physical locations, go to Google Maps, select a city where you have a location and do a search for that location.
When you find it, click on the name in the dialog box.

That should turn up a Google *Place* file. It might look like the following. You can see from this example that that the owner has not claimed this site, it has not been verified, and anyone can edit it. It has pictures they didn't put there and a list of categories that may or may not make any sense.

The difference between claimed and unclaimed sites is that the unclaimed sites say "Business owner?" and provide a form where you can claim the listing, and the claimed sites say "Owner-verified listing."

You should find and claim all your locations in Google. There are some bulk upload programs available, but some of the articles I read said you can't rely on them and that it is better to have someone manually make sure each individual Place has been claimed and is accurate and representative.
See Google Places' [Personalized Dashboard](https://googleblog.blogspot.com/2009/06/local-business-center-dashboard-opens.html) for more ideas on optimizing those listings.
Some articles that explain this in more detail include:
- [What Does Googleâs New Layout Mean to Your Local SEO](https://www.seoworks.com.au/02-seo-tips-ideas/local-seo-google-layout/)
- [Google Local - Out of Date, Riddled with Spam But Absolutely Worth It](http://www.avenuewebmedia.com/external/google-local-out-date-riddled-spam-absolutely-worth-it)
- [How to improve rankings on google maps; Top 10 tips for Local SEO](https://www.chatmeter.com/2010/03/how-to-improve-rankings-on-google-maps-top-10-tips-for-local-search-marketing-and-seo)
## Create Custom Maps
There is some speculation that creating a custom Google maps may help SEO (for example, see [Google Custom Maps: A Goldmine For Local Businesses](https://searchengineland.com/guide/local-marketing), which says that pages created with custom maps are displayed prominently in Google results). As with everything about SEO, it's hard to separate the speculation from the reality, so who knows if, or how much, this will help your search results, but it makes sense to add a map to any article that talks about locations.
## In Summary
None of the tasks in this list are especially difficult to do, but it certainly seems that it is worth taking the time to do them. If you, or your clients, have physical locations, make sure the search engines know as much about them as possible!
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
- [ Search ](/topics/search)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Site Development Workflow: Keep it in Code"
url: "/articles/site-development-workflow-keep-it-in-code"
type: article
date: 2010-06-04
updated: 2018-03-05
---
# Site Development Workflow: Keep it in Code
# Site Development Workflow: Keep it in Code
The tools we use and the reasons we use them.
By
[ Jerad Bitner ](/about/jerad-bitner)
June 4, 2010
Almost a year ago now [Development Seed](https://developmentseed.org/) had an article on some of the [tools they were developing](https://developmentseed.org/blog/2009/jul/09/development-staging-production-workflow-problem-drupal) in order to address a very real, very important problemâthat of the whole development to staging to live development process. Up until these tools were available, this process consisted of an archaicâand quite frankly, pain in the buttâmethod of either duplicating the clicks and changes you made through the Drupal UI in your various environments, or putting all of your changes that needed to be made to the database into [update hooks](http://api.drupal.org/api/function/hook_update/6) that did very specialized queries, set variables, installed or uninstalled modules etc, etc. This came with a heavy price tag of testing your migration path over and over, resetting your database, and testing again. Oops! Small mistake there... change the code, reset the database, run it again, wash, rinse and repeat. These tools attempt to change all that and now that they're maturing, they've become a godsend for site builders and developers everywhere. Here is the workflow and process we are using at Lullabot and some of the finer points we've picked up along the way.
## Version Control
Keeping your code in a revision control system is imperative to the seasoned developer and more than a good idea for those not as experienced. It's more important when you are working on a project with multiple developers so that code conflicts can be resolved instead of accidentally overwriting what your buddy is working on, but also becomes part of a developers toolbox in many other ways.
We're moving to using [Git](https://git-scm.com/) for all of our projects at Lullabot due to the power and flexibility it gives us. Personally I love it so much that I can't stand to use Subversion anymore. When I am involved in a project where everyone is using Subversion [I use git-svn](https://www.lullabot.com/articles/making-the-transition-to-git) so that I can manipulate the Subversion repository on my local computer with Git. The ability to [track Subversion branches with Git](https://www.lullabot.com/articles/making-the-transition-to-git) is my latest candy and super useful for those situations where you need to do frequent merges between branches. Git succeeds in merging where Subversion really falls behind. I don't know if you've ever tried to do this in Subversion, but even the [RedBean book](https://svnbook.red-bean.com/en/1.1/ch04s03.html) introduces it as a 'headache'. And it is. So I use Git for this.
Git is also great for it's quick and easy branching. It's actually become a habit for me to create a branch for each and every issue that I work on for a project. It's so easy to create a branch, and merge it back into HEAD, or the main trunk, that there just isn't a reason to NOT do it. It's quick, it's easy and it keeps your changes separated in a way that if you're not done with the feature branch you're working on and need to fix something in say, the main branch, you can simply switch back to the main branch, fix it, commit it and then go back to what you were doing in your feature branch. With Subversion this is a total PITA because it's such a heavy process. It copies every single file, creating a new physical directory structure and then just try to figure out those merge instructions. With Git it's as simple as
```
$ git branch [branch-name]
$ git checkout [branch-name]
[make changes]
$ git checkout master
$ git merge [branch-name]
```
Now that I've beaten that horse to death, let's move on to the actual modules that are making our lives easier these days.
## Export it! Export it all!
Getting everything into code has gotten much easier now that [Chaos Tools](http://drupal.org/project/ctools) has come and cleared up some of the actual chaos around exporting objects into code. It provides a way to simply [add a few keys](https://civicactions.com/blog/2009/jul/24/using_chaos_tools_module_create_exportables) to your data schema, [implement a few hooks](http://www.stellapower.net/blog/using-chaos-tools-module-create-exportables), and voilà ! Your data object can be read from code, overwritten, re-exported or reverted back to code in an easy to read structure. If you're having trouble visualizing this, think of the way exporting views works and apply that to any arbitrary object that you might be able to think of. Yeah, cool. There are a few prerequisites (such as [machine names instead of auto-incremental integer columns](http://drupal.org/node/572880)) that you need to make sure your tables have, but other then that it's actually pretty straightforward. There's even a nice [tag on a lot of modules](http://drupal.org/taxonomy/term/11478) for Features integration that will give you a list of most of the modules that implement this on d.o - take a look, see how they work, and get your data exportable for goodness sake!
### Settings
Have you ever made changes to your local site, or your development site that you then need to make to your live site? Maybe you had to change your default theme, or perhaps the default comment settings for a node type. Instead of clicking these setting on your local and having to go through the UI again and click these settings, checkout [Strongarm](http://drupal.org/project/strongarm). Strongarm makes these settings exportable into a custom module.
### Layout
[Context](http://drupal.org/project/context) and [Panels](http://drupal.org/project/panels) have become our layout tools of choice. We tend to choose one or the other based on the project and what the client needs. Personally I can lay out a website much faster with Panels, but there are quite a few occasions where Context is simply a better fit for the situation. Usually this is the case when a client does not need the ability to change the layout around themselves, and it's more of a cleanup job than a brand new site. If I want to rapidly prototype complex layouts I use Panels. If I just need to replicate some basic rules of block visibility, I use Context.
### Boxes
[Boxes](http://drupal.org/project/boxes) has become a favorite of mine recently. It does two things really well that are a huge improvement over the traditional core Blocks. It allows for easy inline editing of content, and they're exportable. There is a module called *fe\_block* within the [Features Extra project](http://drupal.org/project/features_extra) that allows you to export blocks, but the problem with this is that they are not very good exports. In this case, it's a core decision to use an auto-incremental key on the boxes table (yes, blocks uses a boxes table, it's confusing, but bear with me) and this has the side effect that if you 'revert' a block and then read it from code, it is reinserted and the id changes automatically which means that anything that referenced that original block (like Context, or [Skinr](http://drupal.org/project/skinr) for example) no longer has the correct id to find what you're intending it to find. Boxes gets around this problem by using a machine name for it's unique identifier which plays much better with the exportable mindset. However, one thing that is pretty nice in fe\_block is that you can export a block's *settings*. The actual block itself doesn't work so well, but having those setting is really nice. For instance, if you have a block that is provided by a view and you want to override the title attribute of that block, you can use fe\_block to export those settings into code.
### Bring it all together
Then we get to [Features](http://drupal.org/project/features) itself. This is what brings it all together and why it's so important to have all of those other module exportables. Features gives you a nice UI to pick and choose what all you want exported, and then it creates the module for you. That's right, it **writes a module for you**. Features also gives you an easy way to detect if any of those exported things have changed, and update them if they have. Features' [Drush](http://drupal.org/project/drush) integration is awesome for allowing you to quickly determine what features are overridden (`$ drush fr`), revert them all (`$ drush fra`), or update them all (`$ drush fu-all`). [Special thanks](http://drupal.org/node/810958) to [James- aka: q0rban](https://www.lullabot.com/about/team/james-sansbury) for allowing us to type `$ drush fu [feature-name]`, for those days when things just aren't going too well and you need an expletive to help get you through the day. ;)
## And finally...
So now that I've shown you some of the tools, explained the importance of version control, and harped on getting things into code, how does any of this solve the problem of getting all of your changes from development, to staging, to the live site? Well, here's an example workflow from a project I am currently working on. Using the aforementioned tools and the following workflow I was able to make extensive changes to an existing site on my local computer within a new Git branch, export all of my changes to code wrapped within a few features, turn these features on in the staging environment, and have an upgraded copy of the live site without writing a single upgrade path.
### Tracking Subversion Branches with Git-svn
The current site I'm working with is in Subversion. It's Phase 2 of the project and this calls for a new branch of the code so that we can provide bug fixes to the current live site which is running on the trunk in our repository. A new Subversion branch was created for the architectural changes we're making to the site. Since we'll be making bug fixes to the trunk, we're going to need to keep merging those fixes into the new dev branch as well. And since merging is such a pain in Subversion, I'm going to use git-svn to work with this code repository. I want to be sure that the new Subversion branch is tracked in Git as a branch, so when I first checkout, or clone the repository I'm going to use the `-T` and `-b` operators.
svn repo structure
```
- branches
-- 2.x-dev
-- original
- trunk
```
Command to control this svn repo with git `$ git svn clone [svn-repo-location] -T trunk -b branches `
Resulting git structure `$ git branch -r `
```
2.x-dev
original
trunk
```
### Working with Our Subversion Branch in Git
Checkout the dev branch of the project which is now using Git locally. `$ git checkout 2.x-dev `
This new branch is connected to the Subversion branch and any changes that are committed to it locally with Git can be pushed into this new branch within the Subversion repository. Notice in the screenshot above that it says: "Committing to https://grammys.unfuddle.com/svn/grammys\_grammy365/branches/2.x-dev". You can see that I'm clearly not in a 'branches' directory, yet it certainly committed to the 2.x-dev branch. You may also note that the branch indicator in my screenshot says "(context-2.x)". This is actually a branch of the 2.x-dev branch. So it's worth noting that a `$ git svn dcommit` here is directly connected to the subversion branch "2.x-dev" that I branched off of, and will commit changes to that branch as well. (Whew)
### Fixing up the site
The current site I'm working on has a lot of custom blocks with PHP in them doing some things they probably shouldn't. The theme also needs to be redone, and the views and custom blocks need to be exported so that they're not being read from the database constantly. My buddy [Jay Wolf](http://drupal.org/user/70134) is helping me out with the theme, so he is making changes to the development branch in Subversion while I'm using Git on my local. As soon as he had the base theme with all the regions I needed, I got busy with the Context. Once I had the blocks cleaned up and [converted to boxes](http://drupal.org/node/787198), and started using the Views correctly, I setup separate contexts for the current sections of the site and placed the boxes where their predecessors went. The boxes are using [Skinr](http://drupal.org/project/skinr), and there are a few [Quicktabs](http://drupal.org/project/quicktabs) thrown in. We also want to make sure that the default theme is switched over to the new theme when this goes live.
### Merging fixes from trunk
Oh! But then here comes Dave with a bug fix to trunk! The bug fix goes in and we need to merge that change back into our new dev branch. A quick `$ git merge master` and we have the changes we just made to the master branch merged into our development branch and can continue on our way.
### Creating Features
Ok, back to our dev branch. Now that we have everything laid out with context, we're going to create features that correspond to the different contexts, as well as a site feature that will hold some global settings. Let's create our site feature first. This will hold the default theme settings as well as a sitewide context. Let's put this in /sites/all/modules/custom/features. Now that we have our new 'feature' module, we enable it and then take a look at our Features list. You'll see that it is marked 'Default' which means it is reading the feature from code.
And now that we have some basic site settings changed, we create our front page feature. Since the front page of this site holds a lot of the views and boxes referenced through our context, as soon as we tell our new front page feature to include the front page context, it also automatically detects some of the other elements that are referenced through this context. This feature is also wrapped up for us and we put it in the same place and enable it.
### Moving the changes to the staging server
Our staging server is running the new Subversion branch already, so we add these new feature modules to the dev branch:
```
$ git add custom/features
$ git commit -am "adding new features"
$ git svn dcommit
```
These modules are then on the dev server when we `$ svn up`. Now we can simply enable our feature modules, and like magic, all of our changes are now working on the staging site!
Oh, and don't forget to clear the caches ;) `$ drush cc all`
Published in:
- [ Deployment ](/topics/deployment)
- [ Drupal Development ](/topics/drupal-development)
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Upgrading Drupal core"
url: "/articles/upgrading-drupal-core"
type: article
date: 2013-07-24
updated: 2014-05-15
---
# Upgrading Drupal core
# Upgrading Drupal core
Learn a few tips to overcome the headache of upgrading your site
By
[ Juampy NR ](/about/juampy-nr)
July 24, 2013
The Drupal core team releases new versions of Drupal frequently. These can contain bug fixes, security patches, or both (sometimes, they even break the law and add new features). Keeping your websites up to date will ensure your users do not see unexpected errors produced by core, and helps prevent hackers from hijacking your site. Upgrading Drupal core in a live website with a few contributed modules is a pretty straightforward process. Doing it in a website with custom modules, large amounts of data, and custom business logic is no easy task. Patience and meticulousness are your best friends in this endeavor. Every website is different and most probably you will need to perform additional tasks when upgrading your Drupal site. At Lullabot we have come to a list of common steps which act as a guideline to reduce risks. Before we start with it, let's imagine the following (and pretty safe base to work from), scenario:
- You have a production environment, a development environment and a local environment.
- Your local environment has [Drush](http://drupal.org/project/drush) installed.
- You use a Version Control System such as [Git](https://git-scm.com/) to track code changes.
- You are able to extract a database backup of your production environment.
- You have SSH access to the Development and Production environments, plus permissions to manage the directory where Drupal is installed in each of these environments.
The local and development environments are useful, because they allow you to test upgrades before performing them on your production environment. If there's any way to avoid it, *do not* upgrade core straight in production (That would definitely qualify as "Extreme Programming.") The closest you can get to the scenario listed above, the better. It will help you trap bugs during the process before getting to production.
## Verifying current and target versions
Let's start by checking how many versions behind we are (the higher the number, the longest each forthcoming step it will be).
1. Locally, run **drush status** to check what version the site is at.
2. Go to , filter by the version of Drupal core you are using and see how many versions behind your site is.
3. Read each of the release notes (do not be sneaky here, open the whole release node and do not read just the summary) to pinpoint API changes that may break the site (normally this is not the case, but beware). The Drupal core team makes it very clear if there is a change in an API (for example, a function have been removed or its arguments have changed). If that happens, verify that your custom modules (and maybe even contributed ones) comply with that change.
## Updating the code locally and inspecting changes
Now let's get the new version in place locally:
1. Open a console and go to the root directory of our local Drupal installation
2. Make sure that your code and database are up to date. The former may mean to execute **git pull** if you are using Git, while the latter can be achieved by executing **drush sql-sync @myprodsite @self**. Alternatively, you can just extract a database dump of your production environment, recreate your local database and load that dump into it.
3. On the command line execute **drush pm-update drupal** to obtain the new release and update the database.
4. Now you have the new version of Drupal core in your local environment. You may like to check what has changed in case you want to restore, for example, your customized *.htaccess* file, or in case you do not want files such as INSTALL.txt or LICENSE.txt in your root directory. You can get an overview of these changes with **git status** and quickly revert changes in some of the files with **git checkout path-to-a-file**.
5. Now we are going to create a commit with the changes in core, while reading at what has changed. If you feel confident enough, you can just do **git add .** and commit that. Alternatively, if you want to really know what has changed in this new core release run **git add --patch**, which will go change by change and will let you decide if you want to commit or discard each of them. Note that this can be a very long process, but it will teach you a lot too.
## Testing
Test the new release locally before pushing your changes to the remote repository. Navigate through your site and simulate the most important tasks in it, verifying that there is nothing that break them. If there are automated tests (the best and rarest scenario), run them. Next step is to push our changes to the remote repository and update the development environment. Normally you will just need to do the following (unless an [automated job](https://www.lullabot.com/blog/podcasts/server-automation-and-deployment-tools) does it for you):
1. Log into the develop environment and install a copy of the production environment's database.
2. Go to the Drupal root directory.
3. Run the following commands:
4. git pull drush updatedb
That's it. Now let your QA team have a look at the site for a while. If your workflow follows a [SCRUM](https://en.wikipedia.org/wiki/Scrum_(development)) methodology or similar, try to get the core upgrade into the development environment at the start of a sprint so the rest of the team can test the new codebase while the sprint goes on.
## Hitting the red button to go to production
Once you have done enough testing, you are ready to go. The steps would be pretty similar to the ones in the previous section, except that you should already have backups of the current and previous states of the production's database. It is very useful to use [git tags](https://git-scm.com/book/en/Git-Basics-Tagging) for the production environment and point it to them instead of a branch, as it gives you the option to roll back to the previous tag in case something goes wrong.
Published in:
- [ Deployment ](/topics/deployment)
- [ Drupal Development ](/topics/drupal-development)
- [ Technical Project Management ](/topics/project-management)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Building a Drupal Dashboard "
url: "/articles/building-a-drupal-dashboard"
type: article
date: 2011-05-12
updated: 2014-05-15
---
# Building a Drupal Dashboard
# Building a Drupal Dashboard
Baby Got Backend, Part 2
By
[ Jeff Eaton ](/about/jeff-eaton)
May 12, 2011
In the previous installment of the ongoing "[Baby Got Backend](http://chicago2011.drupal.org/sessions/baby-got-backend-content-administrators-are-users-too)" article series, I wrote about [the various tools that Drupal site builders and developers can use](https://www.lullabot.com/articles/baby-got-backend) to customize the content editing and administration experience. With a well-populated toolbox, the biggest challenge is often *figuring out the needs* of the people using the site's backend, and determining what parts of their workflow to focus on.
In this installment, we're going to take a closer look at a simple example we encountered at Lullabot: a painful speed bump in our site management workflow, a brainstorming session to come up with solutions, and the iterative work to make the fix a reality. The specific problem we encountered wasn't earth-shattering, and the solution isn't rocket science, but the process we had to work through is universal.
## Step One: Spot the Pain Point
This year before DrupalCon Chicago, the Lullabot crew spent a few days getting valuable face-to-face time, planning for the coming year, and brainstorming about the challenges inherent in running a virtual company. One of the things we all agreed on was that the company web site (this one!) needed more fresh content on a regular basis. Coming up with material wasn't the problem; we all had ideas in the queue, and more than a dozen "almost finished" articles were already on the site, waiting for the finishing touches. In the scramble of day-to-day work, though, balls kept being dropped and we weren't getting them out the door in a timely fashion. Everyone was frustrated: it seemed like a simple problem, but repeating "We should just do it!" wasn't helping.

Over lunch, we asked everyone who'd worked on one of those stalled articles what (other than time!) was blocking them. After just a few minutes, it became clear there were a couple of common stumbling blocks:
- **No editor.** Quite a few of us worked on the site, but no one person was "in charge" of getting new content published. Questions about style issues, design considerations, and overlap with existing articles, often languished without a clear answer.
- **No way to know what's coming.** As unpublished articles accumulated, it was unclear which ones were "in the queue" and which ones were unfinished outlines. Without a central authority on scheduling, it was often tough to know whether publishing an article immediately would steal the spotlight from other announcements in the pipeline.
- **No easy way to pitch in.** All of the Lullabots loved giving and getting feedback during the editing process, but the only mechanism was shouting, "Help!" in IRC. It worked sometimes, but if not enough people spotted the shout-out, articles were delayed waiting for feedback.
- **Too easy to collide.** On several occasions, two people had started work on similar articles -- only realizing they were duplicating their efforts when they asked for feedback. It wasn't the end of the world, but collaboration would have been more rewarding.
- **Too many options.** Our almost-two-year-old design had accumulated over a dozen content types including Events, Workshops, Podcasts, Articles, Blog Posts, Podcasts, Videocasts, and more. As new Lullabots joined the company, they scratched their heads figuring out where to put their writing.
Whew. Like most discussions with the people who produce a web site's content, it resulted in a laundry list of tricky problems, most of which were unrelated to Drupal. We weren't hung up on ugly input forms or too many menus, we were frustrated by a clunky *process* for publishing.
## Step Two: Brainstorm Solutions
With our master list of pain points in hand, we started tossing out ideas. The easiest problem to fix was the lack of an editor: the most opinionated person in the room was quickly volunteered. In Open Source, expressing an opinion about a problem means you get to fix it. ;-)
From our conversations, we knew that the early stages of actual article production (brainstorming, first drafts, and so on) often occurred offline. Everyone was excited about the idea of an online whiteboard or wiki to keep track of the embryonic ideas, but concerned that something *too* complicated would be ignored. Most of the other coordination problems boiled down to giving everyone an easy-to-use view of upcoming articles' progress.
A few minutes with a pen and paper gave us one potential solution: an admin-only landing page that each of us would see on logging into Lullabot.com. It could list "unclaimed" article ideas, show the upcoming content that was ready for review or publication, and give us a starting point for common article types.

Our ideas for a solution were pretty fuzzy at this point, and we knew it wouldn't solve all of our problems, but the proposed dashboard was simple and focused, easy to implement without a huge time investment, and easy to abandon if it didn't work out for us. Those factors made it the textbook definition of an "easy win," and we cracked open Views to start building.
## Step Three: Implement it!
Our first stab was a simple view of articles, sorted by publish date, with a 'Published/Unpublished' field indicating whether they were visible to the public. While it was handy, it also became clear that the information we needed to display for current content and the information we needed to display for *upcoming* content was very different. Quickly, our proposed dashboard split into two views.
The first, our *Published Articles* view, was the simplest. It listed the latest ten articles on Lullabot.com, who authored them, and quick stats like the number of comments and the number of reads for each article. The second, our *Upcoming Articles* view, displayed unpublished articles and their scheduled publishing dates. We've been using the [Scheduler](http://drupal.org/project/scheduler) module for quite some time to handle timed publishing of our articles, so the "upcoming publishing date" for each article was already available for us to use in this view.
To help the Lullabots keep track of each articles' progress during the creation and editing process, we also added a custom [Flag](http://drupal.org/project/flag) called "Ready for Review" to the mix, and included the latest *revision message* for each article in the view. With those tools, anyone skimming the View can see what articles are completed and ready to be proofed, read notes on the latest edit to each article without clicking to another page, and see what the projected publish date is for each one.
[ ](https://www.lullabot.com/sites/lullabot.com/files/dashboard.jpeg)

To tie it all together, we created a simple Panel that combines the two views and contains quick links to our most commonly used content types. On one screen, the site's editor can keep track of what's in the pipeline; writers can jump to their articles without wading through the normal Drupal admin screens; and other Lullabots proofing and tweaking the articles can quickly get to the screens they care about.
## Step Four: Iterate!
The dashboard has served us well since we started using it, and in the first few weeks we spotted some easy improvements Although it was useful, we'd tucked it out of the way at an odd administrative URL. We gave it a prominent link in the site navigation for logged in users, and with the [Login Destination](http://drupal.org/project/login_destination) module, we turned the dashboard into the site's default landing page for logged in users. With the [Admin Notes](http://drupal.org/project/admin_notes) module, we're also adding a quick and dirty whiteboard to the panel. It will let us maintain a *very* simply list of unclaimed article ideas for writers to pick from when they need inspiration.
We have a few other crazy ideas floating around, including automatically adding items to the article whiteboard based on #hashtags used in our internal discussion system, pushing messages to our IRC bot when an article is ready for review, and so on.
Some of those ideas, of course, are more *fun* than practical. Continuing to gather feedback from the rest of the Lullabot writing crew has helped avoid wasted work, and keeps us focused on the real goal: making it easier for a virtual team to keep great content flowing.
## Conclusions
I've attached an exported [Feature](http://drupal.org/project/features) that gathers up our dashboard's current functionality to this article. (It requires the Features module, Scheduler, Panels and Views, Flag module, and all the other pieces that we already had installed on our site, but with those in place, it should work pretty smoothly.) If you've been reading this far, however, you'll recognize that the *technical* side of the process wasn't the tricky part. Like most site-specific UX and workflow issues, the challenge was identifying the real pain points, understanding the true workflow we needed to support, and finding low-risk ways to improve things quickly. Once we got our first pass out the door, continuing to gather feedback and iterate the tool helped closed the gap between "interesting idea" and "good fit."
In the future, we'll explore some of the more complex challenges faced on large client sites, and the custom development work that had to be done on top of existing Drupal tools. Even without that extra work, though, a little bit of listening and a good brainstorming session can go a long way to eliminating the pain points in a Drupal site's administration section.
What useful utilities have *you* put together to streamline things on your sites? Feel free to share ideas here!
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "How To Solve All Your Problems"
url: "/articles/how-to-solve-all-your-problems"
type: article
date: 2011-06-02
updated: 2014-05-15
---
# How To Solve All Your Problems
# How To Solve All Your Problems
Using the Drupal issue queue to your best advantage
By
[ Karen Stevenson ](/about/karen-stevenson)
June 2, 2011
OK, maybe not *all* your problems... But at least your Drupal problems. The Drupal ecosystem is composed of several thousand contributed modules, each one maintained by one or more volunteers. When things go wrong, as they will, you need to know how to get your problems resolved, and that requires understanding how to get the most out of the Drupal issue queues.
## Before You Report Anything
Before reporting a bug, *always* try the latest code to see if that resolves it (see the next section for how to tell which version is the latest). Many times people report problems that are already fixed in the latest code and they could immediately start using the working code instead of helplessly waiting for someone to respond to their issue.
## Go to the Project Issue Queue
Each Drupal project has its own issue queue. You can get to the project page by typing the project's url, like http://drupal.org/project/cck or http://drupal.org/project/views. Be sure to read the project page. Sometimes there are messages there about known issues.
## 'Latest Code' Means 'Development'
The project page will have a list of 'Releases'.

You should usually use the official releases (the ones with the green background), but if you have a bug you will need to try the development release (the ones with the red background) to pick up the bugfix (or to test if that fixes your bug). If you don't see a development release on the project page, you can click on the link that says 'View all releases' to see if there is a development release (a release for your version of the code that has '-dev' appended to the name). Testing the development release does not mean that you need to switch your production site to the -dev version, you should do this testing in a local or development environment.
Official releases are snapshots of the code as it existed at one moment in time. Once released, they are never again modified. So when bugs are fixed, they are fixed in the development version. The official release will not pick up the change. When you want to see if a bug is fixed, you need the latest code. When you need the latest code, it is always the development version.
Even the development release can be out of date. The development tarballs and zip files on the Drupal.org project pages are updated every 12 hours, so they only include patches committed at the time they were created. If a new patch is committed today, it is probably \*not\* yet in the project's tarball, even if that tarball has today's date on it. The only way to be absolutely sure you have a specific patch in a tarball is to wait for the first tarball created the day after the fix was committed. The git repository is the only place that is always guaranteed to have the latest code immediately.
## See if Your Problems Are Already Solved
Anytime you pull down a new version of the code, do the following:
1. Immediately run update.php. Be sure to watch carefully to see if there are messages that you need to re-run it or that anything went wrong.
2. Clear all caches.
3. If the problem is related to a CCK field (in Drupal 6) or a Fields field (in Drupal 7), go to the 'Manage Fields' screen for any field you are having trouble with, double-check that the values all look reasonable, and re-save it even if you don't change anything. Also go to the 'Display Fields' screen, double-check all the settings there, and re-submit that screen.
4. If your problem has anything to do with Views, be sure to clear the Views caches by clicking on the 'Clear cache' button in the Views 'Tools' tab. Edit the problematic view, and look at each field, argument, filter, or sort. Double-check that all the values look reasonable and re-save the fields and the view itself.
Then see if your problem still exists on the latest code.
## Find the Issue Queue
If you still have a problem, you need to see if the problem is already reported. In the right sidebar of the project page you will see a block that looks like the following:

Type a search term in the box, or just click on the 'Search' button to go to the full issue queue. Then filter the issue queue down to the major version you are using. You don't want to see issues about the D6 version of the code when you are using the D7 version. Keep in mind that there might be a closed issue that addresses your problem, so look at all issues, including closed ones (issues get closed after the bug is fixed).
Search to see if your issue is already reported. For instance, if you are seeing an error message, do an advanced search on a few key words from the error message.
If you can't find a similar issue, open a new issue and be sure to say you have already taken all these troubleshooting steps. Note that the new issue should now be filed against the -dev version, since that's what you're now using ... right?
## One Issue Per Issue
In some places, you would expect to open an issue and dump into it every problem you know about.
The Drupal.org issue queues don't work that way. We report each problem as its own issue. This way other people having the same problem can more easily find it and see what they need to do, or provide more information about the problem.
This is really important for maintainers. They don't need to see a lot of unrelated information, they need to see information that is specifically related to that one issue. And when it is fixed, they can close the issue and move on to another one.
## Make the Title Descriptive
The title of the issue is really important. Maintainers often scan the titles to decide how to handle it. Other users scan the titles to see if there is already a report about their problem.
A bad title would be something like 'Everything is broken!!!!!'. The only way to tell what that report is about is to read the whole issue. A good title will provide a useful summary of the heart of the issue, like 'Multiple value nodereference fields broken in some views'. A title like that makes it possible to tell at a glance if this issue might be related to your problem.
## Don't Say Too Little, or Too Much
Well this sounds impossible, but bear with me. 'Too Little' means terse reports like 'Everything is broken' or just pasting in an error message with no context about where you were or what you were doing when you saw it.
Remember that there are hundreds, maybe thousands of possible combinations for the things that might go into a field or a view. Saying 'I created a view and added a field and it broke' is way too little information.
'Too Much' means providing a complete history of everything on the site and all you hope it will accomplish, where you have to wade through several paragraphs to get to the place where the problem is described.
The goal is to provide enough information that the maintainer could start with a blank slate (they don't have your site or your data, after all), and get to a place where they could see the problem you are seeing. A useful report would look something like:
> 1. Create a new number field that uses a textfield widget. Set it up to be a single value field that is required.
> 2. Create a new view. Make it an unformatted list of fields. Add this field to the view and set it up to use the default formatter.
> 3. When you display the view you will see the error 'XXXX'.
## Don't Switch Versions
Stick with issues that use the same major version you are using. If you are using the Drupal 7 version and you see an issue that looks similar for Drupal 6, don't just jump in and say 'Me too' and switch the version on the issue. It is highly unlikely that the problem is actually the same from one version to another, and changing the version pollutes the issue, making it much harder for the maintainer to do anything with it.
In the above case, if you see a similar issue in another version but nothing in your version, create a new issue, marked with the version you are actually using. In the issue you can make a link to the other version's issue. If they are actually the same, the maintainer can say so and mark one as a duplicate, otherwise they can stand as separate issues that may (and probably do) have different implications and fixes.
## Don't Re-Open Closed Issues
If you find an old, closed issue that looks similar to your problem, generally you should not re-open it. Especially if it is very old and very long. The exception is if the issue was just closed recently and you have determined that something that was supposed to be fixed is actually still broken in the latest code. In that case you can re-open it with the explanation that you have tested it in the latest code and are still seeing the problem. Make sure you describe how you determined that it was still broken.
A corollary to this is not to post questions on closed issues. Issues that are marked 'fixed' or 'closed' or 'duplicate' all fall off the radar of the maintainers. They often don't even look at those issues any more. Post questions on new or open issues.
## Profit!
If you follow all the above steps, you will either:
- Discover that your problem has already been solved,
- Find an existing bug report you can follow to see when the problem is fixed -OR-
- Create a new bug report designed to provide the right information to help the project maintainer get the problem solved.
Congratulations! You're ready to leverage the Drupal.org issue queues. Obviously, there's a lot more to troubleshooting and problem-solving than bug reports and patches. By using the tools available on Drupal.org, though, you'll be able to leverage the work of thousands of other developers and site builders -- and they'll benefit from your work, too.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Installing Drupal with a Translation"
url: "/articles/installing-drupal-with-a-translation"
type: article
date: 2008-02-22
updated: 2014-05-15
---
# Installing Drupal with a Translation
# Installing Drupal with a Translation
By
[ Addison Berry ](/about/addison-berry)
February 22, 2008
\[embed\]http://blip.tv/file/2512182\[/embed\]
One of Drupal 6's nice new features allows you to install Drupal using a language other than English. This video will show you how to get a translation, extract it and run the installer with the new language. We will cover the extraction process using three methods (GUI unzip utility, command line and CPanel) because it is important to make sure it extracts properly for the installer to see it.
The video assumes you are already familiar with the basic installation process and only covers the translation part.
Important Note for the geekier people out there - DO NOT USE CVS. A CVS checkout of a translation will not work because the correct structure for the files is created during the tarball packaging process. I learned this the hard way. :-)
\- There are other videos that show more of the multi-language features of Drupal 6. The packaging of installation translations has changed a bit since the end of January 2008 so I wanted to cover the new process for just this part.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Learning JavaScript from PHP - a Comparison"
url: "/articles/learning-javascript-from-php-a-comparison"
type: article
date: 2009-08-17
updated: 2014-05-15
---
# Learning JavaScript from PHP - a Comparison
# Learning JavaScript from PHP - a Comparison
By
[ Nate Lampton ](/about/nate-lampton)
August 17, 2009
This is a basic comparison between PHP and JavaScript. It's intended for users familiar with PHP and looking for JavaScript equivalents.
**JavaScript and PHP Comparisons:**
- [Variables](#variables)
- [Scope](#variables-scope)
- [Types](#variables-types)
- [Casting](#variables-casting)
- [NULL and empty() values](#variables-empty)
- [Booleans](#variables-booleans)
- [Case Sensitivity](#variables-case-sensitivity)
- [Dumping variables](#variables-dumping)
- [Objects and Arrays](#objects-arrays)
- [Declaration](#objects-arrays-declaration)
- [Syntax](#objects-arrays-syntax)
- [Associative Arrays](#objects-arrays-associative)
- [Control Structures](#control-structures)
- [for() loop](#control-structures-for)
- [foreach() loop](#control-structures-foreach)
## Variables
### Variable Scope
PHP and JavaScript take two very different approaches to declaring variables. In PHP, all variables are *local* in scope unless declared as global. JavaScript is opposite, and all variables are *global* unless declared with the `var` keyword.
**PHP**
```php
function foo() {
$variable_a = 'value'; // Local variable declaration.
}
function bar() {
print $variable_a; // Prints nothing.
}
function foo() {
global $variable_b; // Global variable declaration.
$variable_b = 'value';
}
function bar() {
global $variable_b;
print $variable_b; // Prints 'value'.
}
```
**JavaScript**
```
function foo() {
var variableA = 'value'; // Local variable with use of "var".
}
function bar() {
alert(variableA); // Variable not defined error.
}
function foo() {
variableB = 'value'; // Global variable, no "var" declaration.
}
function bar() {
alert(variableB); // alert('value')
}
```
An interesting twist is JavaScript also allows scoping within functions. When using the "var" declaration, variables are available for everything in the current function or any sub-functions.
**PHP**
```
function foo() {
$variable_a = 'value'; // Local variable declaration.
function bar() {
print $variable_a; // Prints nothing.
}
}
```
**JavaScript**
```
function foo() {
var variableA = 'value'; // Local variable with use of "var".
function bar() {
alert(variableA); // alert('value');
}
}
```
### Variable Types
Both PHP and JavaScript are *loosely typed*, meaning a variable can be of any type, and change from one type to another. However both PHP and JavaScript keep track of the type of variables, and you can check this type.
**PHP**
```php
$foo = 3;
is_int($foo); // TRUE
$foo = '3';
is_int($foo); // FALSE
is_string($foo); // TRUE
```
**JavaScript**
```
var foo = 3;
type_of(foo); // 'number'
foo = '3';
type_of(foo); // 'string'
```
### Casting Variables
Every now and then you might need to cast variables to a specific type. This is extremely important when dealing with JavaScript's `+` operator, which is used for both string concatenation and for numeric addition.
**PHP**
In PHP, variables may be cast to certain type by using parenthesis. String concatenation is done with "." and addition with "+".
```php
$foo = '3.5 kg';
$bar = (float)$foo; // 3.5
$bar = (int)$foo; // 3
$baz = (string)$foo; // '3.5 kg'
print $bar + $baz; // 6
print $bar . $baz; // '33'
```
**JavaScript**
JavaScript has functions specifically for casting variables to numbers. Both string concatenation and addition is done with "+". If mixing a string and a number with "+", concatenation will take precedence over addition.
```
var foo = '3.5 kg';
var bar = parseFloat(foo); // 3.5
bar = parseInt(foo); // 3
var baz = '3';
alert(bar + baz); // '33'
alert(bar + parseInt(baz)); // 6
```
### Checking for NULL or empty() values
Variables in PHP don't have to be defined for you to use them, though if you're working with E\_ALL compliance on (not the default of most PHP installs), your script will throw a notice if you try to use an undeclared variable. JavaScript is a bit mixed concerning undeclared variables, if you attempt to modify or compare with an undeclared variable, the script will break entirely, but you can check the variable status using typeof() or in conditional statements containing only that variable.
**PHP**
```php
// Check if a variable is declared at all.
if (!isset($foo)) {
$foo = TRUE;
}
// Or check if a variable has a value that equates to FALSE.
// This includes variables that have not been declared.
if (empty($bar)) {
$bar = TRUE;
}
```
**JavaScript**
```
// Check if a variable is declared at all.
if (typeof(foo) == 'undefined') {
var foo = true;
}
// Or check if a variable has a value that equates to false.
// This includes variables that have not been declared.
if (!bar) {
var bar = true;
}
// However an undeclared variable can't be used in comparisons.
if (baz == false) { // Variable undefined error.
var baz = true;
}
```
### Boolean Variables
A simple but important thing to remember is that JavaScript only recognizes the keyword `true` in all lowercase. PHP accepts both uppercase and lowercase.
**PHP**
```php
is_boolean(TRUE); // TRUE
is_boolean(true); // TRUE
is_boolean(True); // TRUE
```
**JavaScript**
```
typeof(true); // 'boolean'
typeof(TRUE); // 'undefined'
typeof(True); // 'undefined'
```
### Case Sensitivity
Both JavaScript and PHP are case sensitive in their *variables*. PHP is not case-sensitive in function or class declarations, but JavaScript is case sensitive for these also.
**PHP**
```php
// Variable case:
$foo = 'bar';
print $foo; // Prints 'bar'.
print $Foo; // Prints nothing.
// Function case:
function foo() {
print 'bar';
}
foo(); // Prints 'bar'.
Foo(); // Prints 'bar'.
```
**JavaScript**
```
// Variable case:
var foo = 'bar';
alert(foo); // alert('bar')
alert(Foo); // Variable not defined error.
// Function case:
function foo() {
alert('bar');
}
foo(); // alert('bar')
Foo(); // Function not defined error.
```
## Objects and Arrays
In PHP, objects and arrays are two distinctly different things and have different syntaxes. In JavaScript, objects and arrays are often interchangeable, and you can switch between syntaxes freely.
### Declaring an Object or Array
There are a few different ways to declare an object or an array in both JavaScript and PHP. The key difference between PHP and JavaScript is that *JavaScript does not have associative arrays*. Arrays in JavaScript are always numeric based. However, since objects may use array-like syntax, simply declare a new object when you'd use an associative array in PHP.
**PHP**
```php
// Define an array.
$foo = array(); // New empty array.
$foo = array('a', 'b', 'c'); // Numeric index.
$foo = array('a' => '1', 'a' => '2', 'c' => '3'); // Associative.
// Define an object.
$bar = new stdClass(); // New empty object.
$bar->a = '1';
$bar->b = '2';
$bar->c = '3';
```
**JavaScript**
```
// Define an array (longhand).
var foo = new Array(); // New empty array.
var foo = new Array('a', 'b', 'c'); // Numeric index.
// Define an array (shorthand, more common).
var foo = []; // New empty array.
var foo = ['a', 'b', 'c']; // Numeric index.
// Define an object.
var bar = {}; // New empty object.
var bar = { // New populated object.
a: '1',
b: '2',
c: '3'
};
```
As you might notice in the last example, declaring an object in JavaScript uses the format commonly known as [JSON](http://www.json.org/), which stands for "JavaScript Object Notation". JSON strings having become very popular as a faster alternative to XML, and can be read and created with the PHP functions [json\_encode()](https://www.php.net/json_decode) and [json\_decode()](https://www.php.net/json_decode).
### Object and Array Syntax
JavaScript and PHP are very similar in array notation, though they differ more in their object notation. The key difference is that PHP uses an array "->" to reference items within objects, while JavaScript uses the dot ".".
**PHP**
```php
$foo = array('a', 'b', 'c'); // New numeric index array.
print $foo[0]; // 'a'
$bar = new stdClass();
$bar->a = '1';
print $bar->a; // '1';
```
**JavaScript**
```
var foo = ['a', 'b', 'c']; // New array.
alert(foo[0]); // 'a'
var bar = { a: '1', b: '2', c: '3' }; // New object.
alert(bar.a); // '1'
```
### Using Objects as Associative Arrays
Let's take one more look at defining an object in JavaScript and see how it can be used to compensate for the lack of associative arrays in JavaScript.
**PHP**
```php
$bar = array(
'a' => '1',
'b' => '2',
'c' => '3',
);
```
**JavaScript**
JavaScript doesn't have associative arrays, but defining an object works identically to an associative array in PHP.
```
var bar = {
a: '1',
b: '2',
c: '3'
};
```
As mentioned earlier, in JavaScript array and object syntaxes can be mixed freely, so this new object can be referenced either as an array or an object.
```
// Using array syntax, even though this is an object.
alert(bar['a']); // '1'
// Or the standard object property syntax.
alert(bar.b); // '2'
```
If we had a multi-level object, we can even combine the bracket and dot syntaxes.
```
var bar = {
a: { red: 'my favorite', blue: 'not so bad' },
b: '2',
c: '3'
}
alert(bar.a['red']); // 'my favorite'
alert(bar['a'].blue); // 'not so bad'
```
### Dumping variables
**PHP**
```php
var_dump($foo);
// Or
print_r($foo);
```
**JavaScript**
```
console.log(foo); // Prints to Firebug or Safari console.
```
## Logic Constructs
### for()
The classic `for()` construct is supported nearly identically in PHP and JavaScript.
**PHP**
```php
for ($n = 0; $n 10; $n++) {
print $n;
}
```
**JavaScript**
```
// Note that variables should always be
// prefixed with "var" to define a local scope.
for (var n = 0; n < 10; n++) {
alert(n);
}
```
### foreach()
PHP's `foreach()` construct can easily be converted to JavaScript's `for()`.
**PHP**
```php
foreach ($array as $key => $value) {
// Do something.
}
```
**JavaScript**
```
for (var key in array) {
// There is no "value" directly, but you can get it easily.
var value = array[key];
// Do something.
}
```
**Wrap up**
There are still a lot of other topics that could be covered (JavaScript is a language with a lot of tricks), but this should be a good foundation to work from. Hope you enjoy!
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Creating Custom CCK Fields"
url: "/articles/creating-custom-cck-fields"
type: article
date: 2009-07-15
updated: 2014-05-15
---
# Creating Custom CCK Fields
# Creating Custom CCK Fields
By
[ Karen Stevenson ](/about/karen-stevenson)
July 15, 2009
You can create custom CCK fields, widgets, and formatters for any situation, but it can be hard to see how to do it. I finally found time to create an 'Example' module that creates a simple field, formatter, and widget, with lots of embedded documentation about what belongs where. You need to create three files, an .info file, an .install file, and the module itself. The code below creates a very simple textfield, but it can be used as a starting point for any custom module. I'm also attaching a .zip file with the contents of this custom module.
## The .info File
```
; $Id$
name = Example field
description = Defines an example field type.
dependencies[] = content
package = CCK
core = 6.x
```
## The .install File
```php
// $Id$
// Notify CCK when this module is enabled, disabled, installed,
// and uninstalled so CCK can do any necessary preparation or cleanup.
/**
* @file
* Implementation of hook_install().
*/
function example_install() {
drupal_load('module', 'content');
content_notify('install', 'example');
}
/**
* Implementation of hook_uninstall().
*/
function example_uninstall() {
drupal_load('module', 'content');
content_notify('uninstall', 'example');
}
/**
* Implementation of hook_enable().
*
* Notify content module when this module is enabled.
*/
function example_enable() {
drupal_load('module', 'content');
content_notify('enable', 'example');
}
/**
* Implementation of hook_disable().
*
* Notify content module when this module is disabled.
*/
function example_disable() {
drupal_load('module', 'content');
content_notify('disable', 'example');
}
```
## The Module
```php
// $Id$
/**
* @file
* An example to define a simple field, widget, and formatter.
* A module could define only a field, only a widget, only a
* formatter, or any combination. Widgets and formatters must
* declare what kind of field they work with, which can be any
* existing field as well as any new field the module creates.
*/
//==========================================//
// DEFINING A FIELD
//==========================================//
/**
* Implementation of hook_field_info().
*/
function example_field_info() {
return array(
// The machine name of the field,
// no more than 32 characters.
'example' => array(
// The human-readable label of the field that will be
// seen in the Manage fields screen.
'label' => t('Example field'),
// A description of what type of data the field stores.
'description' => t('Store text data in the database.'),
// An icon to use in Panels.
'content_icon' => 'icon_content_text.png',
),
);
}
/**
* Implementation of hook_field_settings().
*/
function example_field_settings($op, $field) {
switch ($op) {
// Create the form element to be used on the field
// settings form. Field settings will be the same for
// all shared instances of the same field and should
// define the way the value will be stored
// in the database.
case 'form':
$form = array();
$form['max_length'] = array(
'#type' => 'textfield',
'#title' => t('Maximum length'),
'#default_value' => is_numeric($field['max_length']) ? $field['max_length'] : 255,
'#required' => FALSE,
// Use #element_validate to validate the settings.
'#element_validate' => array('_example_length_validate'),
'#description' => t('The maximum length of the field in characters. Must be a number between 1 and 255'),
);
return $form;
// Return an array of the names of the field settings
// defined by this module. These are the items that
// CCK will store in the field definition
// and they will be available in the $field array.
// This should match the items defined in 'form' above.
case 'save':
return array('max_length');
// Define the database storage for this field using
// the same construct used by schema API. Most fields
// have only one column, but there can be any number
// of different columns. After the schema API values,
// add two optional values to each column,
// 'views', to define a Views field
// 'sortable', to add a Views sort field
case 'database columns':
$columns['value'] = array(
'type' => 'varchar',
'length' => is_numeric($field['max_length']) ? $field['max_length'] : 255,
'not null' => FALSE,
'sortable' => TRUE,
'views' => TRUE,
);
return $columns;
// Optional: Make changes to the default $data array
// created for Views. Omit this if no changes are
// needed, use it to add a custom handler or make
// other changes.
case 'views data':
// Start with the $data created by CCK
// and alter it as needed. The following
// code illustrates how you would retrieve
// the necessary data.
$data = content_views_field_views_data($field);
$db_info = content_database_info($field);
$table_alias = content_views_tablename($field);
$field_data = $data[$table_alias][$field['field_name'] .'_value'];
// Make changes to $data as needed here.
return $data;
}
}
/**
* Custom validation of settings values.
*
* Create callbacks like this to do settings validation.
*/
function _example_length_validate($element, &$form_state) {
$value = $form_state['values']['max_length'];
if ($value && !is_numeric($value)|| $value < 1 || $value > 255) {
form_set_error('max_length', t('"Max length" must be a number between 1 and 255.'));
}
}
/**
* Implementation of hook_field().
*/
function example_field($op, &$node, $field, &$items, $teaser, $page) {
switch ($op) {
// Do validation on the field values here. The widget
// will do its own validation and you cannot make any
// assumptions about what kind of widget has been used,
// so don't validate widget values, only field values.
case 'validate':
if (is_array($items)) {
foreach ($items as $delta => $item) {
// The error_element is needed so that CCK can
// set an error on the right sub-element when
// fields are deeply nested in the form.
$error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
if (!empty($item['value'])) {
if (!empty($field['max_length']) && drupal_strlen($item['value']) > $field['max_length']) {
form_set_error($error_element, t('%name: the value may not be longer than %max characters.', array('%name' => $field['widget']['label'], '%max' => $field['max_length'])));
}
}
}
}
return $items;
// This is where you make sure that user-provided
// data is sanitized before being displayed.
case 'sanitize':
foreach ($items as $delta => $item) {
$example = check_plain($item['value']);
$items[$delta]['safe'] = $example;
}
}
}
/**
* Implementation of hook_content_is_empty().
*
* CCK has no way to know if something like a zero is
* an empty value or a valid value, so return
* TRUE or FALSE to a populated field $item array.
* CCK uses this to remove empty multi-value elements
* from forms.
*/
function example_content_is_empty($item, $field) {
if (empty($item['value'])) {
return TRUE;
}
return FALSE;
}
/**
* Implementation of hook content_generate().
*
* Optional, provide dummy value for nodes created
* by the Devel Generate module.
*/
function example_content_generate($node, $field) {
$node_field = array();
// Generate a value that respects max_length.
if (empty($field['max_length'])) {
$field['max_length'] = 12;
}
$node_field['value'] = user_password($field['max_length']);
return $node_field;
}
/**
* Implementation of hook_token_list()
* and hook_token_values().
*
* Optional, provide token values for this field.
*/
function example_token_list($type = 'all') {
if ($type == 'field' || $type == 'all') {
$tokens = array();
$tokens['example']['raw'] = t('Raw, unfiltered text');
$tokens['example']['formatted'] = t('Formatted and filtered text');
return $tokens;
}
}
function example_token_values($type, $object = NULL) {
if ($type == 'field') {
$item = $object[0];
$tokens['raw'] = $item['value'];
$tokens['formatted'] = isset($item['view']) ? $item['view'] : '';
return $tokens;
}
}
//==========================================//
// DEFINING A FORMATTER
//==========================================//
/**
* Implementation of hook_theme().
*/
function example_theme() {
return array(
// Themes for the formatters.
'example_formatter_default' => array(
'arguments' => array('element' => NULL),
),
'example_formatter_plain' => array(
'arguments' => array('element' => NULL),
),
);
}
/**
* Implementation of hook_field_formatter_info().
*
* All fields should have a 'default' formatter.
* Any number of other formatters can be defined as well.
* It's nice for there always to be a 'plain' option
* for the raw value, but that is not required.
*
*/
function example_field_formatter_info() {
return array(
// The machine name of the formatter.
'default' => array(
// The human-readable label shown on the Display
// fields screen.
'label' => t('Default'),
// An array of the field types this formatter
// can be used on.
'field types' => array('example'),
// CONTENT_HANDLE_CORE: CCK will pass the formatter
// a single value.
// CONTENT_HANDLE_MODULE: CCK will pass the formatter
// an array of all the values. None of CCK's core
// formatters use multiple values, that is an option
// available to other modules that want it.
'multiple values' => CONTENT_HANDLE_CORE,
),
'plain' => array(
'label' => t('Plain text'),
'field types' => array('example'),
'multiple values' => CONTENT_HANDLE_CORE,
),
);
}
/**
* Theme function for 'default' example field formatter.
*
* $element['#item']: the sanitized $delta value for the item,
* $element['#field_name']: the field name,
* $element['#type_name']: the $node->type,
* $element['#formatter']: the $formatter_name,
* $element'#node']: the $node,
* $element['#delta']: the delta of this item, like '0',
*
*/
function theme_example_formatter_default($element) {
return $element['#item']['safe'];
}
/**
* Theme function for 'plain' example field formatter.
*/
function theme_example_formatter_plain($element) {
return strip_tags($element['#item']['safe']);
}
//==========================================//
// DEFINING A WIDGET
//==========================================//
/**
* Implementation of hook_widget_info().
*
* Here we indicate that the content module will handle
* the default value and multiple values for these widgets.
*
* Callbacks can be omitted if default handing is used.
* They're included here just so this module can be used
* as an example for custom modules that might do things
* differently.
*/
function example_widget_info() {
return array(
// The machine name of the widget, no more than 32
// characters.
'example_widget' => array(
// The human-readable label of the field that will be
// seen in the Manage fields screen.
'label' => t('Example widget'),
// An array of the field types this widget can be
// used with.
'field types' => array('example'),
// Who will handle multiple values, default is core.
// 'CONTENT_HANDLE_MODULE' means the module does it.
// See optionwidgets for an example of a module that
// handles its own multiple values.
'multiple values' => CONTENT_HANDLE_CORE,
'callbacks' => array(
// Who will create the default value, default is core.
// 'CONTENT_CALLBACK_CUSTOM' means the module does it.
// 'CONTENT_CALLBACK_NONE' means this widget has
// no default value.
'default value' => CONTENT_CALLBACK_DEFAULT,
),
),
);
}
/**
* Implementation of hook_widget_settings().
*/
function example_widget_settings($op, $widget) {
switch ($op) {
// Create the form element to be used on the widget
// settings form. Widget settings can be different
// for each shared instance of the same field and
// should define the way the value is displayed to
// the user in the edit form for that content type.
case 'form':
$form = array();
$size = (isset($widget['size']) && is_numeric($widget['size'])) ? $widget['size'] : 60;
$form['size'] = array(
'#type' => 'textfield',
'#title' => t('Size of textfield'),
'#default_value' => $size,
'#element_validate' => array('_element_validate_integer_positive'),
'#required' => TRUE,
);
return $form;
// Return an array of the names of the widget settings
// defined by this module. These are the items that
// CCK will store in the widget definition and they
// will be available in the $field['widget'] array.
// This should match the items defined in 'form' above.
case 'save':
return array('size');
}
}
/**
* Implementation of hook_widget().
*
* Attach a single form element to the form.
*
* CCK core fields only add a stub element and builds
* the complete item in #process so reusable elements
* created by hook_elements can be plugged into any
* module that provides valid $field information.
*
* Custom widgets that don't care about using hook_elements
* can be built out completely at this time.
*
* If there are multiple values for this field and CCK is
* handling multiple values, the content module will call
* this function as many times as needed.
*
* @param $form
* the entire form array,
* $form['#node'] holds node information
* @param $form_state
* the form_state,
* $form_state['values'][$field['field_name']]
* holds the field's form values.
* @param $field
* the field array
* @param $items
* array of default values for this field
* @param $delta
* the order of this item in the array of
* subelements (0, 1, 2, etc)
*
* @return
* the form item for a single element for this field
*/
function example_widget(&$form, &$form_state, $field, $items, $delta = 0) {
$element['value'] = array(
'#type' => 'textfield',
'#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL,
'#autocomplete_path' => $element['#autocomplete_path'],
'#size' => !empty($field['widget']['size']) ? $field['widget']['size'] : 60,
'#attributes' => array('class' => 'example'),
'#maxlength' => !empty($field['max_length']) ? $field['max_length'] : NULL,
);
// Used so that hook_field('validate') knows where to
// flag an error in deeply nested forms.
if (empty($form['#parents'])) {
$form['#parents'] = array();
}
$element['_error_element'] = array(
'#type' => 'value',
'#value' => implode('][', array_merge($form['#parents'], array('value'))),
);
return $element;
}
```
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Why sticking to best practices matters"
url: "/articles/why-sticking-to-best-practices-matters"
type: article
date: 2013-06-19
updated: 2014-05-15
---
# Why sticking to best practices matters
# Why sticking to best practices matters
Help yourself, the project and everyone in your team by following best practices as much as possible
By
[ Juampy NR ](/about/juampy-nr)
June 19, 2013
At Lullabot, while working for a client's project, we assign resolved tickets to other bots for peer review. This process has turned out to be very effective in helping knowledge share, improving our coding standards and doing general QA (note: this does not exclude an external QA test).
Most of the backend and frontend developers at Lullabot have good knowledge of all sort of best practices, whether it is from Drupal.org, JQuery, Compass, AngularJS or any technology that we use. Whenever we need to solve a problem we think what is the standard and most effective way of solving this?.
When a project has been developed following coding standards and relying in third party code as much as possible, it is much more probable that new people joining the project will understand its APIs and be able to start coding without extra help, which minimizes the time someone spends on reading the quirks and custom logic that a project has. Similarly, if other company retakes the project later on, the same rule applies: they will know where the logic is; their assumptions will most probably be correct and they won't have to spend much time evaluating the overall complexity of the site.
## How can I learn Drupal's Coding Standards?
All of these docs are at Drupal.org. Here are links to them:
- Make sure you know the [Drupal Coding Standards](https://drupal.org/coding-standards).
- If you write custom JavaScript in a Drupal project, there are also [JavaScript standards](https://drupal.org/node/172169).
- For documenting your code, there are [Doxygen documentation standards](https://drupal.org/coding-standards/docs).
There are also guides available for each role within a team:
- [Theming guide](https://drupal.org/documentation/theme).
- [Developer guide](https://drupal.org/documentation/develop).
- The [Examples module](https://drupal.org/project/examples) is a great source of best practices too.
## How can I learn all the above?
The amount of information at the above links may be overwhelming. The best way to learn them is to [get involved in the Community](https://drupal.org/getting-involved). Depending on your skills there are different ways to start. The more you participate in contributed modules and core, the more you will understand how Drupal works and the more you will learn from the great and friendly minds which are behind it and which will review your code and give you tips to improve it. Take it as a hobby, a place where you learn for free and are also helping to improve a tool you use.
Looking forward for seeing you in the issue queues!
Published in:
- [ UX & Design ](/topics/design-and-ux)
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Making the transition to Git"
url: "/articles/making-the-transition-to-git"
type: article
date: 2010-05-10
updated: 2019-02-05
---
# Making the transition to Git
# Making the transition to Git
For the Subversion impaired
By
[ Jerad Bitner ](/about/jerad-bitner)
May 10, 2010
So you've probably already heard that [Drupal.org is turning to Git](https://www.lullabot.com/articles/git-is-coming-soon-to-drupalorg) for it's version control system (VCS) needs, but you may be wondering, "Well, how do I get into the practice of using Git?". And if, like most developers, you are using Subversion for most of your projects, then I have a really great suggestion on how to start making this transition.
## Background
### Subversion impaired?
I recently read an article that stated that if you are used to using Subversion for your VCS needs, that you are basically brain damaged! It's a play on an article that compares Mercurial to Subversion, and the concept is really quite similar, being that Git and Mercurial are both distributed VCS systems, while Subversion is centralized. While I'm not sure I would go as far to say you are 'brain damaged', it really is a fundamental shift in thinking.
### A basic but imperative concept
The very basic concept you **must** understand in the difference between Subversion and Git, is that a **Subversion** repository is hosted remotely and you *just have a checkout* of the files that are in that repository on your local machine. With **Git**, you actually get the *whole repository* locally (hence the term 'clone') and then you *also* have a checkout of the files that are in that local repository. Here is a tool to help you transition.
## Git-svn
Most development shops use Subversion for their VCS needs. It's not really going anywhere soon, and the transition to a Git world will probably be a little slow, especially if you are collaborating with other shops or people who do not know Git, are unwilling to learn Git, or you just don't have the time/resources to teach or convince them of Git. So if all of your repos belong to SVN, how can you get into the practice of using Git in your daily work life? In steps [git-svn](https://www.kernel.org/pub/software/scm/git/docs/git-svn.html). **This great tool allows you to work with a remote Subversion repository while using Git on your local machine!** You can run all of your normal Git commands locally, merging, branching and even merging Subversion branches (which Git is far superior at doing) all without using Subversion commands *and* still keeping your remote Subversion repository intact. Other developers can still use it exactly how they are used to, and run Subversion commands if they want... but you will have the real power!
## Installation
Installation is pretty simple, but the guys over at [MetalToad](https://www.metaltoad.com/blog/using-git-svn-manage-standard-and-non-standard-branches) have already got that covered, as well as some basic commands and [cheatsheets](http://cheat.errtheblog.com/s/gitsvn/), so I'll not go into further detail here.
## Typical workflow
My typical workflow when having to collaborate with another team who is using Subversion is as follows:
1. Checkout the code.
2. `$git svn clone ` This pulls down the Subversion repository just like `$svn checkout` would and then puts it into a Git repository locally, adding the original as a [remote branch](http://www.ftp-rfc822-org.lkams.kernel.org/software/scm/git/docs/git-remote.html) so that you can still push your code back into the original Subversion repository.
3. Make your code changes.
4. Commit your code (purely through Git).
5. `$git commit -am "typical commit message" ` This is basically, 'Save'. It commits your changes to your repository, which with Git, is actually on your machine (a main difference between svn and git, as I mentioned). You have not changed anything in the original Subversion repository at this point, everything is *still local*.
6. Check the repository for changes.
7. `$git svn fetch ` `$git svn rebase ` These two commands are basically the equivalent to doing a `$svn up`. The first command pulls down any changes that are in the repository, and the second command rewinds any changes you made, applies the new ones it just got from the repository, and then replays your work on top of those changes.
8. Push your code to the remote repository.
9. `$git svn dcommit ` For all to all intents and purposes, this command is the basic equivalent to `$svn commit -m "typical commit message"`. It actually does a bit more than that. *From the [documentation](http://ftp.kernel.org/pub/software/scm/git/docs/git-svn.html):*
> Commit each diff from a specified head directly to the SVN repository, and then rebase or reset (depending on whether or not there is a diff between SVN and head). This will create a revision in SVN for each commit in git. It is recommended that you run git svn fetch and rebase (not pull or merge) your commits against the latest changes in the SVN repository. An optional revision or branch argument may be specified, and causes git svn to do all work on that revision/branch instead of HEAD. This is advantageous over set-tree (below) because it produces cleaner, more linear history.
## Conclusion
Git is coming. It's better than you can imagine, and with the imminent approach of Git coming to Drupal, you can finally have everything in one VCS. This is a way to start edging into it, using it in your daily life if you use Subversion regularly now. And CVS? A thing of the past... barely worth mentioning. I for one welcome our new Git overlords!
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "GitHub Pull Request Builder for Drupal"
url: "/articles/github-pull-request-builder-for-drupal"
type: article
date: 2013-07-17
updated: 2021-01-12
---
# GitHub Pull Request Builder for Drupal
# GitHub Pull Request Builder for Drupal
Simplify testing with a dedicated QA site for every new feature â automatically!
By
[ Jerad Bitner ](/about/jerad-bitner)
July 17, 2013
It's no secret that at Lullabot, we love GitHub. We use it for as many projects as possible, and have found some great success with the tools it provides. They've helped us simplify development, code review, documentation, and even communication and transparency with our clients.
Our typical process for Drupal project begins with of a checkout of our [Drupal Boilerplate](https://github.com/Lullabot/drupal-boilerplate) (thanks Eric Duran!). It gives us a base directory structure to start from, some basic drush commands, and drush aliases to simplify deployment tasks. From there, we commit Drupal into docroot and start to build out the site as normal.
Next, we input the projects requirements into the [GitHub issue tracker](https://www.lullabot.com/articles/managing-projects-with-github). These take various forms for different clients, depending on whether we're starting from user stories, visual design assets, or actual written technical requirements. After we have a decent backlog of tickets, we prioritize them with the client and group them into milestones. Those milestones are typically set up as two week sprints, and each ticket will typically get its own branch of code.
When a ticket is ready for review, the issue can be turned into a pull request with a nice command line tool called [hub](https://github.com/mislav/hub). Pull requests are an effective means of performing peer review on code before merging into your stable branch. If one developer sends a pull request, another reviews the code before it's merged with the project's master branch.
While the peer review process is something we do for our own sanity, quality control, and knowledge sharing, it's rarely a process that clients can participate with. When the client is technically savvy and has time to work with us on that level it's great, but it's not something we can count on with every project.
A solution we've found to address this is to leverage the power of GitHub and to add some [Jenkins](http://jenkins-ci.org/) magic into the mix. By tying GitHub's webhooks into a Jenkins instance, we can turn the changes for each pull request into a fully testable Drupal environment. This allows the client or a reviewer to click around a fresh QA site, test the features that would be affected, and easily approve or deny those changes. They *don't* have to manually push code to a QA environment, or worry about stepping on other in-progress features in the process. The site they're testing is completely dedicated to the changes within that feature's branch, and it's extremely productive as a result.
If you'd like to skip ahead to the geeky details, dive right into the [GitHub repository](https://github.com/Lullabot/jenkins_github_drupal). Otherwise, you can stick around for an overview of how we did it.
The process goes something like this:
1. A new Pull Request is created. 
2. Jenkins detects the Pull Request, creates a new Drupal instance, and applies the Pull Request to the new instance.
3. Jenkins posts back to the Pull Request on GitHub with a comment about where the new environment can be found. 
4. Once the Pull Request is merged, you can tell Jenkins to delete the environment, and it can then post a comment to that effect. 
There are other commands you can access by posting to the pull request's comment, such as asking the bot to please rebuild (such as after a new commit), and it will also post to the thread if a build fails.
This process has really helped in our projects to streamline peer reviews. Here's what a client had to say about the process:
> "The pull request environments have been a huge help in testing and validating the features or bugs for our sites. We are able to isolate an issue and validate it on a functioning site before final testing and deployment. It has also made our development practices clear, since you know what you're committing code to and testing against." â Mike Shaver, Intel
Overall this tool really saves Lullabot a lot of time, which saves our clients money. We've [open-sourced the project on GitHub](https://github.com/Lullabot/jenkins_github_drupal) and would love to hear what you think of this. If you find it useful, but don't have the expertise to set it up, [give us a shout](https://www.lullabot.com/contact) and let's talk about how we can help you.
Published in:
- [ Deployment ](/topics/deployment)
- [ Drupal Development ](/topics/drupal-development)
- [ Technical Project Management ](/topics/project-management)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Learning From Distributed Companies"
url: "/articles/learning-from-distributed-companies"
type: article
date: 2014-04-10
updated: 2016-04-07
---
# Learning From Distributed Companies
# Learning From Distributed Companies
Lullabot's lessons from the Yonder conference
By
[ Liza Kindred ](/about/liza-kindred)
April 10, 2014
Once upon a time, back in 2005, Lullabot was just two guys collaborating across several time zones. In those days, Lullabot was predominantly a Drupal consultancy and Drupal was much, much smaller. The talent, like the open source project, was spread out around the globe. Weâve stayed that way ever since, even as weâve grown to become a nearly 60-person full-service digital agency. All of our employees at Lullabot work from home, or from a coffee shop, or a co-working space, or a library⦠We work from Copenhagen, Denmark; or Normal, Illinois; or Portland, Oregon. We work on the Internet, and the Internet doesnât care where you live.
Turns out a lot of people work from home these days. According to Forrester Research over 34 million Americans work from home at some point during the week. By 2016, that number is expected to reach 63 million. Thatâs 43% of the U.S. workforce⦠in two years. As the numbers grow, itâs clear more companies are embracing this work style. The data also shows that a lot of people are happier and more productive when they arenât required to go into an office.
But, despite all of that talk, all of the numbers and information, thereâs a gap. Where do business leaders who are running fully distributed companies go to find information and share advice? Much has been written of late, like the books [Remote](https://www.amazon.com/exec/obidos/ASIN/0804137501/orbit0b-20) and [The Year Without Pants](https://www.amazon.com/exec/obidos/ASIN/1118660633/orbit0b-20) which both debuted in 2013. But sometimes thereâs just no substitute for getting together face-to-face with your peers on a secluded island paradise off the coast of San Diego to talk it out, so we created [Yonder](http://yonder.io) â a two-day invite-only event for leaders of distributed companies to come together and meet their peers. In January, we gathered at the Loews Coronado Bay hotel (think lunch and meetings outside in January) for an unconference. We kept the event small so as to include everyone in discussions and benefit from everyoneâs knowledge.
We had a good variety of companies: large and small, product-oriented and services-oriented, b2b and b2c, and tech and non-tech companies â all focused on distributed staffing.
The group shared many of the difficulties and triumphs we had building our companies, managing our teams, communicating with staff and clients, and even things like scheduling meetings across time zones. Some of the primary topics:
- Synchronous vs. asynchronous communication, and the unique purposes of each.
- Modes of communication, including audio meetings, video meetings, in-person meetings, and when each are appropriate.
- How to run company retreats and build distributed company culture.
- The challenges of building legitimacy and dealing with legal and tax issues when there isnât a central office where the majority of the company works.
- How to use the wisdom of Open Source communities in building a company.
We also found ourselves often coming back to discussing the software tools that make our geographical distribution possible. Carl Smith, founder of [nGen Works](http://www.ngenworks.com) and an all-around cool guy, sees these new tools as a way to close any physical gap that we might feel. âI think in the future the tools are going to get to a place where we donât feel like weâre not right next to each other. We may be across the country or around the world from each other, but itâs going to feel like sitting at the same table. Thatâs my hope â that we can get to a point where everybodyâs distributed, but weâre all together.â
From the tools to the tactics, all of the different aspects of distributed work can be viewed as different or challenging, so we let the group decide what mattered most. With pages of ideas, we scored which discussions were top-of-mind and got to work. We walked away as better leaders, and weâre excited to share some of the ideas with you too. Here are some of the topic areas weâll cover over the next few months here on the blog:
- What is a distributed company?
- Finding & hiring managers of one
- Onboarding new employees in-person or not
- How to build culture in a distributed company
- What motivates the office-optional employee?
- Synchronous vs. asynchronous communication
- Org Structures: hierarchical, flat, or Starfish
- Communication tools & methods
Yonder was a great success, and everyone found the information to be extremely valuable. While weâre not exactly sure what the next Yonder looks like yet, weâve seen the power of gathering distributed team leaders. If youâre running a distributed team and are interested in staying in the loop with our plans for the next Yonder, you can sign up for [email updates](http://lullabot.list-manage.com/subscribe/post?u=579cc4bca784b8844042fea50&id=7a64ff23fe).
*Liz has been experimenting with various ways of work for over five years. From building startups and freelance gigs to climbing the corporate ladder, sheâs worked with different types of people in vastly different environmentsâfrom home offices and co-working spaces to cubicles, and most recently, a corner office. Itâs all for her relentless mission to find how work⦠well, works best. This mission has lead her to start [WorkingRemote.ly](#), a resource for business leaders and new era workers.*
Published in:
- [ Business ](/topics/business)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Testing Local Drupal Sites on Multiple Devices"
url: "/articles/testing-local-drupal-sites-on-multiple-devices"
type: article
date: 2013-07-10
updated: 2023-11-20
---
# Testing Local Drupal Sites on Multiple Devices
# Testing Local Drupal Sites on Multiple Devices
With a few tweaks, I can see my localhost on every device I use
By
[ Sean Lange ](/about/sean-lange)
July 10, 2013
Do you develop your websites on your local machine? Do you need to test those sites across multiple devices, and face hassles using them to access the locally-hosted site? Have you tried different methods, processes, workarounds, tutorials, and blog posts about how to connect to your local machine? If you are like me, then you answer all of these questions with a loud, "yes!" The solution is [xip.io](http://xip.io), a service so simple that I spent a long time trying to figure out exactly *what it does!*. It's a free site from 37Signals, the makers of Basecamp, that uses "wildcard" domain names to route requests to any computer on your network. After googling around to understand how xip.io actually works, I was able to configure my system to use it with complete success. My new workflow for testing is easy, fast, and flexible! Let me show you how I did it.
## My goal is to view my locally-hosted website on as many displays/devices as possible.
Before we get into the weeds. Let's take a look at where I was starting from.
- I used MAMP PRO to manage my sites.
- I used a wireless router within my house.
- I developed on a Mac, which gave me great access to Chrome, Safari, and Firefox. With a virtual box I could test Windows with IE7, 8, 9, and 10. It was slow, but serviceable most of the time.
- I had a Windows laptop connected to my local network. If I edited its [hosts file](https://en.wikipedia.org/wiki/Hosts_(file)), I could access the website being hosted on my Mac.
- I had an iPhone, iPad and an Android tablet -- and testing on *those* devices was not easy. I was constantly changing settings and configurations, altering settings in virtual hosts, using easyDNS, and cobbling together partial fixes to get a stable setup that worked for our testing process.
- I wanted *all* of my devices to agree that a particular domain name, like "http://www.my-client-web-site.dev", be served from my Mac.
If you have a similar setup and face similar problems, the steps I used to get xip.io working might be a good solution for you, too. If your setup is different, I hope it will make you curious enough to investigate alternatives and maybe even share your own development process in the comments!
## Making it happen
With xip.io, and a simple addition to my localhost configuration (an alias in MAMP Pro), all of my devices can access my local site from a custom URL. I can grab my iPhone, connect to my wireless network, and enter an address like 'ahoy.192.168.1.14.xip.io' into Safari. The request is routed to my Mac, then MAMP PRO matches the first part of the address (ahoy) to the site alias that I set up. Once it's setup any machine on my local network can use the same address to test the site! Here are the setup steps I followed to get the magic running.
### The site on my local Mac (http://ahoy.local):

### My MAMP Pro setup:

### The ip address for my local machine:

### An additional MAMP Pro alias for this site:
This maps my local ip address, and the xip.io naming convention.

That's it!
## The end result
I can now see my site (http://ahoy.192.168.1.2.xip.io) on all of my devices. By simply changing the prefix on #.#.#.#.xip.io to another site alias, I can use different names for different sites. Once it's set up, it just works!
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ Drupal Site Building ](/topics/drupal-site-building)
- [ Front-end Development ](/topics/frontend-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Legally Binding your Web APIs"
url: "/articles/legally-binding-your-web-apis"
type: article
date: 2014-04-15
updated: 2016-08-26
---
# Legally Binding your Web APIs
# Legally Binding your Web APIs
Spec-driven design of APIs can save you time and prevent confusion
By
[ Andrew Berry ](/about/andrew-berry)
April 15, 2014
As web architecture evolves towards building distributed, independent applications, and away from single-purpose omnibus websites, Drupal implementations are including services, APIs, and feeds almost by default. While [Services](https://drupal.org/project/services), [RestWS](https://drupal.org/project/restws), and [Drupal 8](https://drupalize.me/blog/introduction-restful-web-services-drupal-8) all simplify creating APIs to distribute content, it's important to step back and think about API design before writing a single line of code.
A quick aside: many prefer to refer to REST APIs as a subtype of the more general Hypermedia API, but for simplicity this article uses the better-known REST acronym.
## The Legacy of Common Law APIs
A stock Drupal 7 site contains a large number of HTTP calls that are tightly coupled to specific JavaScript or frontend implementations. Some examples of these include:
- Taxonomy autocomplete
- "Add another" for unlimited count fields
- \#ajax for dynamic form interactions
If your site uses Views, there is also pager and search functionality, along with administrative-only AJAX calls for building Views. This is only the tip of the iceberg for a complex Drupal site. It's not unheard-of for a production site to have dozens of these "internal" APIs.
The problem with these sorts of calls isn't that they exist, or that they are meant to be internal to Drupal only. The real issue is that they provide poor examples for new developers when designing modules or site-specific code. As we design and build sites incrementally, from basic Drupal themes with custom JavaScript, through to complete front-end applications using Angular or Backbone, we carry this legacy of coupled implementations with us.
How can we escape this pattern of fragile and one-off APIs? We must:
- Understand different API paradigms
- Design the Object Schema
- Document and Communicate
## Civil Law: Define an API paradigm
Ask three developers to design a REST API, and you'll end up with three totally different designs. In practice, it's common for developers to think of REST as meaning "not SOAP" or "over HTTP". Simply choosing to use URLs and JSON objects doesn't make a RESTful API. There are [many](http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http) [great](https://blog.apigee.com/detail/restful_api_design_nouns_are_good_verbs_are_bad) articles about [designing RESTful APIs](https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm), but REST itself might not be the best fit for your application. The three common approaches to an HTTP API that I've seen are:
### API as Feeds
This paradigm is common in Drupal implementations due to the simplicity of exporting JSON or RSS from Views. I'd go so far as to say that a feed, even in JSON, is not really an "API" but more of a raw data source. You can identify a Feeds-style API if:
- Your API entry mechanism returns a list of recent content with basic filters.
- You have a small number of entry points with query parameters to extract subsets of data. For example if to fetch articles you query `/content?type=article` you might have a Feeds API.
- The primary focus of your API is lists of data and not individual instances of the data itself.
### API as Remote Procedure Calls (RPC)
This paradigm is easy to identify. Almost always, URLs contain the equivalent of method names instead of using HTTP semantics. For example:
- `/api/getUsers` would map to a user load or search method.
- If your API has `/api/user/{id}` as a valid URL, if modifying a user was executed with a POST call to `/api/user/{id}/update`, the API is still an RPC API.
- If the API has a small number "endpoints" (such as with SOAP), it's likely an RPC API. These sorts of APIs would typically have a large number of operations tied to each URL that accepts a POST request, with operational information included in HTTP headers or in the POST body itself.
### API as REST
A REST API tries to exploit the functionality and semantics already defined in HTTP as much as possible. A key distinction with a REST API is the concept of a Resource, which roughly maps to an object instance. Every resource has a unique identifier in the form of it's URL. While we often include numeric IDs for our own sanity, there's nothing that prevents the unique identifier from being a human-readable string. What's important is that the URL is always unique per resource.
- If a GET on `http://example.com/organization/lullabot` returns the Lullabot organization, it is likely that the API is RESTful.
- Likewise, if a POST on the same URL is used to update the Lullabot organization, it's likely that the API is RESTful.
- The API is traversable and discoverable through the API itself when combined with basic HTTP methods.
Many web APIs end up with a mixture of all three paradigms, as functionality is added and modified over time. While developers often prefer REST APIs, what is the most important is that the API is designed and kept to a single paradigm, regardless of what that is.
## Setting Jurisdiction: Defining the Object Schema and it's boundaries
Exposing data as JSON and calling it a day doesn't define an API. While message formats (like JSON, XML, and HTML) are important, even more important is the actual structure of objects returned by the API. Where possible, object keys should be common across objects if they have the same meaning. For example, instead of having `videoTitle` and `articleTitle` properties on videos and articles, combine them into a single title property.
Data formats within objects should be defined and controlled as well. A common mistake is to mix date formats between Unix timestamps and ISO dates, or to expose numbers and booleans as strings instead of their raw type. This will not only help to make your API consistent, but will also ensure that it's discoverable and intuitive to API consumers.
Finally, where possible responses should use references instead of composition. Many APIs will embed related objects within a single response to try to reduce the number of HTTP requests. With complicated content models, it's common to end up with circular references between related content. Splitting the objects into separate resources allows clients to decide how deep they want to traverse the object graph. Also, smaller objects allows faster returns to the API client, which in turn unblock the client and gives it the flexibility to run subsequent requests in parallel if it actually needs the data.
For example, when returning an article, don't include an array of every contributor:
```
{
"type": "article",
"title": "Legally Binding your Web APIs",
"contributors": [
{
"name": "Andrew Berry",
"email": "nobody@example.com"
},
{
"name": "Juan Pablo Novillo Requena",
"email": "nobody@example.ca"
]
}
```
Instead return a reference to the author resource:
```
{
"type": "article",
"title": "Legally Binding your Web APIs",
"contributors": [
"http://example.com/authors/aberry",
"http://example.ca/authors/juampynr",
]
}
```
What about the per-HTTP-request performance hit? It does depend on the scope of the data being returned, but composition might be reasonable if:
- The data is a bounded list, such as a single author instead of a list of authors.
- If the additional data is small, both in the number of properties and the data stored in each property.
- Composition is broadly beneficial to every API consumer's performance.
However, don't let this imply that the public API should necessarily change. Instead, aim to let it be the responsibility of the client to intelligently cache resources or to add their own implementation-specific proxy. This allows the client to aggregate and modify responses tailored exactly to their use case, freeing your application from having to understand the implementation details of API consumers.
## Codification and Communication
Without documentation, an API might as well not exist. Anyone who has done ecommerce work or who has worked with proprietary APIs is familiar with the 10MB PDFs or Word documents that typically accompany them. It's this documentation, that describes both the rationale and the implementation of the API that will determine how successful an API is. For web APIs in particular, it makes the most sense for documentation to live on the web where it can be referenced, stubbed, and tested. Some documentation tools I've used in the past include:
- [Apiary](https://apiary.io/): A wonderful service for documenting with Markdown, stubbing with JSON, and creating quick API demos.
- [Swagger](https://github.com/swagger-api/swagger-core): A tool that can be used to generate and self-host your documentation.
- [JSON Schema and Hyper-Schema](https://json-schema.org/): A specification for describing the object schema of JSON returns from APIs.
When building your own clients against your own API, it's critical that you use your own documentation as the reference for your implementation. This ensures that your API client isn't a privileged application, and that any other client has access to the same functionality. It's also a great way to do a pass of QA before releasing your API and documentation to the public.
## Next Steps: Amendments and Iterations
While we should take great care as API designers to limit API breaks for arbitrary reasons, it's entirely OK to modify and iterate on both the basic assumptions of your API as well as the actual implementation. It's important to give yourself the flexibility to amend and improve your API. After all, best practices are still in such flux that it's unlikely that everything recommended today will stick. Where possible, try to amend your existing API without breaking paradigms or object schemas. If you find that your application or best practices dictate changing those assumptions, consider writing a new, separate API to support in parallel.
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ Mobile ](/topics/mobile)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "A Beginner's Guide to Caching Data in Drupal 6"
url: "/articles/a-beginners-guide-to-caching-data-in-drupal-6"
type: article
date: 2011-07-14
updated: 2014-05-15
---
# A Beginner's Guide to Caching Data in Drupal 6
# A Beginner's Guide to Caching Data in Drupal 6
By
[ Jeff Eaton ](/about/jeff-eaton)
July 14, 2011
Building complicated, dynamic content in Drupal is easy, but it can come at a price. A lot of the stuff that makes a Web 2.0 site so cool can spell 'performance nightmare' under heavy load, thrashing the database to perform complex queries and expensive calculations every time a user looks at a node or loads a particular page.
One solution is to turn on page caching on Drupal's performance options administration page. That speeds things up for anonymous users by caching the output of each page, greatly reducing the number of DB queries needed when they hit the site. That doesn't help with logged in users, however: because page level caching is an all-or-nothing affair, it only works for the standardized, always-the-same view that anonymous users see when they arrive.
Eventually there comes a time when you have to dig in to your code, identify the database access hot spots, and add caching yourself. Fortunately, Drupal's built-in caching APIs and some simple guidelines can make that task easy.
### The basics
The first rule of optimization and caching is this: never do something time consuming twice if you can hold onto the results and re-use them. Let's look at a simple example of that principle in action:
```php
function my_module_function($reset = FALSE) {
static $my_data;
if (!isset($my_data) || $reset) {
// Do your expensive calculations here, and populate $my_data
// with the correct stuff..
}
return $my_data;
}
```
The important part to look at in this function is the static variable named $my\_data. Static variables start out empty the first time a function is called, but they keep the data they're populated with even when the function is called again. That means that we can check if the variable is already populated, and if so return it immediately without doing any more work.
This pattern appears all over the place in Drupal -- including key functions like node\_load(). Calling node\_load() for a particular node ID requires database hits the first time, but the resulting information is kept in a static variable for the duration of the page load. That way, displaying a node once in a list, a second time in a block, and a third time in a list of related links (for example) doesn't require three full trips to the database.
Another important feature is the use of the $reset variable. Caching is good, but occasionally you want to be sure you're getting the absolute freshest data available. Using a 'reset' variable in your function, and always performing the 'expensive' version of the function if it's set to TRUE, lets you bypass caching when you really need to.
### Drupal's cache functions
You might notice that the static variable technique only stores data for the duration of a single page load. For even better performance, it's often possible to cache data in a more permanent fashion...
```php
function my_module_function($reset = FALSE) {
static $my_data;
if (!isset($my_data) || $reset) {
if (!$reset && ($cache = cache_get('my_module_data'))) {
$my_data = $cache->data;
}
else {
// Do your expensive calculations here, and populate $my_data
// with the correct stuff..
cache_set('my_module_data', $my_data, 'cache');
}
}
return $my_data;
}
```
This version of the function still uses the static variable, but it adds another layer: database caching. Drupal's APIs provide three key functions you'll need to be familiar with: [cache\_get()](http://api.drupal.org/cache_get), [cache\_set()](http://api.drupal.org/cache_set), and [cache\_clear\_all()](http://api.drupal.org/cache_clear_all). Let's look at how they're used.
After the initial check of the static variable, this function checks Drupal's cache for data stored with a particular key. If it finds it, $my\_data is set to $cache->data and we're done. Combined with the static variable, future calls during this page request won't even need to call cache\_get()!
If no cached version is found (or if we called the function using the $reset parameter), the function does the actual work of generating the data. Then it saves it TO the cache so future requests will find it. The key that you pass in as the first parameter can by anything you choose, though it's important to avoid colliding with any other modules' keys. Starting the key with the name of your module is always a good idea.
The end result? A slick little function that saves time whenever it can -- first checking for an in-memory copy of the data, then checking the cache, and finally calculating it from scratch if necessary. You'll see this pattern a lot if you dig into the guts of data-intensive Drupal modules.
### Keeping up to date
What happens, though, if the data that you've cached becomes outdated and needs to be recalculated? By default, cached information stays around until some module explicitly calls the cache\_clear\_all() function, emptying out your record. If your data is updated sporadically, you might consider simply calling cache\_clear\_all('my\_module\_data', 'cache') each time you save the changes to it. If you're caching quite a few pieces of data (perhaps versions of a particular block for each role on the site), there's a third 'wildcard' parameter:
<?php cache\_clear\_all('my\_module', 'cache', TRUE); ?>
This clears out all the cache values whose keys start with 'my\_module'.
If you don't need your cached data to be perfectly up-to-the-second, but you want to keep it reasonably fresh, you can also pass in an expiration date to the cache\_set() function. For example:
<?php cache\_set('my\_module\_data', $my\_data, 'cache', time() + 360); ?>
The final parameter is a unix timestamp value representing the 'expiration date' of the cache data. The easiest way to calculate it is to use the time() function, and add the data's desired lifetime in seconds. Expired entries will be automatically discarded as they pass that date.
### Controlling where cached data is stored
You might have noticed that cache\_set()'s third parameter is 'cache' -- the name of the table that stores the default cache data. If you're storing large amounts of data in the cache, you can set up your own dedicated cache table and pass its name into the function. That will help keep your cache lookups speedy no matter what other modules are sticking into their own tables. The Views module uses that technique to maintain full control over when its cache data is cleared.
The easiest place to set up a custom cache table is in your module's install file, in the `hook_schema()` function. It's where all of the custom tables used by your module are defined, and you can even make use of one of Drupal's internal helper functions to simplify the process.
```php
function mymodule_schema() {
$schema['cache_mymodule'] = drupal_get_schema_unprocessed('system', 'cache');
return $schema;
}
```
Using the `drupal_get_schema_unprocessed()` function, the code above retrieves the definition of the System module's standard Cache table, and creates a clone of it named 'cache\_mymodule'. Prefixing the name of custom cache tables with the word 'cache' is common practice in Drupal, and helps keep the assorted cache tables organized.
If you're really hoping to squeeze the most out of your server, Drupal also supports the use of alternative caching systems. By changing a single line in your site's settings.php file, you can point it to different implementations of the standard cache\_set(), cache\_get(), and cache\_clear\_all() functions. The most popular integration is with the open source [memcached](http://drupal.org/project/memcache) project, but other approaches are possible (such as a file-based cache or against PHP's APC). As long as you've used the standard Drupal caching functions, your module's code won't have to be altered.
### A few caveats
Like all good things, it's possible to overdo it with caching. Sometimes, it just doesn't make sense -- if you're looking up a single record from a table, saving the result to a database cache is silly. Using the [Devel](http://drupal.org/project/devel) module is a good way to spot the functions where caching will pay off: it can log the queries that are used on your site and highlight the ones that are slow, or the ones that are repeated numerous times on each page.
Other times, the data you're using will just be a bad fit for the standard caching system. If you need to join cached data in SQL queries, for example, cache\_set()'s practice of string data as a serialized string will be a problem. In those cases, you'll need to come up with a solution that's specific to your module. VotingAPI maintains one table full of individual votes and another table full of calculated results (averages, sums, etc.) for quick joining when sorting and filtering nodes.
Finally, it's important to remember that the cache is not long term storage! Since other modules can call cache\_clear\_all() and wipe it out, you should never put something into it if you can't recalculate it again using the original source data.
### Go west, young Drupaler!
Congratulations: you now have a powerful set of tools to speed up your code! Go forth, and optimize.
*Note: This article has been updated from its original content (Drupal 4.7 and 5) to work with the Drupal 6 API. If writing against older versions of Drupal, [see the previous article](https://www.lullabot.com/articles/a-beginners-guide-to-caching-data).*
Published in:
- [ Performance and Scalability ](/topics/performance-and-scalability)
- [ System Administration ](/topics/system-administration)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "What are Drupal Entities?"
url: "/articles/what-are-drupal-entities"
type: article
date: 2013-04-19
updated: 2014-05-15
---
# What are Drupal Entities?
# What are Drupal Entities?
By
[ Addison Berry ](/about/addison-berry)
April 19, 2013
We've launched a new series, [Working with Entities in Drupal 7](https://drupalize.me/course/working-entities-drupal-7), which takes a deep dive into the Entity API, and shows you how to work with existing entities, as well as creating your own, new custom entity. To kick things off, we have a free video to give a nice overview of what Drupal entities are, and the various pieces associated with them.
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "What is the Content Construction Kit? A View from the Database."
url: "/articles/what-is-the-content-construction-kit-a-view-from-the-database"
type: article
date: 2007-03-07
updated: 2016-04-07
---
# What is the Content Construction Kit? A View from the Database.
# What is the Content Construction Kit? A View from the Database.
Drupal CCK
By
[ Robert Douglass ](/about/robert-douglass)
March 7, 2007
This article describes the [Content Construction Kit](http://drupal.org/project/cck), version [5.x-1.4](http://drupal.org/node/125060).
The Content Construction Kit (CCK) began as a natural evolution from the popular [Flexinode module](http://drupal.org/project/flexinode). The Flexinode module allowed you to define your own content types (a blog entry, a recipe, a poll, etc) with a number of custom fields. CCK also allows you to do this, but in a more powerful way.
## Content types and the content.module
With Drupal 5, you can create your own content types. The default installation comes with Page and Story content types, which are included for historical reasons. You can delete these and create your own content types, or modify them to suit your needs.
CCK allows you to extend the data model of content types through the addition of fields such as a date, an image, an email address, etc. The core CCK module is the content.module. The content.module is the workhorse that handles CCK's main goal of extending content types with these new fields. Therefore, it is logical that the content.module manages its own database table for every content type you have defined. This includes the built in Page and Story types.
When you install and enable content.module, it creates tables for every content type you currently have. Here is the schema for the table it creates if your Drupal installation has a Page content type:
```
mysql> describe content_type_page;
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| vid | int(10) unsigned | NO | PRI | 0 | |
| nid | int(10) unsigned | NO | | 0 | |
+-------+------------------+------+-----+---------+-------+
```
*vid and nid are the bare minimum fields needed to extend a content type.*
As you can see, the content\_type\_page table is an empty shell at this point, only having columns for vid (revision id) and nid (node id).
The content.module manages a great deal of data about the various fields you will use to extend your content types. I will show later that fields exist at two levels; the *global* level, which affects a field no matter which content type it extends, and the content-type-specific level, or *field instance* level, where a field can be customized to behave in a specific way for a specific content type. This dichotomy can be seen in the two administrative tables that the content.module creates, node\_field, and node\_field\_instance:
```
mysql> describe node_field;
+-----------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+-------+
| field_name | varchar(32) | NO | PRI | | |
| type | varchar(127) | NO | | | |
| global_settings | mediumtext | NO | | | |
| required | int(11) | NO | | 0 | |
| multiple | int(11) | NO | | 0 | |
| db_storage | int(11) | NO | | 0 | |
+-----------------+--------------+------+-----+---------+-------+
```
*Note the global\_settings field, as well as details about the database storage mechanism; these are details that are stored at the global level.*
```
mysql> describe node_field_instance;
+------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+-------+
| field_name | varchar(32) | NO | PRI | | |
| type_name | varchar(32) | NO | PRI | | |
| weight | int(11) | NO | | 0 | |
| label | varchar(255) | NO | | | |
| widget_type | varchar(32) | NO | | | |
| widget_settings | mediumtext | NO | | | |
| display_settings | mediumtext | NO | | | |
| description | mediumtext | NO | | | |
+------------------+--------------+------+-----+---------+-------+
```
*Note that most of the information about how a field is displayed, i.e. weight, label, description, widget and display settings, are all stored at the instance level of a field.*
## Creating a new content type
To create a new content type, navigate to Administer -> Content management -> Content types -> Add content type (admin/content/types/add). You'll be required to give your new content type a human readable name and a machine readable name. There are other configuration options as well, but since creating and configuring new content types is part of Drupal 5 core, and not a feature of CCK, I won't cover that here.
For this article, I've created a new content type with the human readable name "Test content type" and the machine readable name "test". Here is the table that the content.module created on-the-fly, which is used to extend the "Test content type":
```
mysql> describe content_type_test;
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| vid | int(10) unsigned | NO | PRI | 0 | |
| nid | int(10) unsigned | NO | | 0 | |
+-------+------------------+------+-----+---------+-------+
```
*The structure of a CCK content table before fields have been added.*
The fact CCK creates these tables for you is significant. The Flexinode approach was to save all of the information needed to extend content types in a few central tables, no matter how many flexinode types were created, or how many fields were added. These central tables became a bottleneck due to excessive JOIN queries being made. The CCK approach of creating new tables for the purpose scales much better.
## Fields - an overview
Fields are the tools with which you extend the data model of a content type. A field comes in three parts; its underlying data type, its input widget, and its rendered output. These three parts are referred to as the field, the widget, and the formatter.
A good example of all these elements coming together is the date field. A date can have different underlying data structures (thus the [choice between date and datestamp](http://drupal.org/node/92460)).
There are also many ways that a date can be input in a browser. A simple textfield is enough, if you can justify having your end users type in ISO standard date strings. A more comfortable solution is a separate input element, either text field or select list, for the various units of the date/time (year, month, day, hour, minute second). These are two different widgets; a textfield versus a number of select lists. Another possible widget is a JavaScript driven date picker. No matter which widget is used, the underlying data that gets stored in the database will be the same.

Finally, there are many options when it comes to displaying and formatting the date. This is the realm of formatters. Formatters will not be covered in this article, although they are similar to a theme function in that they are concerned with the rendered output of a field.
This separation of concerns within a field is diagrammed below:

## Fields - adding new fields
So far I have enabled the content.module and created a new content type. If I try to add a field at Administer -> Content management -> Content types -> Add field I receive the following error message.
No field modules are enabled. You need to enable one, such as text.module, before you can add new fields.
This is because I have not enabled any modules which provide fields. One of reasons Flexinode became so popular was because of the relative simplicity with which people could add new field types. CCK is extensible in much the same way that Flexinode is, and it comes with three modules, the text, number and date modules, which define fields. In another article, I intend to show that creating your own custom fields is a straightforward process, granted you have an overview of what CCK is and how it does its business.
Go to Administer -> Site building -> Modules and enable text.module. The text module manages text-based fields.
To add a new field to an existing content type, go to Administer -> Content management -> Content types -> Add field (admin/content/types/test/add\_field).

As soon as you create a new text field, CCK updates the underlying table in your database for that content type. Now look at the database schema for content\_type\_test:
```
mysql> describe content_type_test;
+--------------------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------------------+------------------+------+-----+---------+-------+
| vid | int(10) unsigned | NO | PRI | 0 | |
| nid | int(10) unsigned | NO | | 0 | |
| field_example_text_value | longtext | YES | | NULL | |
+--------------------------+------------------+------+-----+---------+-------+
```
*field\_example\_text\_value shows up in this table because I added a text field named "Example text".*
## Fields - global versus instance
Fields store global data plus per-instance data. The global configuration for the field goes into the node\_field table. This includes the underlying data type, plus some data handling information specific to text fields, such as the input filter that should be used.
mysql> select \* from node\_field;Column nameValuefield\_namefield\_example\_texttypetextglobal\_settings`a:4:{ s:15:"text_processing";s:1:"1"; s:10:"max_length";s:0:""; s:14:"allowed_values";s:0:""; s:18:"allowed_values_php";s:0:""; } `
required1multiple0db\_storage1*The global\_settings column contains configuration data such as whether filtering is supposed to take place, what the maximum length is, and what the allowed values are. The data is in serialized form.*
The node\_field\_instances table contains configuration information for the field that is specific to the the "test" content type. The fact that the "test" content type uses the "Example text" field is what is considered an instance of a field. Later I'll show that other content types can also use the same field. Each content type that decides to use it creates another instance of it, and each instance can, in turn, have different configurations. The data included at the per-instance level and stored in the node\_field\_instances table includes the label to be shown on the form, the widget that is to be used (textfield), and the number of rows that the form element should have.
mysql> select \* from node\_field\_instance;Column nameValuefield\_namefield\_example\_texttype\_nametestweight0labelExample textwidget\_typetextwidget\_settings
```
a:3:{
s:13:"default_value";
a:1:{
i:0;
a:1:{
s:5:"value";
s:0:"";
}
}
s:17:"default_value_php";s:0:"";
s:4:"rows";s:1:"1";
}
```
display\_settings`a:0:{}`descriptionThis is an example text field for a Lullabot article.*The per-instance settings include information about the widget that is to be used for capturing input (widget\_type: text), the label for the input element ("Example text"), and the description ("This is an example text field for...")*
## Creating content
Now, with our extended content type, we can create some content. The data that is common to all nodes (author, published, created...) will be stored in the node and node\_revisions tables, but the data that extends this content type will be stored in the content\_type\_test table. Here is what the content\_type\_test table looks like after creating a first "Test content type" node:
mysql> select \* from content\_type\_test;Column nameValuevid1nid1field\_example\_text\_value`Here is some example text!
Forbidden HTML will be stripped.
`field\_example\_text\_format1
## Adding a second field
Now I'll add a second field to the Test content type, extending it even further. This time I'll add an Integer. The Integer field comes from the number.module (contained in the CCK download), so the first step is to enable that module. The number module defines two new data types, Integer, and Decimal. Each of them rely on the textfield widget by default. This clearly shows the independence of data types and widgets in the CCK architecture.

The configuration options for Integer and Text data are different. The reason that each needs different configuration is made clear when considering the validation requirements. An Integer is a much narrower set of values than Text, and to guarantee that only integers get stored in the database, the number module needs to do some extra work when accepting user input. Furthermore, there are possibilities for integers (such as minimum or maximum values) that don't make sense when considering free text. [Karen Stevenson](https://www.lullabot.com/audiocast/lullabot_podcast_no_30_karen_stevenson) [describes](http://groups.drupal.org/node/2720) how the validation of user input is divided between the widget and the underlying data type:
> In the current CCK model there are two layers of validation. The widgets provide their own input validation that is naive to the requirements of the data layer. The Data layer then validates what the widget produced as final output.

As you may have guessed, adding a second field to the Test content type results in further expansion of the content\_type\_test table. It is interesting to note that the storage requirements of each field are not the same. Text fields need the value and the format (for filtering), whereas our integer field only needs the data itself.
```
mysql> describe content_type_test;
+-------------------------------------+------------------+------+-----+
| Field | Type | Null | Key |
+-------------------------------------+------------------+------+-----+
| vid | int(10) unsigned | NO | PRI |
| nid | int(10) unsigned | NO | |
| field_example_text_value | longtext | YES | |
| field_example_text_format | int(10) unsigned | NO | |
| field_number_of_toes_you_have_value | int(11) | YES | |
+-------------------------------------+------------------+------+-----+
```
*field\_number\_of\_toes\_you\_have\_value has been added to the content\_type\_test table by CCK because I added an Integer field named "Number of toes you have".*
## Multiple values
So far I've only demonstrated fields that have a single value per node. I've shown that such fields have their data stored directly in a table (content\_type\_test in the example) that is used to extend a content type. This paradigm changes, however, when a field is marked as "multiple".

Once multiple values are enabled, many copies of the widget show up on the node form. If you fill them all up, submit, and then edit the node again, you will be provided with even more widgets for your use. The number of copies of the widget per node is not limited, so you could repeat the process indefinitely. This workflow is still a bit clumsy in CCK, but the groundwork has been laid for a very powerful system of managing one-to-many relationships between nodes and fields.


When a field can have multiple copies, it no longer has a one-to-one relationship with the node. Rather, it has a one-to-many relationship, and this must be mirrored at the database level. Let's see what happens when I go back to the configuration of the text field and specify that multiple values are supported.
```
mysql> describe content_type_test;
+-------------------------------------+------------------+------+-----+
| Field | Type | Null | Key |
+-------------------------------------+------------------+------+-----+
| vid | int(10) unsigned | NO | PRI |
| nid | int(10) unsigned | NO | |
| field_number_of_toes_you_have_value | int(11) | YES | |
+-------------------------------------+------------------+------+-----+
```
*content\_type\_test has again been modified by CCK: this time fields have been removed.*
Where did the field\_example\_text\_value and field\_example\_text\_format columns go? They're now in their own table:
```
mysql> describe content_field_example_text;
+---------------------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------------------+------------------+------+-----+---------+-------+
| vid | int(10) unsigned | NO | PRI | 0 | |
| delta | int(10) unsigned | NO | PRI | 0 | |
| nid | int(10) unsigned | NO | | 0 | |
| field_example_text_value | longtext | YES | | NULL | |
| field_example_text_format | int(10) unsigned | NO | | 0 | |
+---------------------------+------------------+------+-----+---------+-------+
```
*In order to handle a one-to-many relationship between a content type and a "Multiple" field, CCK creates a new table for the field.*
mysql> select \* from content\_field\_example\_text;vid delta nid field\_example\_text\_value field\_example\_text\_format 101`Here is some example text!
Forbidden HTML will be stripped.
`1 111`I can use a different input format for each text field!`3 121Even more example text.1 *The data storage of a "Multiple" text field.*
## Semantic meaning and sharing fields between content types
One of the primary goals of CCK is that the fields have semantic meaning. What does this mean? It means that a field called "Age", while being in principle identical in nature to a field called "Number of toes you have", is intended to convey a different meaning. Both fields store data as an integer. Both should be configured to only allow positive numbers. Age, however, should always be *understood* to refer to the length of time something has existed and the number of toes you have, while still a number, should be *understood* to have a totally different meaning.
Furthermore, it is often the case that a particular field with a particular semantic meaning needs to be used for more than one content type. For example, a content type called "Person" may have an Age field, and a content type called "Animal" may also have an Age field. Semantically, these fields should have the same meaning. CCK solves this problem by letting you use existing fields with any number of content types.

As soon as a field is used in more than one content type, its values are no longer stored in the content-type-specific tables. A new table is created to store the values for that field across content types. This is the table that got created when I added the "Number of toes you have" field to a second content type:
```
mysql> describe content_field_number_of_toes_you_have;
+-------------------------------------+------------------+------+-----+
| Field | Type | Null | Key |
+-------------------------------------+------------------+------+-----+
| vid | int(10) unsigned | NO | PRI |
| nid | int(10) unsigned | NO | |
| field_number_of_toes_you_have_value | int(11) | YES | |
+-------------------------------------+------------------+------+-----+
```
*The table structure for an Integer field named "Number of toes you have". This field is being used by more than one content type, which is why CCK uses a dedicated table to store its data.*
This information will no longer be stored in the content\_type\_test table. In fact that table has now been reduced back to its original state:
```
mysql> describe content_type_test;
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| vid | int(10) unsigned | NO | PRI | 0 | |
| nid | int(10) unsigned | NO | | 0 | |
+-------+------------------+------+-----+---------+-------+
```
*content\_type\_test has been stripped of its field columns altogether.*
## Summary
The Content Construction Kit is a carefully crafted tool that allows you to extend the data model for content types. The storage, retrieval and presentation of data is divided into the following parts:
- data: fields
- input: widgets
- output: formatters
Fields can have single or multiple values per node. This distinction also influences how the data is stored in the database - whether it is stored directly in a table for a content type when the relationship is one-to-one, or whether it is stored in a field-specific table that allows the one-to-many relationship to be modeled.
Fields have semantic meaning that is retained across content types. When you add a field to a second content type, the storage of that field will switch from being within the table for the original content type to storage in a field-specific table.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Take control of your Drupal theme"
url: "/articles/take-control-of-your-drupal-theme"
type: article
date: 2006-02-26
updated: 2014-05-15
---
# Take control of your Drupal theme
# Take control of your Drupal theme
Creating a front page that's styled differently from the rest of your site.
By
[ Matt Westgate ](/about/matt-westgate)
February 26, 2006
Want to create a front page that's styled differently from the rest of your site? Perhaps you need a separate admin theme? Or how about a login page which only shows the login block and nothing else? With a little PHP knowledge these problems are easy to solve. Note: You must be using the [PHPTemplate](http://drupal.org/phptemplate) theme engine for your theme. An easy way to determine this is by looking for files ending in **.tpl.php** within your site's theme folder. PHPTemplate is compatible with Drupal 4.6 and up. As a matter of fact, it's the default [theme engine](http://drupal.org/node/11774) for Drupal 4.7 since it combines the best of both worlds for designers and programmers. Designers get an easy way to manipulate HTML lightly sprinkled with PHP variables for dynamic content, and developers get a fast rendering template system that's a snap to extend. Here's what a template looks like using PHPTemplate.
```html
»
```
Developers can override any of the above PHP variables or even add new ones to pass to the template for the designer to use. What most folks don't know is that for every template section, PHPTemplate looks for a special variable named `template_file` which stores the name of the file to execute. This is your key to conditionally loading different theme files.
## Working with template\_file
Intercepting PHPTemplates' default variables is accomplished through creating a new function named `_phptemplate_variables()` within your theme. Navigate to your site's current theme folder and look for a file called **template.php**. If that file doesn't exist, create it. Now add the following function:
```php
/**
* Intercept template variables
*
* @param $hook
* The name of the theme function being executed
* @param $vars
* A sequential array of variables passed to the theme function.
*/
function _phptemplate_variables($hook, $vars = array()) {
switch ($hook) {
// more code here...
}
return $vars;
}
```
This function is called by Drupal just after a template engine call is made. Examples of this include: loading the node template, the block template or what we want, the page template. The `$hook` parameter above is the name of the template *section* the system is calling (node, block, page, etc). First the system assigns a bunch of default variables. In the case of page hook: $sidebar\_left, $sidebar\_right, $footer\_message, $search\_box, $title, $content, etc. If `_phptemplate_variables()` exists, any values set there will override the defaults, including your opportunity to change the name of the template file the system should be looking for. Let's take a look at our first example.
## Separate Administration Theme
The admin area is known for its wide tables which can be difficult to style when they're found nowhere else on your main site. Many folks find themselves wishing for a separate theme. Here's how to do it.
```php
function _phptemplate_variables($hook, $vars = array()) {
switch ($hook) {
case 'page':
if ((arg(0) == 'admin')) {
$vars['template_file'] = 'page-admin';
}
break;
}
return $vars;
}
```
Here we're changing value of `$vars['template_file']` from page.tpl.php to page-admin.tpl.php. Let's build our new admin theme. You probably want to use the Blue Marine layout as the admin theme. If your already using Blue Marine for the rest of your site that's okay as you'll get that basic idea. Grab a copy of **page.tpl.php** from the Blue Marine theme and paste it into your theme folder and rename the file to **page-admin.tpl.php**. Next, we want this file to use a separate stylesheet, so copy the Blue Marine **style.css** file and rename it to **admin-style.css**. Finally edit **page-admin.tpl.php** so it knows about the new CSS file.
```php
```
Now navigate to your admin section and behold the marvels of PHPTemplate.
## Custom Front Page
Here's how we do the special front page on Lullabot.com.
```php
function _phptemplate_variables($hook, $vars = array()) {
switch ($hook) {
case 'page':
global $user;
if ($vars['is_front']) {
$vars['template_file'] = 'page-index';
}
break;
}
return $vars;
}
```
Create a file called **page-index.tpl.php** and start styling away.
## Stand-alone Login Page
Sometimes you want the login/register page to stand alone, with no other action for a user to take.
```php
function _phptemplate_variables($hook, $vars = array()) {
switch ($hook) {
case 'page':
global $user;
if (arg(0) == 'user'){
if ($user->uid == 0) {
$vars['template_file'] = 'page-login';
}
elseif (arg(1) == 'login' || arg(1) == 'register' || arg(1) == 'password' ) {
$vars['template_file'] = 'page-login';
}
}
break;
}
return $vars;
}
```
Create a **page-login.tpl.php** such as the following
```php
>
```
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal Input Formats and Filters"
url: "/articles/drupal-input-formats-and-filters"
type: article
date: 2007-04-09
updated: 2023-10-26
---
# Drupal Input Formats and Filters
# Drupal Input Formats and Filters
Drupal text formats
By
[ Lullabot ](/about/lullabot)
April 9, 2007
This article applies to Drupal 5.x.
Processing textual content for output in a browser is one of Drupal's most critical tasks. Without such processing we would all have to become masters at typing in HTML text! In this article I will explain what filters and input formats are, why they are important, how they are used, and why they impact the security of your site.
## Filters and Input Formats
The pillars of Drupal's text handling are filters and input formats. A **filter** is a set of rules that can be applied to transform text in some way. Some filters strip certain HTML tags or security hazards from text. Other filters look for special patterns and expand the text in a meaningful way. Other fun-oriented filters, such as the [Pirate Filter](http://drupal.org/project/pirate), rewrite the text altogether (in this case, to make it "talk like a pirate"). Filters know how to do one thing, and do it well; text in, filtered text out.
Some filters have extra configuration options. The HTML filter, for example, strips all but an allowed set of HTML tags from text. The set of allowed tags can be determined by the administrator.
An **input format** is an ordered collection of filters. Any text that is being displayed to the browser should be run through the filters in an input format first. The input format then applies all of the filters, in the right order, so that one filter feeds its output to the next, forming a chain. This chaining of filters can be the source of great flexibility as well as great confusion. The flexibility comes from the fact that filters can be made to work together, the confusion comes from the case where filters inadvertently work against each other, one filter undoing the work of the previous filter. I'll show examples of both.
## Input versus Output
Drupal captures input in its raw form, saving whatever gets submitted straight to the database without alteration. Then, before displaying any such content in the browser, Drupal processes the text by choosing an input format to apply. Why doesn't Drupal apply the filters in an input format before saving input into the database? The answer is simple; flexibility. If you were to change the text that a user has input before saving it in the database, you could never get back to the original state. You could never change your mind about the configuration of the filters. By filtering on output, not on input, Drupal gives the site administrator the option of changing how content is displayed at any time. As an example, imagine that you notice the users on your site using character patterns to represent smiley faces. I know, that stuff is so 1998 :P But just for fun, let's say they're doing it ;-) You look around and find the [Smiley Filter](http://drupal.org/project/smileys) on Drupal.org, and install it. Now all of the keystroke patterns that your users had been using can be displayed as images
This ability to change is *only* available if the input is saved verbatim and filtering is done on output.
## Meet Drupal's Core Filters
Here is a rundown of the filters that Drupal ships with:
- **HTML Filter:** The HTML filter is primarily responsible for removing HTML tags from text. It can be configured to allow any number of tags (whitelist) and it will remove the rest. It removes them either by stripping them, or by escaping them into entities like this: **<div>** If tags are escaped, they show up in the output as visible tags: **<div>Some text</div>**. The set of tags that are allowed by default include: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
The final task of the HTML filter is to add a spam link deterrent to anchor tags. The deterrent, proposed by Google, gives search engines a tip about which links to follow when crawling the web. If this option is enabled, rel="nofollow" will be added as an attribute of all anchor tags.
- **Line Break Converter:** This filter converts line breaks into <br> or <p> tags depending on whether a single or double line break is found. This preserves the paragraph formatting in the text that is input.
- **URL Filter:** Any web or email addresses that are found in the text will be converted to clickable links, thus saving the user the hassle of having to type <a href="....">
- **PHP Evaluator:** The PHP Evaluator is the most radical of all Drupal's core filters. It looks for text enclosed in <?php ... ?> and evaluates it as PHP code. This effectively allows you to program and extend Drupal just by submitting content to the site! In 99% of cases, this is a bad idea, and the initial attraction of harnessing such power should be weighed by a healthy sense of fear. If you really need to write PHP code to accomplish what you're trying to do, writing a module is usually a better idea (and not that hard in most cases). Furthermore, in the wrong hands, the PHP Evaluator is an enormous security risk. A malicious attacker, with the PHP Evaluator at their disposal, could wipe out your database and take control of your web server.
## Drupal's Core Input Formats
Drupal also comes with three input formats pre-defined.
- **Filtered HTML:** This is the workhorse input format that is used most of the time for displaying posts such as blogs, pages, forum topics and so forth. It combines the URL Filter, the HTML Filter and the Line Break Converter in a way that allows users a small set of HTML tags for formatting while taking care of paragraphs and URLs behind the scenes. This is also the *default input format* for new Drupal installations. More on default input formats later.
- **PHP Code:** This input format consists of only one filter, the PHP Evaluator filter. This input format is to be used when the goal is embedding PHP code in a post.
- **Full HTML:** The Full HTML input format applies only the Line Break Converter filter. No HTML tags are stripped and no weblinks are converted to anchor tags.
## Order Matters
When an input format consists of more than one filter, the ordering of the filters has a huge impact on what the final output is. The *Filtered HTML* input format has three filters, the URL Filter, the HTML Filter, and the Line Break Converter. Here is the order in which they are executed in a new Drupal installation:

Assuming the HTML Filter allows the default set of tags (see the list above), let's examine what happens to some HTML text as it gets processed by the Filtered HTML input format. Here's the text:
```
The quick brown fox jumps over the lazy dog.
King Phillip came over from Germany swimming.
Every good boy deserves fudge.
http://drupal.org
```
The first filter is the URL filter. It will find the URL which we have in this text and make it into a proper anchor tag:
Before
```
http://drupal.org
```
After
```
http://drupal.org
```
The second filter is the HTML Filter. The text contains two HTML tags that are not on the whitelist, namely <h1> and <br>. Thus, they will be stripped.
Before
```
The quick brown fox jumps over the lazy dog.
King Phillip came over from Germany swimming.
Every good boy deserves fudge.
```
After
```
The quick brown fox jumps over the lazy dog.
King Phillip came over from Germany swimming.Every good boy deserves fudge.
```
Finally, the Line Break Converter gets its chance. It looks for line break characters (\\n, \\n\\n, etc.) and replaces them either with <br /> or encloses blocks of text in <p>...</p> tags. The [function responsible for this](http://api.drupal.org/api/5/function/_filter_autop) is quite cunning, and was inspired by code from WordPress (our debt of gratitude).
Before (as received from the HTML Filter)
```
The quick brown fox jumps over the lazy dog.
King Phillip came over from Germany swimming.Every good boy deserves fudge.
http://drupal.org
```
After
```
The quick brown fox jumps over the lazy dog.
King Phillip came over from Germany swimming.Every good boy deserves fudge.
```
So what went wrong here? Well, nothing, technically. But the output is unlikely to be what the user expected. First of all, the <h1> tag was stripped, which is a good thing because it is what the Drupal administrator wanted. Second, the place where the user wanted to make a line break using <br><br> is totally different than what the user might expect. After all, the final rendered HTML contains a <br /> tag, so why were the <br> tags from the user stripped out? And why weren't they replaced by something more intelligent by the Line Break Converter? The answer can be seen by looking at the the text as it gets passed from the HTML Filter to the Line Break Converter. Because <br> isn't on the whitelist of allowed tags, the HTML Filter takes them out, leaving the Line Break Converter no clues to follow concerning the user's wish for a line break between "swimming." and "Every good boy".
## Changing the Order
Let's look at the example above and see what would happen if we change the order of the filters. This is done by clicking to *Administer -> Site configuration -> Input formats -> (Filtered HTML) configure -> Rearrange*. Here I've changed the order so that the HTML Filter comes after the Line Break Converter.

As in the first example, the first filter is the URL Filter, so the output from that will be the same. The second filter, though, is now the Line Break Converter. Here is what happens to our text coming from the URL Filter and going into the Line Break Converter:
Before (as received from the URL Filter)
```
```
Interesting to note is that no paragraph tag was placed on "The quick brown fox". This is because <h1> elements are block level elements (which get their own line breaks in rendered HTML), so the Line Break Converter ignores them. Also interesting is that we have many instances of <p> and <br> tags, even though we're about to go into the HTML Filter which is configured to strip those tags out. Here is the final output with the new ordering of the filters (line breaks added for readability):
```
The quick brown fox jumps over the lazy dog. King Phillip came over
from Germany swimming.Every good boy deserves fudge.
http://drupal.org
```
What a mess =)
What's the real solution in this case? Well, the original order of filters worked better, so consider leaving it *URL Filter -> HTML Filter -> Line Break Converter*. One way to fix the problem would be to configure the HTML Filter to allow <br> and <p> tags. This gives HTML savvy users control over paragraph formatting. The other solution would be to submit a patch against the HTML filter (see filter.module) to have it replace <br> tags with \\n line break characters so that the Line Break Converter will pick up on them. Guess which solution is easier :P
## Input Formats and User Roles
Not all Drupal users are created equal. Some are anonymous users, some are authenticated users, and some have other user roles that allow them to have greater privileges than normal authenticated users. Furthermore, one Drupal user on every site is the super-user (user #1) who can do anything. The privilege of using an input format can be assigned to users on a per-role basis. This is an important mechanism that exists for allowing some trusted users to have access to some filters while denying this access to less trusted users.
Look at the screen *Administer -> Site configuration -> Input formats*. It lists all of the input formats that have been established. The default Drupal installation comes with three, as noted above. On any Drupal site, one input format has to be designated as the default input format. This is indicated by the radio button in the **Default** column. To guarantee the presence of at least one input format, the default format cannot be deleted. The others can be, however, and you might consider deleting any input formats (such as the PHP code format) that you don't plan on using.

On the configuration screen for an input format, you will see a listing of all the roles for users on your site. For any input format *besides the default* you can specify which user roles are privileged to use that input format. The default input format is automatically available to all users in all roles on your site and this cannot be changed.
## Filters and Security
Filters and security go hand-in-hand. Without filters, there would be no security for your site as malicious attackers would have free reign in using scripts to deface your site, subject your users to phishing scams, and steal important data such as passwords.
The heart of the security offered by filters comes from from the HTML Filter and the calls it makes to [filter\_xss](http://api.drupal.org/api/5/function/filter_xss) and [check\_plain](http://api.drupal.org/api/5/function/check_plain). These are the functions that Drupal uses to prevent attacks based on user input. For this reason, all of your user submitted output should be run through the HTML Filter. It is tempting to ignore this advice, especially if you are having troubles getting the configuration settings just right for your purposes. Don't ignore this advice. You may end up sorry.
Also worth reiterating is the fact that the PHP Evaluator filter poses an extreme risk if it can be used by anyone but highly trusted, PHP-competent site administrators. Most sites will be better off deleting the PHP code input format and not extending use of the PHP Evaluator filter to anyone.
Finally, it should be obvious that the Full HTML input format, which does not use the HTML Filter, is insecure and should be offered only to those users who can be trusted not to ruin your site. Most sites will be better off deleting this input format.
## Many More Filters Available
The fun with filters is that modules can offer their own filters. The number of filters available is large, and I can't possibly cover them all, but you can get a feel for the possibilities by looking at the [Filters and Editors](http://drupal.org/project/Modules/category/63) category of Drupal modules on Drupal.org. Here are some interesting modules that offer filters:
ModuleDescription[Amazon Filter](http://drupal.org/project/amazon_filter)Provides a text filter to insert amazon book title/links, cover images, and themable formatted information using a simple \[amazon {title|cover|info} \] tag.[BBCode](http://drupal.org/project/bbcode) Allows users to specify markup using BBCode.[Code Filter](http://drupal.org/project/codefilter)Renders syntax-highlighted PHP code. This module is used on Drupal.org.[DruTex](http://drupal.org/project/drutex) A LaTex renderer that can, among other things, render mathematical formulas and generate PDFs of nodes.[HTML Corrector](http://drupal.org/project/htmlcorrector)Corrects corrupt HTML in nodes and comments. This is useful for cases where users forget to close tags, or for where the teaser view breaks the HTML.[Inline Filter](http://drupal.org/project/inline)Uses a *\[inline:filename.jpg\]* syntax to allow for inline images or file links.[Markdown with SmartyPants](http://drupal.org/project/marksmarty)One of my favorites, this allows simple ASCII formatting to be turned into HTML. For example, ##This would be a h2, and \*this would be emphasized\*. This module is in use on http://groups.drupal.org.[Paging Filter](http://drupal.org/project/paging)Break long pages into smaller ones by means of a "page" tag.[Pirate Filter](http://drupal.org/project/pirate)Turns English into Pirate speak.[Smileys](http://drupal.org/project/smileys) Parses smiley character combinations and replaces them with inline smiley images.[Word Filter](http://drupal.org/project/wordfilter)Filters a list of restricted words.
## Conclusion
The filtering of output is an essential part of web publishing and one of Drupal's great strengths. Understanding the difference between input formats and filters, and how to configure each, is an essential step in becoming a great Drupal site administrator. Drupal modules can implement filters to make your site powerful and fun.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal Charting"
url: "/articles/drupal-charting"
type: article
date: 2009-04-23
updated: 2014-05-15
---
# Drupal Charting
# Drupal Charting
By
[ Karen Stevenson ](/about/karen-stevenson)
April 23, 2009
I needed to find a way to create nice charts from Views data so that end users could adapt them to selected date ranges or categories, but I found that this is not as easy as it ought to be. It took quite a bit of time just to figure out what the options were, let alone decide which were the most promising solutions for my situation. Since this turned into such a time-consuming project, I've documented the steps I took and what I found to make things easier for anyone else looking for solutions like this.
I investigated several Drupal 6x modules to see which ones might be ready for prime time. Many of the modules have alpha releases or less and/or have dependencies on other modules that are alpha or beta (i.e. Views Charts (alpha) depends on SWF Object API (beta), several of the modules depend on the Charts module (alpha)). Several are brand new modules with no activity beyond the initial check in. Because of that, I used the latest development version of each module in my testing to be sure I had the latest code with all fixes applied.
I checked a few statistics to evaluate how useful, popular, and well maintained each module is. I looked at the number of downloads in a recent week rather than total downloads because so many of the modules are new. I looked at the dates of the first and latest commit to see how new they are and how active the maintainers are. I noted the most recent version to make it clear which ones are development, alpha, beta, or full releases. I also noted whether or not there is Views integration, since that will be the easiest way for most people to use them. Many provide charts of system information in the administration 'Reports' area and I noted that below.
The names and dependencies are especially confusing. There are both a 'Chart' and a 'Charts' module, and both 'Open Flash Chart API' and 'Open Flash Chart 2 API'. Many require downloads from third party libraries and may not always make it clear what files are needed, where to get them, or where to put them. Many dependencies are not documented or not clear. Several require PHP 5.1 or PHP 5.2. Whatever I figured out I noted in the evaluations below.
I created some numeric and text data using CCK fields and auto-filled them using the Devel Generate module, then tried to chart my data in various modules. There are three main types of charting that are supported by the Drupal modules, Google Charts, Open Flash Charts, and Fusion Charts.
The end result in Google Charts looked like the following:


In Open Flash Charts, similar data looked like the following (with neat flash animation that you can't see here):


More information (and better examples of each) are available on their web sites:
http://teethgrinder.co.uk/open-flash-chart-2/
http://www.fusioncharts.com/
http://code.google.com/apis/chart/
## General Notes about Views Charting
There are some special caveats to getting this working with Views. None of the flash charts can be viewed in the Views preview pane, you have to save the view and look at the page or block to see the effect. This was true for every module that provided a flash alternative.
You can chart the individual values in Views, but most of the time you will want to chart aggregated totals, the COUNT or SUM or AVG of your values, so finding a way to do aggregations is important. This is handled differently by different modules. Charts provides a way to do a COUNT of values, FusionCharts is trying to incorporate aggregation settings, and add on modules like Views Calc or Views GroupBy provide other ways to aggregate totals. There is work going on in Views to add more ways to get aggregated totals and that will change the way this works in the future.
When you use aggregations you have to be careful not to add any extra fields, filters, sorts, or arguments to the view or you will screw up the logic that creates the query when Views tries to aggregate that new field. You may have to look at the query created to be able to tell if it is doing the right thing, most useful for people who know how to interpret SQL code.
Watch the values for 'pager' and 'number of items'. The number of items defaults to 10, so unless you change it you will not be viewing a chart of all your data, only the first 10 items.
It's a good idea to first create a simple view like a table of your data to be sure your view is set up to produce logical results.
Following are more details about each of the modules I evaluated.
## Charts
Url: http://drupal.org/project/charts
Drupal dependencies: Open Flash Charts depends on the Open Flash API.
Third party code: Open Flash Charts and Fusion Charts require add-ins.
Maintainer(s): brmassa
\# Downloads week of Apr 4: 647
Date added: March 10, 2008
Date lasted updated: November 13, 2008
Latest version: 6.x-1.0-alpha5
Views integration: Yes
Demonstration/Tutorial: http://drupal.org/node/233753
This module provides a single integration for all three charting methods: Google Charts, Open Flash Charts, and Fusion Charts. Because of that it implements a basic set of functions and does not support special features of any of them. For instance there is no way to use the 'map' chart in Google charts or use features like Google's setting to automatically fit the bars in a bar graph to the space available.
There is no documentation about where to put the external files for Open Flash or Fusion Charts and there were numerous issues reporting that no one else was able to get them to work. I finally figured out that using Open Flash in Charts introduces an undocumented dependency on the Open Flash Chart API (not to be confused with Open Flash Chart 2 API) but it does not check if the module is installed before trying to use it, causing potentially fatal errors if not set up correctly. Open Flash Chart API in turn requires version 1, not version 2, of Open Flash, which is fairly well buried on the Open Flash site. Once I got the right files and modules enabled, the Open Flash charts worked as well as the Google charts. I was never able to find any way to get Fusion Charts working.
The Views integration only works on simple data sets or counts (the configuration says 'Display sum of different values', but the code is counting the values, not summing them). You add one field to the view and either display its values or the aggregated counts of its values. It would take custom code or another module to do more than that. The Views Calc module provides an additional Views chart style that will do SUM, COUNT, AVG, MIN, or MAX aggregations of the data. The Views GroupBy module adds a COUNT aggregation field that can be charted.
This package also includes an optional 'System Charting' module that will create a charts display of some system statistics in the administration 'Reports' section.
This appears to be the most widely-used module, it does have Views integration, and it is used by other modules that extend the core code.
## Chart
Url: http://drupal.org/project/chart
Drupal dependencies:
Third party code: None, works with Google Charts
Maintainer(s): tjholowaychuk, chrislynch
\# Downloads week of Apr 4: 114
Date added: January 3, 2008
Date lasted updated: September 11, 2008
Latest version: 6.x-1.2
Views integration: No
Demonstration/Tutorial: http://code.google.com/p/drupal-chart-api/wiki/Examples
This module predates the other Drupal charts modules and works only with Google charts. It overcomes the problem the Charts module has in that it can fully implement the Google API since it's not trying work with other charting platforms. You can do Maps and other special Google charts. However, there is no Views integration for this module, so for all practical purposes, the only way to use it is as an API for your custom code or for system charts. The provided system charts are nice:

## FusionCharts
Url: http://drupal.org/project/fusioncharts
Drupal dependencies: Colorpicker
Third party code: Fusion Charts
Maintainer(s): aaron1234nz
\# Downloads week of Apr 4: 107
Date added: July 26, 2008
Date lasted updated: March 14, 2009
Latest version: 6.x-1.x-dev
Views integration: In progress
Demonstration/Tutorial: http://sandbox.webtolife.org/fusioncharts/multi\_series
The module has a dependency on the Colorpicker module, which is clearly noted on the project page with a link to that project. The project page also provides an outline of what it does, which includes integration with Views, Webform, and CCK, and availability as an API. Much of the installation documentation is in an included README.txt file, which makes it clear how to set things up. The state of the Drupal 6 version is correctly noted as very early stage and not ready for production.
The module includes charts in the administration 'Reports' section, like the following:

The views integration is not complete so I could not test it, but it looks very interesting, it looks like it could be far more flexible than the Charts module, letting you choose which fields to group by, how to aggregate them, and whether to include multiple values on a single chart or multiple charts. At the moment it appears progress on that is stuck, but this is exactly the kind of interface I hoped to find in all the modules.

This module is clearly not ready to use, but has interesting possibilities.
## Open Flash Chart 2 API
Url: http://drupal.org/project/ofc\_api
Drupal dependencies: PHP 5.2+
Third party code: Open Flash 2
Maintainer(s): kong
\# Downloads week of Apr 4: 26
Date added: April 3, 2009
Date lasted updated: April 10, 2009
Latest version: 6.x-1.1
Views integration: No
Demonstration/Tutorial: http://suksit.com/node/230/open-flash-chart-2-api-module-for-drupal, http://drupal.org/node/423020
This is an API for Open Flash 2 and does not do anything on its own, but is needed by other modules or used as an API.
## Open Flash Chart API
Url: http://drupal.org/project/open\_flash\_chart\_api
Drupal dependencies: None
Third party code: Open Flash 1
Maintainer(s): redndahead
\# Downloads week of Apr 4: 265
Date added: November 16, 2007
Date lasted updated: January 13, 2009
Latest version: 6.x-2.10
Views integration: No
This is an API for Open Flash 1 and does not do anything on its own, but is needed by other modules or used as an API.
## Views Charts & Charts and Graphs
Url: http://drupal.org/project/charts\_graphs
http://drupal.org/project/views\_charts
Drupal dependencies: Views, SWFObject API
Third party code: Open Flash 2, SWF Object
Maintainer(s): irakli
\# Downloads week of Apr 4: 90
Date added: March 4, 2009
Date lasted updated: March 4, 2009
Latest version: 6.x-1.0-alpha1
Views integration: Yes
Video: http://dc2009.drupalcon.org/session/business-analytics-drupal-views
Charts and Graphs is the API, and Views Charts is the Views integration module that uses the API. The project page notes that you will want to use something like Views GroupBy to do meaningful charts, and clearly identifies its dependencies, with links to the project pages for each. It depends on the SWF Object API (not to be confused with SWF Object), which in turn requires that you download and include some of the SWF Object code (which has both a version 1 and a version 2, so you have to pick up the correct one, version 2). Charts and Graphs requires that you find and download Open Flash 2 and the instructions for doing that are buried in charts\_graphs/apis/charts\_openflash/INSTALL.txt. The module creates no automatic system charts, just provides Views integration, but you could create your own system charts using Views.
Once I got all the right modules installed and all the right files set up I was able to create simple charts fairly well. The project page recommends using Views GroupBy to do aggregations but that only works for COUNT queries, plus I was not able to get it working correctly for anything other than basic node fields like title and type. CCK fields would not work at all, they added additional fields to the query that kept it from doing the right grouping.
This is a brand new project, so I assume they will iron the wrinkles out, but as it stands right now I wasn't able to get useful results for anything that required aggregation.
### Update!
When [irakli](http://drupal.org/user/96826) reported below that CCK fields ought to work, I went back to file an issue illustrating my problems and found there were significant updates to several of the modules between the time I tested them (April 15) and the time this report was published (April 23), so I re-ran all my tests using the latest code. This time I was able to aggregate and chart CCK fields with no problems. I also found that they have removed some of the dependencies and incorporated some of the external files, making it easier to set up. So I would say this alternative is much further along than it was when I first ran my tests. Since these fixes were made before the date of this report, I wanted to clarify this information.
## Statistics Pro
Url: http://drupal.org/project/statspro
Drupal dependencies: Statistics (core), Charts
Third party code: Whatever Charts requires
Maintainer(s): mr3dblond
\# Downloads week of Apr 4: 107
Date added: December 31, 2008
Date lasted updated: January 2, 2009
Latest version: 6.x-1.x-dev
Views integration: Yes
This module depends on the Charts module, so has all the same installation and set up issues. It will use whatever charting framework you set up for Charts, either Google Charts or Open Flash Charts. The project page says it has Drush support, but I didn't investigate what that meant. This package is primarily designed to provide charting for administrative information. Once set up it creates an impressive collection of system information tables and charts. All the tables are Views tables so they can be adjusted, although there are few changes you can make because they only use custom 'Statistics Pro' fields and filters, not all the fields and filters you might be expecting to see. However, as an administration tool, this is pretty impressive.

## Which Way to Go?
So which is best? Many of these are not yet ready for production, so I would use them in the administrative area or other places where failures are not critical. If you only need system charts, Chart, Charts, FusionCharts and Statistics Pro are good candidates. If you want to create custom charts using Views, only Charts and Statistics Pro, and possibly Views Charts feel at all 'ready', and even then only using simple data sets.
Views charting is definitely still in its infancy. Even with the best of them getting meaningful charts out is very much trial and error. But there are some interesting possibilities coming along that are worth keeping an eye on.
## Useful Links
- [Open Flash 1 & 2](https://sourceforge.net/projects/openflashchart/files/open-flash-chart/) - a place to get source files for both version 1 and version 2
- [SWFObject](https://github.com/swfobject/swfobject)
- [Views Calc](http://drupal.org/project/views_calc)
- [Views GroupBy](http://drupal.org/project/views_groupby)
- [Issue to add grouping to Views](http://drupal.org/node/396380)
- [Drupal Charting Group](http://groups.drupal.org/charts)
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "The Future of Form Building in Drupal (it's here!)"
url: "/articles/the-future-of-form-building-in-drupal-its-here"
type: article
date: 2008-12-03
updated: 2014-05-15
---
# The Future of Form Building in Drupal (it's here!)
# The Future of Form Building in Drupal (it's here!)
By
[ Nate Lampton ](/about/nate-lampton)
December 3, 2008
Today Lullabot released an exciting new project into the Drupal community. It's the Form builder module: an AJAX, Drag and Drop interface for constructing forms in Drupal. We hope that it will become the defacto standard in building forms in Drupal, replacing our inconsistent form-building tools that are spread across CCK, Webform, Profile, and other modules.
[
](http://quicksketch.org/demos/form-builder-example)Drag and drop and AJAXy, but degrades too! No need for JavaScript required.
### Overview
The Form builder project reads and modifies Form API arrays. Using a well-known data-structure that most Drupal developers are familiar with should make for low barrier to entry for utilizing the new module.
The project uses a AJAX-based interface for updating form elements. As you modify properties such as "Title" or "Description", Form builder makes requests in the background to update the element through Drupal's internal FAPI system. The user gets a live preview of their changes without saving the form. This approach means that no additional JavaScript needs to be written by implementing modules, since the rendering is done in PHP and then sent to the client as needed.
### Demo
Enough talk, [go try out the demo and see it in action](http://quicksketch.org/demos/form-builder-example).
### Implementing Form Builder
The Form builder is intended to only *build* forms. You can't actually *use* the forms you've built through the interface unless another module implements the hooks provided by Form Builder to save the changes. In short, the implementing modules need to learn how to read FAPI arrays and determine what those changes mean.
Take the node form for example. We have several modules that all modify this form:
- Taxonomy: Adds options for vocabularies/free tagging.
- Menu: Adds options for placement in the menu system.
- CCK: Adds all kinds of customized fields.
When a Form Builder interface for the node form is presented, Form Builder takes the **entire** form and presents it for editing. Each module that modifies the Form will say "hey, I'm responsible for X elements in this form". Each element that is claimed by a module then becomes editable. Elements that are not claimed cannot be changed.
After the editing of the form is finished, the new FAPI array is sent back to each of the modules that claimed elements. Each module module looks at the new array and updates its own settings as necessary to permanently save the changes.
This opens up all kinds of possibilities for editing forms. Instead of navigating to 6 different places to configure the node form, you've got a one-stop place where you can turn on and off or configure any field on the node-form (as long as the providing module identifies itself to Form builder).
### Contributing
This module is still very new, so we don't recommend using implementations on any production site. The APIs are still very fresh, undocumented, and due to change. However, we'd love some help shaping the future of Drupal and there is a lot of work to be done implementing support for different modules. Check out the [Form builder project page](http://drupal.org/project/form_builder) for more information on participating.
Form Builder Project: http://drupal.org/project/form\_builder
Form Builder Demo: http://quicksketch.org/demos/form-builder-example
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Using Pantheon"
url: "/articles/using-pantheon"
type: article
date: 2012-06-27
updated: 2014-05-15
---
# Using Pantheon
# Using Pantheon
By
[ Karen Stevenson ](/about/karen-stevenson)
June 27, 2012
The first ever [Midwest Developer Summit](http://midwest-developer-summit.com) is going to be held July 26 and 27, and I have been trying to help get it organized. We needed a web site, and I agreed to build it and [Pantheon](https://pantheon.io/) offered to host it for us. I've toyed around with Pantheon a bit since they first launched, but hadn't tried to build a real site and take it live. And I thought it would be interesting to use this as an opportunity to put Pantheon through its paces.
The Pantheon team has put a lot of thought into their product and I was very impressed. So impressed that once I got the Developer Summit site up I turned around and launched another personal site on Pantheon, taking screen shots as I went so I could share the experience in this article.
## Get a Pantheon Account
Step one is to set up a Pantheon account. This part is free. In fact you don't have to pay anything until you get to the point of adding in a custom domain. So you can spin up a site just to see how things work, or import an existing site to see how the process differs from whatever you do now.
Once you have an account you will see that you have a dashboard that lets you see how many sites you are able to manage. The ones you already created will show up with a screenshot of the home page, and the unused ones will have a link you can use to add a new site.

Notice that there is a link that will download a Drush alias file for all your sites. Drop that file in the same place you have installed Drush and site aliases for all your Pantheon sites are available. With that you can remotely manage your Pantheon account from a local installation using Drush commands. A really nice benefit!
## Create a New Site
When you click on the link to create a new site, you are prompted to give it a name, then wait while it is prepared.

Next you see a place to indicate what to use as the source of the site. You have an option to create a new Drupal site, use a Drupal Distribution, or import an existing site.

The available choices to create a brand new site are Drupal 6, Drupal 7, or even Drupal 8 (for those core developers who might be working on Drupal 8). If you want to build a site from a distribution, you can. The distributions available currently include things like OpenPublic by Phase2 Technology and Open Enterprise by LevelTen Interactive.
If you want to import an existing site, you can provide links to zip files that contain the code, files, and a database dump of the existing site.

I had some questions about exactly how to prepare those files for Pantheon. The database dump is self-explanatory. If you are using the Backup and Migrate module, a backup created by that works just fine.
The code includes everything in your Drupal folder except your files directory. So I made a copy of my root Drupal folder, copied the file directory (site/default/files) out of that to a separate file, and then compressed the code (without the files), and the files, each to their own zip file.
I also was unclear what to do about the settings.php file. It contains database credentials which are not going to be correct in the new environment anyway. This file is going to end up in the git repository, so I removed the database credentials and left the rest of settings.php alone. And that worked fine.
You need to put these files in a web-accessible location, and I used my Dropbox account. You have an opportunity to upload all three of these files at once, but only the codebase is required. After problems loading several large files at once, I changed my methods and started loading only the codebase in the first step. You can upload the files and database later, after the site is created. And I felt like that reduced the chances of problems.
## Dashboard
Once you have uploaded your code you will get a dashboard for your new site. You can see a number of interesting things about this dashboard.

First, you can see that you get not one, but three sites, a 'development', a 'test', and a 'live' site. Your initial code goes into the 'development' site. The system is designed to allow you to make changes in 'development', move the code to 'test' to confirm that things are working right, and then ultimately move it to 'live', i.e. the standard workflow for a Drupal site.
You can see buttons on the right that let you populate the database either by uploading a tarball or by synching from either the 'test' or 'live' site. This is where you can now upload the database if it wasn't done in the initial import. Later you can use that to sync the live database back into the 'development' site so you can test your new code against the latest content.
Similarly you have a button that allows you to populate the files directory, either by upload or by synching from 'test' or 'live'.
Other things you can do are control whether your site is public or private (hidden unless someone provides a specified username and password).
You can also clear the Varnish cache, check error logs, and make backups from the dashboard. And the button labeled "On Server Development" allows you to manage your site using sftp instead of git.
Another nice feature is a list showing the most recent git commits, with tags that indicate which environments they have been pulled into. Git commits initially show up in 'development'. After you add new code into 'development', you will see a button on 'test' and 'live' to pull that code into those environments.
Finally, you have a place where you can identify a custom domain name for the site instead of 'dev.MYSITENAME.gotpantheon.com'. As noted earlier, this step requires payment. Everything else seems to be available without payment.

If you want to export the site you created, use the dashboard to export the code, database, and files. If you make a mistake while trying to create a new site, or just want to get rid of a site you were playing around with, click the 'Configure' tab on the dashboard, where you will see an option to delete your site. The 'Configure' section also provides a place where you can give other Pantheon users access to this site by adding them as 'Team' members.

## Search, Performance, and Advanced Configuration
A really nice thing about Pantheon is that it includes lots of tools that make Drupal run better. Varnish, Redis (an alternative to Memcache), and Apachesolr are all available. Hosts not optimized for Drupal won't have these, and it's nice not to have to set it all up manually. Pantheon provides documentation about how to tweak these tools (see links at the bottom of this article).
Performance has been great. That personal site I moved to Pantheon had gotten ridiculously slow in its former location and I didn't really have time to figure out what needed to be done to make it perform better. I moved it to Pantheon and it is serving up pages significantly faster than before, and that was without doing anything at all to tweak any of Pantheon's settings. I just imported the site as-is and saw an immediate improvement.
One thing you don't get with Pantheon is direct SSH access to the server. But between the dashboard tools they provide and the ability to use Drush locally to manipulate my code, database, and files using the Drush aliases, I didn't have any need for more.
## Conclusion
Overall, my conclusion is that Pantheon is a very slick solution for people who want a fair bit of control over their Drupal code and site, but are happy to let someone else make sure the server is set up correctly and optimized to run Drupal.
## More Information
Some helpful articles from the Pantheon site include:
- [On Server Development](https://docs.pantheon.io/articles/sites/code/developing-directly-with-sftp-mode) (alternative to using git)
- [Drupal's Performance Settings](https://docs.pantheon.io/articles/drupal/drupal-s-performance-and-caching-settings)
- [Working with Varnish](https://docs.pantheon.io/articles/architecture/edge/varnish)
- [Apachesolr](https://docs.pantheon.io/articles/sites/apache-solr)
- [Using Redis](https://docs.pantheon.io/articles/sites/redis-as-a-caching-backend)
- [Going Live](https://docs.pantheon.io/articles/going-live)
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ System Administration ](/topics/system-administration)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Announcing BeautyTips, a jQuery Tooltip Plugin"
url: "/articles/announcing-beautytips-a-jquery-tooltip-plugin"
type: article
date: 2008-10-20
updated: 2014-05-15
---
# Announcing BeautyTips, a jQuery Tooltip Plugin
# Announcing BeautyTips, a jQuery Tooltip Plugin
By
[ Jeff Robbins ](/about/jeff-robbins)
October 20, 2008
\[update: This is the initial release announcement of BeautyTips. However, there's a [newer version](https://www.lullabot.com/articles/beautytips-09-release) that's been released since. To download the latest version of the module, [go here](http://plugins.jquery.com/project/bt).\]
\[update II: There is now a [project page for BeautyTips at jQuery.com](http://plugins.jquery.com/project/bt). If you have **bug reports** or **support requests** *please* [post them to the BeautyTips issue queue](http://plugins.jquery.com/project/issues/bt). I *cannot* manage issues through the comments on this post.\]
Well I've done it! I've written my first jQuery plugin. The plugin creates rollover balloon-help style tooltips for any element on your page. While there were a few different tool tips plugins that existed for jQuery, none of them seemed to quite meet my need for a NetFlix (or Google Maps) style talk-balloon popup.
It quickly became apparent that in order to accomplish this type of flexible talk-balloon tooltips, I was going to need to engage the use the HTML 5 [canvas element](https://html.spec.whatwg.org/multipage/canvas.html) (and a lot of high-school algebra and trigonometry). The result is BeautyTips, a flexible and smart tooltip which calculates the best position for each tooltip bubble and then draws it. Bubbles can have rounded corners with a variable corner radius, "spike" length, color, opacity, and much more.
[
](http://www.doitwithdrupal.com/schedule)[BeautyTips in action on DoItWithDrupal.com](http://www.doitwithdrupal.com/schedule)
The canvas element is supported in modern versions of Firefox, Safari, and Opera. However, Internet Explorer needs a separate library called [ExplorerCanvas](https://excanvas.sourceforge.net/) included on the page in order to support canvas drawing functions. ExplorerCanvas was created by Google for use with Google Maps and several of their other web apps. Include it on the page according to the readme file and BeautyTips should work just fine in IE.
Beauty Tips was written to be simple to use and pretty. All of its options are documented at the bottom of the jquery.bt.js file and defaults can be overwritten globally for the entire page, or individually on each call.
By default each tooltip will be positioned on the side of the target element which has the most free space. This is affected by the scroll position and size of the current window, so each Beauty Tip is redrawn each time it is displayed. It may appear above an element at the bottom of the page, but when the page is scrolled down (and the element is at the top of the page) it will then appear below it. Additionally, positions can be forced or a preferred order can be defined.
[
](http://www.doitwithdrupal.com/schedule)Each tip's position is determined based on its size and the available space around the target.
[Visit the DIWD schedule](http://www.doitwithdrupal.com/schedule) to see how it works.
## Usage
The function can be called in a number of ways.
`$(selector).bt(); `
`$(selector).bt('Content text'); `
`$(selector).bt('Content text', {option1: value, option2: value}); `
`$(selector).bt({option1: value, option2: value}); `
## Some examples:
`$('[title]').bt(); `
This is probably the simplest example. It will go through the page finding every element which has a *title* attribute and give it a Beauty Tips popup which gets fired on hover.
`$('h2').bt('I am an H2 element!', { trigger: 'click', positions: 'top' }); `
When any H2 element on the page is clicked on, a tip will appear above it.
`$('a[href]').bt({ titleSelector: "attr('href')", fill: 'red', cssStyles: {color: 'white', fontWeight: 'bold', width: 'auto'}, width: 400, padding: 10, cornerRadius: 10, animate: true, spikeLength: 15, spikeGirth: 5, positions: ['left', 'right', 'bottom'], }); `
This will find all <a> tags and display a red baloon with bold white text containing the href link. The box will be a variable width up to 400px with rounded corners and will fade in and animate position toward the target object when appearing. The script will try to position the box to the left, then to the right, and finally it will place it on the bottom if it does not fit elsewhere.
`$().bt.defaults.fill = 'rgba(102, 102, 255. .8)'; $(selector).bt(); `
All bubbles will be filled with a semi-transparent light-blue background unless otherwise specified.
## Live Demo
We've got a good example of BeautyTips in action over on the [Do It With Drupal schedule](http://www.doitwithdrupal.com/schedule). Resize your window and scroll the page up and down to see how the tips move around to accommodate.
## Get it!
There are still a few buggy options such as the animation in IE (it's always IE, isn't it?). And I have yet to figure out how to submit the plugin to jquery.com. But I'm really proud of what I've got so far, so I thought I'd share it here.
A complete list of options are available at the end of file. Enjoy!
[Download at jQuery.com](http://plugins.jquery.com/project/bt)
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "DrupalCon Munich Update"
url: "/articles/drupalcon-munich-update"
type: article
date: 2012-09-04
updated: 2014-05-15
---
# DrupalCon Munich Update
# DrupalCon Munich Update
By
[ Karen Stevenson ](/about/karen-stevenson)
September 4, 2012
About 1,800 people met in Munich for [DrupalCon Munich](http://munich2012.drupal.org/), the largest European event so far. It was a huge success, by any standard. The venue and food were great, the turnout was amazing, the code sprint on Friday might have had the most people I have ever seen coding in one room.
Lullabot was there. We had a number of presentations.
- [Addison Berry](https://www.lullabot.com/who-we-are/addison-berry) presented [The State of Drupal Community Education](http://munich2012.drupal.org/program/sessions/state-drupal-community-education) about the community efforts around education.
- [Joe Shindelar](https://www.lullabot.com/who-we-are/joe-shindelar) discussed [Introduction to Drupal: What I Wish Someone Told Me in the Beginning](http://munich2012.drupal.org/program/sessions/introduction-drupal-what-i-wish-someone-told-me-beginning), a discussion about important topics that can help those who are just getting their feet wet with Drupal.
- [Brock Boland](https://www.lullabot.com/who-we-are/brock-boland) teamed up with [Karyn Cassio](https://techgirlgeek.com/), [Paul Johnson](http://stuffly.posterous.com) and Addi on [To Beer Or Not To Beer? Making meetups work](http://munich2012.drupal.org/program/sessions/beer-or-not-beer-making-meetups-work)., a discussion on how to grow your local meetups.
- And my session was [There Might (Not) Be A Module For That](http://munich2012.drupal.org/program/sessions/there-might-not-be-module), a session about finding modules to solve your problems and when it might be time to roll your own solution.
In addition, the [Drupalize.me](https://drupalize.me/blog/drupalcon-munich-recap) team provided training to people who want to learn how to contribute to core, and we worked on various core initiatives. Shameless plug, I'm trying to get some momentum behind adding [more of Date into core](http://groups.drupal.org/date-api).
There were announcements of upcoming DrupalCons. The first DrupalCon in the southern hemisphere will take place in December in [Sao Paulo, Brazil](http://saopaulo2012.drupal.org/), in February will be [DrupalCon Sydney, Australia](http://sydney2013.drupal.org/), in May there is [DrupalCon Portland, Oregon](http://portland2013.drupal.org/), and the next European DrupalCon will be in [Prague, Czech Republic](http://prague2013.drupal.org/), next summer.
Another big announcement at DrupalCon Munich was that several European Drupal shops (NodeOne, Krimson, Mearra and Wunderkrau) have merged into a new company that will use the name 'Wunderkraut'. The new company has 140 Drupal professionals with offices in ten countries. See http://wunderkraut.net/en/blog/wunderkraut-merger for the announcement. Last DrupalCon, in Denver, we had another big merger ([Phase II and Treehouse](https://phase2.io/press-release/phase2-technology-announces-merger-treehouse-agency)). A number of people at DrupalCon also remembered the [Blue Marine Synergistics 'merger'](https://www.lullabot.com/articles/lullabot-is-now-bluemarine-synergistics) announced on April 1 as an April Fool's joke as another big announcement in the last year. So there is lots of merger activity in Drupal, and most of it is actually genuine.
In his keynote Dries gave a video demonstration of some of the new functionality that is already, or will soon be, available in Drupal 8. The big topics he focused on were mobile, authoring, web services, layouts, multilingual, views, and configuration management. He showed a video of these new features (some finished, some not) which you can see in the video of the keynote at http://munich2012.drupal.org/speakers/keynote/dries-buytaert (the keynote itself starts about 20 minutes in).
Some of the Drupal 8 highlights included:
- The core Bartik theme is now responsive and has been designed to work well on a small mobile screen.
- There is work going on to create a new mobile administration theme that uses pull-out menus and a new icon toolbar that should look better on a mobile device.
- The code will be HTML5-compliant out of the box.
- The node administration page is being redesigned.
- The Spark project is working on adding in features like inline editing.
- Services functionality is being added to core to better integrate with third party services, and make it easier to do things like build mobile apps on top of Drupal and do Drupal-to-Drupal communication.
- Tools are being added to create responsive layouts using the UI.
- There are numerous improvements to the multilingual system.
- There is an initiative to get Views into core.
- There is better separation between configuration and data, and configuration is now saved in files so it can be more reliably deployed to other sites.
Dries reiterated that the feature freeze will be in December and he has no plans to push that back, so any new features for Drupal 8 must be finished soon. The code freeze will be in February, and the release of Drupal 8 is targeted for next August, about the time of the next European DrupalCon.
There was a common thread among the core conversations we heard from all the major D8 initiatives. All have made good progress toward their goals and most have at least some early patches in core now, but every initiative leader talked about how much more they have to do and that they really need help. Over and over they said they hope that shops and clients using Drupal will consider contributing resources to get these initiatives finished and polished. Especially since many of them are going to be huge improvements in deployment, user experience, translation, and mobile compatibility. You can see more about the status of the initiatives and how to help at http://drupal.org/community-initiatives/drupal-core.
All in all, it was a great DrupalCon. If you missed it, I hope you'll make the next one. In addition to being a good place to learn how to use Drupal, DrupalCon is a great opportunity to meet the people that make Drupal work and help shape its future direction.
Published in:
- [ News ](/topics/news)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal Core API Cheat Sheet"
url: "/articles/drupal-core-api-cheat-sheet"
type: article
date: 2006-02-23
updated: 2014-05-15
---
# Drupal Core API Cheat Sheet
# Drupal Core API Cheat Sheet
By
[ Jeff Robbins ](/about/jeff-robbins)
February 23, 2006
[inmensia.com](https://jcmellado.github.io/articulos/drupal/cheatsheet4.7.html) has posted a cheat sheet covering the functions in core Drupal. Print it out. Put it up on the wall next to your monitor. Make some code!
The page is in Spanish. Here's a [Google translation](https://translate.google.com/translate?u=http://www.inmensia.com/articulos/drupal/cheatsheet4.7.html&langpair=es%7Cen&hl=en&ie=UTF-8&oe=UTF-8&prev=/language_tools).
And of course, [drupaldocs.org](https://www.drupaldocs.org/) can provide more in depth documentation on these functions.[inmensia.com](https://jcmellado.github.io/articulos/drupal/cheatsheet4.7.html) has posted a cheat sheet covering the functions in core Drupal. Print it out. Put it up on the wall next to your monitor. Make some code!
The page is in Spanish. Here's a [Google translation](https://translate.google.com/translate?u=http://www.inmensia.com/articulos/drupal/cheatsheet4.7.html&langpair=es%7Cen&hl=en&ie=UTF-8&oe=UTF-8&prev=/language_tools).
It would be great to see this (or something like it) in in the docs directory of the contributions repository.
And of course, [drupaldocs.org](https://www.drupaldocs.org/) can provide more in depth documentation on these functions.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Debugging Drush commands with Xdebug and PHPStorm"
url: "/articles/debugging-drush-commands-with-xdebug-and-phpstorm"
type: article
date: 2014-01-15
updated: 2014-05-15
---
# Debugging Drush commands with Xdebug and PHPStorm
# Debugging Drush commands with Xdebug and PHPStorm
By
[ Angus Mak ](/about/angus-mak)
January 15, 2014
Oftentimes, I run into issues with drush commands that needed more debugging power than dpm() provides. In search for a way to debug PHP scripts from the CLI, or drush commands more specifically, I stumbled upon PHPStormâs Zero-configuration Debugging which turned out to be perfect for the job.
First, you will need Xdebug installed. has some excellent documentation on installing XDebug. For OSX users, I would recommend using homebrew with the formulae here .
In the CLI, we will need to set the XDEBUG\_CONFIG variable.
In bash,
```
export XDEBUG_CONFIG="idekey=PHPSTORM"
```
Once Xdebug is installed and the XDEBUG\_CONFIG variable set up, start a new project in PHPStorm. Click on the Magic Button to "Start Listen PHP Debug Connections"

In the CLI, we can then run any drush command inside the drupal docroot and a breakpoint should trigger on the first line of drush.php.

Set up breakpoints and debug like you normally would. As long as PHPStorm is listening for a connection and the XDEBUG\_CONFIG variable is set, any PHP script run on the CLI will trigger the debugger to break on the first line of the script. Once you are done with debugging, click the Magic Button again to "Stop Listen PHP Debug Connections".
Drush commands always trigger a break at the first line, unless drush is included in the project. When that gets a little old, uncheck "Force break at the first line when a script is outside the project" to stop the break at the first line.

I am in the debugger so much I ended up with the xdebug.idekey set up in my php.ini permanently.
```
xdebug.idekey="PHPSTORM";
```
That way the XDEBUG\_CONFIG variable is not necessary anymore. In fact, this way any PHP activities including browsing a local site will pass through the debugging as long as PHPStorm is listening for a connection.
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Command Line Basics: More Editing with Vi/Vim"
url: "/articles/command-line-basics-more-editing-with-vivim"
type: article
date: 2010-08-31
updated: 2019-01-11
---
# Command Line Basics: More Editing with Vi/Vim
# Command Line Basics: More Editing with Vi/Vim
Replace text, copy/paste, and visual mode
By
[ Addison Berry ](/about/addison-berry)
August 31, 2010
This video picks up where we left off in the [Editing with Vi/Vim video](https://www.lullabot.com/articles/command-line-basics-editing-with-vivim). This time we take a look at some shortcuts for replacing text, how to copy/paste, and the cool visual mode feature you get with Vim.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Inline Editing and the Cost of Leaky Abstractions"
url: "/articles/inline-editing-and-the-cost-of-leaky-abstractions"
type: article
date: 2012-12-11
updated: 2021-01-12
---
# Inline Editing and the Cost of Leaky Abstractions
# Inline Editing and the Cost of Leaky Abstractions
Inline WYSIWYG editing can improve life for some content managers, but brings new problems for content-rich sites.
By
[ Jeff Eaton ](/about/jeff-eaton)
December 11, 2012
For several years, core Drupal contributors have been working on ways to improve the user experience for content editors. Since May of 2012, project lead Dries Buytaert and his company Acquia have been funding the [Spark Project](https://dri.es/announcing-spark-authoring-improvements-for-drupal-7-and-drupal-8), an ambitious set of improvements to Drupal's core editing experience. One of the most eye-popping features they've demonstrated is [Inline WYSIWYG editing](https://dri.es/spark-update-in-line-editing-in-drupal), the ability to click on a page element, edit it in place, and persist the changes without visiting a separate page or opening a popup window.
Chances are good that [inline editing functionality could make it into Drupal 8](http://drupal.org/node/1824500) -- specifically, an implementation that's powered by [Create.js](https://nicaraguanriviera.com/faq/) and the closely associated [Aloha](http://aloha-editor.org/) WYSIWYG editor. Fans of decoupled Drupal code definitely have something to cheer for! The work to modernize Drupal 8's codebase is making it much easier to reuse the great front-end and back-end work from open source projects like Symfony and Create.js.
With that good news, though, there's a potential raincloud on the horizon. Inline editing, as useful as it is, could easily be the next WYSIWYG markup: [a tool that simplifies certain tasks but sabotages others](https://rachelandrew.co.uk/archives/2011/07/27/your-wysiwyg-editor-sucks/) in unexpected ways.
## Direct manipulation: A leaky abstraction
Over a decade ago, software developer Joel Spolsky wrote a critically important blog post about user experience: [The Law of Leaky Abstractions](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/). He explained that many software APIs are convenient lies about more complex processes they hide to simplify day-to-day work. Often these abstractions work, but just as often the underlying complexity "leaks through."
> One reason the law of leaky abstractions is problematic is that it means that abstractions do not really simplify our lives as much as they were meant to.
>
> The law of leaky abstractions means that whenever somebody comes up with a wizzy new code-generation tool that is supposed to make us all ever-so-efficient, you hear a lot of people saying "learn how to do it manually first, then use the wizzy tool to save time." Code generation tools which pretend to abstract out something, like all abstractions, leak, and the only way to deal with the leaks competently is to learn about how the abstractions work and what they are abstracting. So the abstractions save us time working, but they don't save us time learning.
>
> And all this means that paradoxically, even as we have higher and higher level programming tools with better and better abstractions, becoming a proficient programmer is getting harder and harder.
Those words were written about APIs and software development tools, but they're familiar to anyone who's tried to build an humane interface for a modern content management system.
At one extreme, a CMS can be treated as a tool for editing a relational database. The user interface exposed by a CMS in that sense is just a way of giving users access to every table and column that must be inserted or updated. Completeness is the name of the game, because users are directly manipulating the underlying storage model. Any data they don't see is probably unnecessary and should be exorcised from the data model. For those of us who come from a software development background this is a familiar approach, and it's dominated the UX decisions of many open source projects and business-focused proprietary systems.
At the other extreme, a CMS can be treated as an artifact of visual web design. We begin with a vision of the end product: a photography portfolio, an online magazine, a school's class schedule. We decide how visitors should interact with it, we extrapolate the kinds of tasks administrators will need to perform to keep it updated, and the CMS is used to fill those dynamic gaps. The underlying structure of its data is abstracted away as WYSIWYG editors, drag-and-drop editing, and other tools that allow users to feel they're directly manipulating the final product rather than markup codes.
The editing interfaces we offer to users send them important messages, whether we intend it or not. They are affordances, like knobs on doors and buttons on telephones. If the primary editing interface we present is also the visual design seen by site visitors, we are saying: "This *page* is what you manage! The things you see on it are the true form of your content." On certain sites, that message is true. But for many, it's a lie: what you're seeing is simply one view of a more complex content element, tailored for a particular page or channel.
In those situations, Inline WYSIWYG editing is one of Joel Spolsky's leaky abstractions. It simplifies a user's initial experience exploring the system, but breaks down when they push forward -- causing *even more confusion and frustration than the initial learning would have.*
---
## A brief interlude, with semantics
With that provocative statement out of the way, I'll take a step back and define some terminology. Because Drupal's administrative interface, the improvements added by the Spark project, and the nature of web UX are all pretty complicated, there's a lot of potential for confusion when a term like "Inline Editing" gets thrown around. There are four kinds of editing behaviors that we'll touch on, and clarifying how they differ and overlap will (hopefully) prevent some confusion.
### Contextual editing
When a content editor is on a particular portion of the web site or is viewing a particular kind of content, they should have access to options and tools that are *contextually relevant*. If an editor visits an article on their web site, give them access to an "Edit" link for that article. If it's unpublished, they should see a "Publish" link, and so on. Contextual editing also means hiding options from users when they're inappropriate. If you don't have permission to modify an article, you shouldn't see the "Edit" link.
Well-designed contextual editing is a great thing! It puts the right tools in the users' hands when they're needed, and helps prevent "option overload"."
### API-based editing
Rather than rendering an HTML form, API-based editing means bundling up a copy of the content object itself -- usually in a format like XML or JSON -- and sending it to another program for editing. That "Client" could be Javascript code running on a user's browser, a native mobile app, or another CMS entirely. The client presents an editing interface to the user, makes changes to the object, and sends it back to the CMS they're done.
API-based editing is cool, too! It's not a specific user-visible widget or workflow. In fact, it could be used to deliver the very same HTML forms users are used to -- but it provides a foundation for many other kinds of novel editing interfaces.
### Inline editing
Inline editing takes contextual editing a step farther. When you see data on the page, you don't just have a link to edit it at your beck and call: you can edit it *right there* without going to another page or popup window. One common scenario is tabular data: click in a cell, edit the cell. Click outside of the cell, and your changes are saved. A more complex example might include [clicking on the headline of an article](https://patternry.com/p=inline-edit/) and editing it while viewing it on the front page, or clicking on the body text and adding a new paragraph then and there. The emphasis here is on eliminating context switches and unecessary steps for the editor.
Inline editing can dramatically simplify life for users by replacing cluttered forms, fields, and buttons with direct content manipulation. However, when direct manipulation the primary means of editing content, it can easily hide critical information from those same users. We'll get to that later.
### WYSIWYG editing
"What You See Is What You Get" editing is all about allowing users to manipulate things *as they will appear in the finished product* rather than using special codes, weird markup, or separate preview modes. Desktop publishing flourished on 1980s Macintosh computers because they let would-be Hearsts and Pulitzers lay out pages and set type visually. WYSIWYG HTML editors have been popular with web content editors for similar reasons: finessing the appearance of content via clicks and drags is easier than encoding semantic instructions for web browsers using raw HTML.
WYSIWYG editing tools can help reduce markup errors and streamline the work of content managers who don't know HTML. Without careful restrictions, though, it can easily sabotage attempts to reuse content effectively. If a restaraunt's menu is posted as a giant HTML table in the "Menu" page's Body field, for example, there's no way to highlight the latest dishes or list gluten-free recipes. Similarly, if the key photo for a news story is dropped into that Body field with a WYSIWYG editor, reformatting it for display on a mobile phone is all but impossible.
### Everything in-between
Often, these four different approaches overlap. Inline editing can be thought of as a particularly advanced form of contextual editing, and it's often built on top of API-based editing. In addition, when inline editing is enabled on the visitor-visible "presentation" layout of a web site, it functions as a sort of WYSWIWG editing for the entire page -- not just a particular article or field.
That combined approach -- using inline editing on a site's front end to edit content as it will appear to visitors -- is what I'll be focusing on. It's "Inline WYSIWYG."
## Inline WYSIWYG! Can anything good come from there[?](https://biblehub.com/john/1-46.htm)
Of course! Over the past year or so, anything with the word 'WYSIWYG' in it has taken a bit of a beating in web circles, but none of the approaches to content editing listed above are inherently good or bad. Like all tools, there are situations they're well-suited for and others that make an awkward fit.
Ev Williams, the co-founder of Blogger and Twitter, recently wrote about [why his team has made inline editing and WYSIWYG the native editing interface for their blogging tool, Medium.](https://medium.com/about/df8eac9f4a5e)
> As Iâm writing this, I see not just a WYSIWYG editor, I see the page Iâm going to publish, which looks just like the version youâre reading. In fact, it is the version youâre reading. Thereâs no layer of abstraction. This is a simple (and old) concept⦠and it makes a big difference. Having to go back and forth between your creation tool and your creation is like sculpting by talking.
That's an incredibly compelling argument for the power of WYSIWYG and inline editing. I've seen it in action on Medium, and it really does feel different than the click-edit-save, click-edit-save cycle that most web based tools require. However, and this is a big however, it's also critical to remember the key restrictions Ev and his team have put in place to make that simplicity work.
> One of the reasons its possible to have this *really* WYSIWYG experience is because weâve stripped out a lot of the power that other online editors give you. Here are things you canât do: change fonts, font color, font size. You canât insert tables or use strikethrough or even underline. Hereâs what you can do: bold, italics, subheads (two levels), blockquote, and links.
In addition, the underlying structure of an article on Medium is very simple. Each post can have a title, a single optional header image, and the body text of the article itself. No meta tags, no related links, no attached files or summary text for the front page. What you see is what you get here, too: when you are viewing an article, you are viewing the whole article and editing it inline on the page leaves nothing to the imagination.
This kind of relentless focus -- a single streamlined way of presenting each piece of content, a mercilessly stripped down list of formatting options, and a vigilant focus on the written word -- ensure that there really is no gap between what users are manipulating via inline editing and what everyone else sees.
That's an amazing, awesome thing and other kinds of focused web sites can benefit from it, too. Many small-business brochureware sites, for example, have straightfoward, easily-modeled content. Many of those sites' users would kill for the simplicity of a "click here to enter text" approach to content entry.
## The other side(s) of the coin
Even the best tool, however, can't be right for every job. The inline WYSIWYG approach that's used by Create.js and the Spark Project can pose serious problems. The [Decoupled CMS Project](https://decoupledcms.org/) in particular proposes that Inline WYSIWYG could be a useful general editing paradigm for content-rich webËsites, but that requires looking at the weaknesses clearly and honestly.
### Invisible data is inaccessible
Inline editing, by definition, is tied to the page's visible design. Various cues can separate editable and non-editable portions of the page, but there's no place for content elements that *aren't part of the visible page at all*.
Metadata tags, relationships between content that drive other page elements, fields intended for display in *other* views of the content, and flags that control a content element's appearance but aren't inherently visible, are all awkward bystanders. This is particularly important in multichannel publishing environments: often, multiple versions of key fields are created for use in different device and display contexts.
### It encourages visual hacks
Well-structured content models need the right data in the right fields. We've learned the hard way that WYSIWYG markup editors inevitably lead to ugly HTML hacks. Users naturally assume that "it looks right" means "everything is working correctly." Similarly, inline WYSIWYG emphasizes each field's visual appearance and placement on the page over its semantic meaning. That sets up another cycle of "I put it there because it looked right" editing snafus.
The problem is even more serious for Inline WYSIWYG. Markup editors can be configured to use a restricted set of tags, but no code is smart enough to know that a user misused an important text field to achieve a desired visual result.
### It privileges the editor's device
In her book *[Content Strategy for Mobile](https://abookapart.com/products/content-strategy-for-mobile)*, author Karen McGrane explains the dangers of the web-based "preview" button.
> â¦There's no way to show \[desktop\] content creators how their content might appear on a mobile website or in an app. The existence of the preview button reinforces the notion that the dekstop website is the "real" website and \[anything else\] is an afterthought.
Inline WYSIWG amplifies this problem, turning the entire editing experience into an extended preview of what the content will look like on the editor's current browser, platform, screen size, and user context. The danger lies in the hidden ripple effects for *other* devices, views, publishing channels, and even other pages where the same content is reused.
### It complicates the creation of new items
Create.js and the Spark Project also allow editors to create new content items in place on any listing page. This is a valuable feature, especially for sites dominated by simple chronological lists or explicit content hierarchies.
On sites with more complex rule-based listing pages, however, the picture becomes fuzzier. If an editor inserts a new piece of content on another author's page, does the content become owned by that author? On listing pages goverened by complex selection rules, will the newly-inserted item receive default values sufficient to ensure that it will appear on the page? If the editor inserts new content on a listing page, but alters its fields such that the content no longer matches the listing page's selection rules, does the content vanish and re-appear in a different, unknown part of the web site?
In addition, multi-step workflows accompany the creation of content on many sites. Translating a single piece of content into several legally mandated languages before publication is necessary in some countries, even for small web sites. Approval and scheduling workflows pose similar problems, moving documents through important but invisible states before they can be displayed accurately on the site.
### Complexity quickly reasserts itself
Many of the problems described above can be worked around by adding additional visual cues, exposing normally hidden fields in floating toolbars, and providing other normally hidden information when editors have activated Inline WYSIWYG. Additional secondary editing interfaces can also be provided for "full access" to a content item's full list of fields, metadata, and workflow states.
However, the addition of these extra widgets, toolbars, hover-tips, popups, and so on compromise the radical simplicity that justified Inline Editing in the first place. On many sites, a sufficiently functional Inline WYSIWYG interface -- one that captures the important state, metadata, and relational information for a piece of content -- will be no simpler or faster than well-designed, task-focused modal editing forms. [Members of the Plone team discovered that was often true](https://plone.org/products/plone/roadmap/238) after adding Inline WYSIWYG to their CMS. After several versions maintaining the feature, [they removed it from the core CMS product](http://plone.293351.n2.nabble.com/RFC-re-inline-editing-td7560809.html).
To reiterate Ev William's vision for Medium,
> Thereâs no layer of abstraction. This is a simple (and old) concept⦠and it makes a big difference. Having to go back and forth between your creation tool and your creation is like sculpting by talking.
In situations where Inline WYSIWIG can't live up to that ideal, it paradoxically results in even *more* complexity for users.
---
## In conclusion, Inline WYSIWYG is [a land of contrasts](http://www.globejotting.com/the-most-overused-cliche-in-travel-writing/)
So, where does this leave us? Despite my complaints, both Inline and WYSIWYG editing are valuable tools for building an effective editorial experience. The problem of leaky abstractions isn't new to Drupal: Views, for example, is a click-and-drag listing page builder, but requires its users know SQL to understand what's happening when problems arise. As we consider how to apply the tools at our disposal, we have to examine their pros and cons honestly rather than fixating on one or the other.
The combined Inline WYSIWYG approach *can* radically improve sites that pair an extremely focused presentation with simple content. But despite the impressive splash it makes during demos, Inline WYSIWYG as a primary editing interface is difficult to scale beyond brochureware and blogs. On sites with more complex content and publishing workflows, those training wheels will have to come off eventually.
Is Inline WYSIWYG right for Drupal core? While it can be very useful, it's not a silver bullet for Drupal's UX werewolves. Worse, it can actively confuse users and mask critical information on the kinds of data-rich sites Drupal is best suited for. Enhanced content modeling tools and the much-loved Views module are both built into Drupal 8; even new developers and builders will easily assemble sites whose complexity confounds Inline WYSIWYG.
At the same time, the underlying architectural changes that make the approach possible are incredibly valuable. If Drupal 8 ships with client-side editing APIs as complete as its existing server-side edit forms, the foundation will be laid for many other innovative editing tools. Even if complex sites can't benefit from Inline WYSIWYG, they'll be able to implement their own appropriate, tailored interfaces with far less work because of it.
Like WYSIWYG markup editors, design-integrated Inline WYSIWYG editing is an idea that's here to stay. Deciding when to use it appropriately, and learning how to sidestep its pitfalls, will be an important task for site builders and UX professionals in the coming years. Our essential task is still the same: giving people tools to accomplish the tasks that matter to them!
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ Drupal Site Building ](/topics/drupal-site-building)
- [ UX & Design ](/topics/design-and-ux)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Using Remote Image Files When You Develop Locally"
url: "/articles/using-remote-image-files-when-you-develop-locally"
type: article
date: 2013-08-21
updated: 2021-01-12
---
# Using Remote Image Files When You Develop Locally
# Using Remote Image Files When You Develop Locally
Save precious disk space with Apache Rewrite rules
By
[ Sean Lange ](/about/sean-lange)
August 21, 2013
If you work on large Drupal sites, you probably run into the problem of the enormous "files" directory. Keeping your development server (or personal computer) in sync with production is a big pain, but without those uploads and file attachments, it's easy to miss important design problems with site content.
There are lots of slow, complicated ways to solve the problem. Drush commands, shell scripts and even (please say no!) FTP can be used to download all of a site's image assets and file uploads to your local development machine. I want to save that precious disk space, though!
I started out with the [Stage File Proxy](http://drupal.org/project/stage_file_proxy) module. It lets Drupal point all of its file requests to the "live" server, even when the site is running on your local development machine. While the module works well it required me to make tweaks to the site that I preferred not to worry about.
One of the issues for me was adding lines of code to settings.php. That caused issues with revision control systems and multiple developers. In addition, I had to re-enable the module after each database sync (because it is not enabled on the dev/prod sites). Finally, there's the general maintenance overhead of adding an extra module to the site. The module is a solid solution, but I wanted something more.
I found my answer in Apache URL rewrite rules. When the Apache program handles incoming web page requests, rewrite rules allow it to change URLs matching certain patterns -- for example, they can turn requests for the 'files' directory on your local machine into requests for remote URLs on the production server.
I tracked down several posts and tutorials on rewrite rules and finally landed on one that worked for me: . I'm using [MAMP](https://www.mamp.info/en/): adding this snippet it was easier than installing a module, requires no changes to your site settings or configuration, and has no code to maintain or enable. The steps are a bit different if you're using a different development setup, but the principle is the same.
Here's the example code:
```
### Apache Rewrite
RewriteEngine on
# Force image styles that have local files that exist to be generated.
RewriteCond %{REQUEST_URI} ^/sites/([^\/]*)/files/styles/[^\/]*/public/((.*))$
RewriteCond %{DOCUMENT_ROOT}/sites/%1/files/%2 -f
RewriteRule ^(.*)$ $1 [QSA,L]
# Otherwise, send anything else that's in the files directory to the
# production server.
RewriteCond %{REQUEST_URI} ^/sites/[^\/]*/files/.*$
RewriteCond %{REQUEST_URI} !^/sites/[^\/]*/files/css/.*$
RewriteCond %{REQUEST_URI} !^/sites/[^\/]*/files/js/.*$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ http://www.example.com/$1 [QSA,L]
```
Next, Open MAMP. Under the Advanced tab, you will find a small "Customized virtual host general settings" box near the bottom. Paste in the code above, but REPLACE 'http://www.example.com' with the address of the production server that contains the files you'll need.

Finally, restart Apache. That's all it took, and now I have plenty of room for cat pictures in my drive!
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Command Line Basics: Bash Aliases"
url: "/articles/command-line-basics-bash-aliases"
type: article
date: 2010-12-06
updated: 2019-01-11
---
# Command Line Basics: Bash Aliases
# Command Line Basics: Bash Aliases
Create custom command shortcuts
By
[ Addison Berry ](/about/addison-berry)
December 6, 2010
This video shows you how to create your own custom shortcuts for various commands. We'll look at some common aliases and see how to add them to our command line environment. This is super handy for commands that you type in all the time and don't want to go through the tedium of typing the whole thing out every time. For example, we show how to automatically go to a particular directory with just one word (e.g. type "clients" and go to the /Users/add1sun/lullabot/clients directory immediately).
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Best practices in open source development"
url: "/articles/best-practices-in-open-source-development"
type: article
date: 2007-02-19
updated: 2016-04-07
---
# Best practices in open source development
# Best practices in open source development
How to develop with open source software
By
[ Angie Byron ](/about/angie-byron)
February 19, 2007
## Introduction
Open source software has countless advantages over proprietary software. While there are disadvantages as well, such as the lack of built-in support contracts and no one to blame when things go wrong (which means no one to sue), in most cases moving to open source software is a smart move. You never need to just accept whatever functionality comes with it; you can modify the software to do whatever your heart desires, and you're never "locked in" with a particular vendor when you need additional functionality. There is a community of people developing, using, and testing the software, which tends to lead to higher quality and faster growth. Plus, in terms of initial costs, it's generally cheaper than proprietary software (often times, free).
However, gaining the full advantages of open source requires a fundamental shift in development practice. And people who are not "in the know" on this point can often get into trouble when they continue to work in a traditional way, and a negative attitude about open source software (it's not me, it's them!) can result.
This article provides some best-practice tips and advice that we at Lullabot employ while working on on development projects. While this article talks primarily around our experiences with [Drupal](http://drupal.org), its lessons should still apply for anyone working with open source software.
## Best Practice #1: Learn how to Extend The Software
Drupal has a little motto that goes something like:
> If you have to "hack core" (change core files) to get Drupal to do what you want, you're probably doing something wrong.
This advice holds true for most other open source projects; always perform investigation into the tools and techniques the software makes available for customization. These may include a "skinning" or "themeing" system to customize the look and feel, the ability to extend functionality through modules, extensions, and add-ons, or configuration options that can be altered to fit your needs.
Your first goal when working with open source software should be to try and "get in the heads" of the developers of the software, and determine how they intend for the project to be extended. While it can appear easier on the surface to just bolt on a chunk of functionality you're missing, doing so means inadvertently undoing all the collective work and thinking the community around your project has poured into the problems you're facing.
Learn what tools and techniques are available to you, and you'll have a much easier time building your project.
## Best Practice #2: Do. Not. Fork.
The second you change any of the default core files of an open source project in any way, even for something as small as changing the text here or there, you have created a "fork." This hurts you in a number of different ways:
- **Difficulty in getting support.** Your changes may cause subtle bugs to appear, and you're going to have a very hard time finding other people to help you track them down, if they can't reproduce the problems themselves.
- **Difficulty in upgrading.** Each change you make, no matter how minor, needs to be carried over each time you upgrade the site's code base. If you forget a change, your site behaves differently. If a security patch changes a line where you've made a customization, you then have to hunker down and do a bunch of analysis and testing as you attempt to merge the changes and ensure that you're still getting the proper fix.
- **Maintenance headaches.** Because this is your code that you've written, you're on the hook for maintaining it, testing it, and improving it. More on this and why it's a bad thing in a bit.
Resist the urge to fork, and instead...
## Best Practice #3: Participate within the community
There are many advantages to using open source software, but probably the biggest advantage is the virtually limitless resources involved in the open source ecosystem. Millions of people around the world are testing the software, fixing problems, and adding new features. It's simply impossible to reproduce this kind of momentum with a small team (or even a very large team).
Yet, many people only see open source software as a cheap (or free) alternative to building things from scratch, and completely ignore the community aspect of the software. This is detrimental, both to you and your clients, and to the software project itself.
Let's imagine a scenario where you find a bug in your open source project of choice. Your natural inclination might be to just troubleshoot and fix the bug in your local copy, and then move on with your day. After all, we're all busy people, and client deadlines aren't getting any shorter.
An alternate, and more effective, approach is to do the following:
1. **Search for the bug.** When you very first encounter a bug, *before you even attempt to fix it*, your first instinct should always be to search for the bug in the issue queue or bug tracking software used by the project. It's possible someone has already found the bug, and reported a fix for it. If so, you just saved yourself some work.
2. **Report the bug.** If the bug isn't there, the second thing you should do, again *before you even attempt to fix it*, is submit a detailed bug report. Often, developers are very active on issue queues, and they are also more familiar with their code base, so they can often find and get to the root of a bug before you can.
3. **Submit your fix.** Assuming someone in the community didn't get a fix out to you before you figured it out, *always* submit your fix back. Not only is this good "karma," as you've just saved the next person from having to do this (and karma in an open source community is your very best asset), but often you can get good feedback from other developers as to whether or not your fix is the best solution, *and* either way, you help ensure the fix gets put into the "official" product, which means you no longer have to maintain it.
Let's use another example. A client needs some crazy new feature for a site you're building.
1. **Search for an existing module.** Even if there's not a "100% fit," a 90% fit (or even 50% fit) is a better starting place than nothing.
2. **Submit a feature request.** If there's a module that will work as a starting point, just as with bug reports, *before you start coding anything* submit your intent to work on the additional functionality. You may get other people interested who will offer co-development, testing, or even just some cold, hard cash toward its development. Or, the module author might chime in with how they already tried it, how it didn't work, and how to use X instead.
3. **Contribute new code (a new module for example) to the community.** If there's not already something out there that fits your needs and you need to build it, submit it to the community in whatever they're using as a "forge" system (CVS, Subversion, etc.) If possible, the best thing to do is place your code there *as early during the development as possible*. This allows others to try out your changes and provide testing, bug reports, and usability feedback long before it reaches your client's site. Procrastination can create more custom code for you to maintain: lots of people have really good intentions of committing stuff back when all's said and done, but often it's easy to get side-tracked.
4. **Work out of the issue queue.** When you add a new feature to your module, make a feature request for it, attach a patch, and commit it. When you fix a bug, make a bug report for it, attach a patch, and commit it. The discipline you show here will help pay off ten-fold by getting more eyes on the code, and by making changes small and easy to isolate should something go wrong.
By pushing fixes and features back "upstream," you help ensure that future sites you build will have the functionality you're looking for, rather than having to constantly re-invent the wheel. You also turn into a collaborator on the project, rather than a user.
## Advantages of following best practices
Working in this manner has the following advantages:
- **It helps save you time (and saving time saves you money).** You might find answers before you even begin your quest. Another developer can come along and code something before you even have the chance to do so.
- **It earns you karma.** In addition to the "warm fuzzy feeling" that contributing gives you which is a reward in its own right, open source communities tend to be "meritocracies." Opinions of individuals are generally weighted by the community, either consciously or subconsciously, by the level of contribution they've given to a project. Gaining a reputation as a contributor often means your questions get answered sooner, and it also means more business for you as new clients see you being committed to the platform, and other contributors in the community refer additional work to you.
- **It increases the quality of your code.** More eyes mean more testing, more feedback on the approaches you've taken to problem solving, and in the end more flexible, usable, and easy-to-understand code and functionality.
- **It lets the community help maintain your stuff.** Putting stuff out in the community means you increase your chances of someone other than just you using your code. And that's a very good thing! These other people will contribute their own bug fixes and features to your module, and help test it for bugs. They can also help with upgrades between major versions. With custom code, you are the only person who is able to do this.
## Disadvantages of following best practices
There are not really any, other than it takes a bit more time to do up front. You can help offset that by building that time into your contracts, and by client education. Explain to your clients about how open source is different, and that in order to get the very best "bang for their buck," it's important to leverage the community development model. This will help ensure that they enjoy all of the benefits of open source, including not being "locked in" to a proprietary fork, having the highest-quality code, and being "future proof" for upgrades.
## "Real world" implications
Lullabot has built a number of sites using these best practices, which has resulted in **thousands** of contributions back to the Drupal community in the form of themes, modules, and core patches. That's a win for us, a win for our clients, and a win for Drupal. You can't get much better than that. :)
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Using Secondary Menus"
url: "/articles/using-secondary-menus"
type: article
date: 2008-11-06
updated: 2014-05-15
---
# Using Secondary Menus
# Using Secondary Menus
By
[ Addison Berry ](/about/addison-berry)
November 6, 2008
\[embed\]http://blip.tv/file/3398950\[/embed\]
This video looks at using Drupal menus, specifically how to use the secondary menu concept to create a relation between your top level menu items and a child menu. Once we set them up, I play around in the theme to change up where they get displayed. I also play a bit with the CSS classes that are used. Besides learning about secondary menus, this video is also a brief intro to tweaking the page.tpl.php file of your theme.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Dynamic CSS Files in Drupal"
url: "/articles/dynamic-css-files-in-drupal"
type: article
date: 2006-01-31
updated: 2014-05-15
---
# Dynamic CSS Files in Drupal
# Dynamic CSS Files in Drupal
By
[ Jeff Robbins ](/about/jeff-robbins)
January 31, 2006
[Dave Cohen](https://www.d10.dev/)'s comment on Nick Lewis' [recent post](http://nicklewis.smartcampaigns.com/node/753) got me to thinking about the power of serving a dynamic CSS file. To quote:
> I don't know why themes don't use PHP to define styles. I've started to define a style.php instead of a style.css. This way, I define all the colors near the top of the file, and use PHP to refer to the variables later.
> You have to put this in style.php:
> `header("Content-type: text/css"); `
>
> And this in your template.php (if using phptemplate engine):
> `theme_add_style(dirname(__FILE__).'/style.php'); `
That's a great trick. Using some variations on this trick, one could create a Drupal module/theme combo that would allow less technical administrators to modify style information through a form in the admin menu. Administrators could have a form where they could enter hex color codes for things like body background, links, hovering over links, block titles; font size for h1, h2, h3, etc.; alignment, border color and width -- all of these would be great for creating a quick, customized site without too much technical knowhow.
A more technical, but more flexible solution is to just allow users to edit the CSS in a textarea (a la Movable Type's template system). However, although I don't want to limit creativity, I still like the idea of a multiple choice and/or guided theme customization process.
Both of these methods have the advantage of being able to view changes immediately and therefore administrators can experiment with different settings easily. Stick in some pop-up color pickers, some image uploading, font-family pickers, and the ability to position things on the page and we'll really have something to offer non-developers who want to tap into the wealth of features offered by Drupal without feeling like they're getting a "canned" site.
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Display Suite: Building Fancy Teasers Without Custom Templates"
url: "/articles/display-suite-building-fancy-teasers-without-custom-templates"
type: article
date: 2011-10-04
updated: 2014-05-15
---
# Display Suite: Building Fancy Teasers Without Custom Templates
# Display Suite: Building Fancy Teasers Without Custom Templates
By
[ Karen Stevenson ](/about/karen-stevenson)
October 4, 2011
I've been working on a Fantasy Site for next week's [Do It With Drupal](http://doitwithdrupal.com) conference, a Drupal version of the [Meetup.com](https://meetup.com/) site. It's involved digging deep into the Drupal 7 versions of Organic Groups, Views, Panels, Date, and lots of other modules. I quickly identified the main content types I needed: Group, Meeting, Place, and RSVP, but looking around the site I soon realized it would need extremely complex 'teaser' views of all these types. Each one would require lots of information from the content itself *as well as* related content, and each content type would need several totally different 'teaser' views.
## The Challenge
The site's 'Group' content type is a good example. It needs a square teaser like the following for display on the home page. It includes the title, image, and location of the Group along with information about the most recent meeting.

That one seems relatively straightforward, but there is a totally different three column teaser for the 'Group' content type used on the 'Find a Group' page. Note that this group teaser has group information, membership information, and even includes what looks like an embedded 'teaser' view of next upcoming meeting.

The 'Meeting' content type, in turn, needs multiple different teasers. In addition to the version embedded in the 'Group' teaser above, there is a different, square view used in a carousel at the top of the 'Find a Group' page that looks like the following.

And there is yet another iteration of the Meeting 'teaser' used in views of upcoming meetings:

## Alternative Solutions
Drupal gives us quite a few ways to create those different teasers. Since they are all displayed in Views, I could try to construct them by assembling each individual fields in the view itself. That would require lots of relationships to join in all the required content, though, and a lot of custom rewrites to format them correctly. In addition, much of the information comes from Organic Groups, but the D7 version of Organic Groups doesn't yet expose all of the necessary fields via its Views Relationships. (There is an issue about this on the [Organic Groups issue queue](http://drupal.org/node/1238186), and hopefully that will be rectified in the future). Even without the Organic Groups problem, adding each individual field to a view and getting them to display exactly as we want them to would be challenging.
Another way to approach the problem would be to create a custom node .tpl file for each content type, and manually add the html to each template to create the variations. This approach to creating the display variations would simplify the views considerably. Each view would be a simple 'content' view rather than a 'fields' view, and would use the the 'teaser,' 'square,' or 'carousel' view mode.
To do this manually, I would have to create a separate .tpl file for each variation, create a preprocess hook to prepare the values they all need, add some custom code to define the additional 'View modes' for each of my content types, finally add theme suggestions so Drupal will look for a different .tpl file for each view mode.
However, one of the requirements of the Meetup.com site is that each group be able to set its own theme. If I used custom .tpl files in the theme, I would have to replicate them all in every theme exposed to the groups. I really wanted a way to create the 'structure' of the teasers independently of the theme. What's the solution?
## Enter Display Suite
That set of requirements led me to the [Display Suite](http://drupal.org/project/ds) module. As its name implies, Display Suite provides a collection of tools to control the display of Drupal entities. It allows you to create as many custom 'View modes' as necessary; use a different layouts and place fields differently in each display mode; and use pre-build Display Suite layouts, existing Panels layouts, or build your own. In addition, it allows you to create custom 'fields' in addition to the node fields that would ordinarily be available, and place those fields wherever you need them.
When you enable Display Suite and visit its settings page at *admin/structure/ds*, you'll see a screen like the following:

The screen provides quick links to the 'Display Fields' screens for each entity and content type (some of which are otherwise pretty well buried in various places in the administration area). You can create custom View modes from this screen, and you can add custom fields.
The custom fields can be created in various ways; Display Suite can locate values that you've created in a custom preprocess function, or you can paste in custom PHP code directly. For the Fantasy site, I chose to create some of the complex values I needed in hook\_node\_preprocess(), then add them to Display Suite as 'Preprocess' fields for placement in my teasers.
For the complex group teaser illustrated above, I needed three values that weren't otherwise available: a count of the group members (member\_count), a formatted verision of the city and state the group is located in (formatted\_location\_text), and a 'square' teaser view of the next meeting (next\_meeting\_view). As you can see from the screenshot above, if the group is private or there is no next meeting, that value needs to have some placeholder text to indicate that. The following code does the work of building those values in a preprocess function:
```php
/**
* Implements hook_preprocess_nodee().
*/
function groupal_preprocess_node(&$vars) {
global $user;
$node = $vars['node'];
switch($node->type) {
case 'group':
// Get the group and its gid.
$group = og_get_group('node', $node->nid);
$gid = $group->gid;
// See if the group is public or private.
$access = field_get_items('node', $node, 'group_access');
$private = $access[0]['value'];
if ($private && og_user_access($gid, 'view meeting content')) {
$private = FALSE;
}
// Get a membership count.
$memberships = og_membership_load_multiple(FALSE, array('gid' => $gid, 'entity_type' => 'user'));
$vars['member_count'] = '
';
// Get a view of the next meeting.
$next_meeting_id = // Logic to retrieve the nid of the next meeting for this group goes here;
$next_meeting = node_load($next_meeting_id);
if ($private) {
$vars['next_meeting_view'] = '
' . t('Meeting details are available only to members.') . '
' . t('There are no upcoming meetings for this group.') . '
';
}
// Get formatted text to display the location the way we want it.
$vars['location_formatted_text'] = '' . t('@city, @state', array(
'@city' => $node->locations[0]['city'],
'@state' => $node->locations[0]['province'],
));
}
}
```
Now that those values are available in the preprocess function, I can go into Display Suite and add my new 'Preprocess' fields.

For each one, I create a field that has a machine name matching the name of the variable from the preprocess function above.

Then I go to the Display Fields screen for the 'Group' content type and select the 'Teaser' view. It looks like it normally would, until I choose the Display Suite option to use a custom layout -- in this case a three column layout.
Once I select the three column layout, lots of new fields will become available. Some of them are the fields I always see, the ones added by Drupal's Field API tools. Others are new ones that Display Suite itself adds, like a customizable 'Title' field, a 'Read more' link, and the image of the node author, each of which can be placed and styled like standard fields. In addition, I see the preprocess fields I defined earlier.
In the screenshot below, you can see the way the 'Display Fields' screen looks for the Group teaser after setting it up to use Display Suite's 3 column layout, as well as adding custom fields like 'Location Formatted Text' and 'Next Meeting View' to the appropriate regions of the layout.

At this point the only custom code I have created is the preprocess hook and some custom css; I have't used any custom .tpl files, either. If I add the preprocess hook and css in a custom module rather than in my theme, it will be available across all themes. That way, when users are given the ability to switch themes, all the complex teasers will still contain the right information in the right layout. Better yet, all the Display Suite configuration is exportable, so it can be captured in a Feature and deployed elsewhere.
See the video of this [Do It With Drupal](http://2011.doitwithdrupal.com/2011/sessions/fantasy-site-meetupcom) Session on [Drupalize.me](https://drupalize.me/videos/fantasy-site-groupal-meetupcom).
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Git Best Practices: Upgrading the Patch Process"
url: "/articles/git-best-practices-upgrading-the-patch-process"
type: article
date: 2011-04-12
updated: 2018-01-25
---
# Git Best Practices: Upgrading the Patch Process
# Git Best Practices: Upgrading the Patch Process
If you're struggling to get your bearings in the new Git world, this article should help with the transition.
By
[ Andrew Berry ](/about/andrew-berry)
April 12, 2011
For close to a decade, the CVS version control system has been an integral part of every Drupal developer's workflow. Many site builders could get by downloading release versions of Drupal and assorted modules, but using bleeding-edge code, contributing modules, and submitting bug fixes or enhancements to existing projects all meant getting comfortable with CVS. In March of 2011, that all changed: all of the projects hosted on Drupal.org were migrated to the Git version control system! If you're struggling to get your bearings in the new Git world, this article should help with the transition. Before the Great Git Migration, there were two options for creating patches.
Using diff: `$ diff -up system.module.orig system.module > 12345_issue_name.patch `
Or, using CVS: `$ cvs diff -up > 12345_issue_name.patch `
Now that we are using [Git](https://git-scm.com/) for our day to day work, we face a different problem. Not only are there multiple ways to create a patch, but they can produce different results that require different commands to apply. Let's take a look at the different commands and see how they can be used.
## Basic Patches with "git diff"
`git diff` is the command that is most similar to `diff` or `cvs diff`. By default, it will create a patch of all [unstaged changes](https://progit.org/) against the current commit. Compared to the output of `cvs diff`, the diff header is slightly different. Let's generate a patch between two commits in Drupal 7:
```diff
$ git clone --branch=7.x git://git.drupalcode.org/project/drupal.git drupal-7.x
$ cd drupal-7.x
$ git diff 66f93d7..f1ba363
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 4319dbf4c2..0981438c20 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -516,8 +516,6 @@ function hook_entity_prepare_view($entities, $type) {
/**
* Perform periodic actions.
*
- * This hook will only be called if cron.php is run (e.g. by crontab).
- *
* Modules that require some commands to be executed periodically can
* implement hook_cron(). The engine will then call the hook whenever a cron
* run happens, as defined by the administrator. Typical tasks managed by
```
What are the first two lines of the diff output telling us?
- `--git` is a helpful note that this patch was generated with Git.
- `a/` and `b/` are prefixes added to the paths by Git.
- `index 4319dbf..0981438 100644` provides three useful pieces of metadata:
1. `index` indicates that the line shows Git index metadata.
2. `4319dbf..0981438` shows that the first [blob hash](https://book.git-scm.com/1_the_git_object_model.html) was 4319dbf and the resulting blob hash was 0981438. One interesting note about the second hash is that if you are running diff against uncommitted changes, the hash represents the hash of the resulting file if you actually commit the change.
3. Finally, `100644` indicates the permissions set on the resulting file in octal format.
## Classic Patches with "git diff --no-prefix"
This command removes the "a" and "b" prefixes from the diff header. Specifying this option allows patch to be run using `patch -p0`, just like with CVS. This was commonly used by those using Git before Drupal itself switched over to Git.
```diff
$ git diff --no-prefix 66f93d7..f1ba363â
diff --git modules/system/system.api.php modules/system/system.api.php
index 4319dbf4c2..0981438c20 100644
--- modules/system/system.api.php
+++ modules/system/system.api.php
@@ -516,8 +516,6 @@ function hook_entity_prepare_view($entities, $type) {
/**
* Perform periodic actions.
*
- * This hook will only be called if cron.php is run (e.g. by crontab).
- *
* Modules that require some commands to be executed periodically can
* implement hook_cron(). The engine will then call the hook whenever a cron
* run happens, as defined by the administrator. Typical tasks managed by
```
Unless you are working with other version control systems as well (such as Subversion), this option should no longer be needed.
## Module Patches with "git diff --relative"
Using `--relative` tells Git to generate the patch relative to the current directory. This is especially useful when generating a patch for a contributed module from within a Drupal instance. For example, here's a patch ([issue #466134 on drupal.org](http://drupal.org/node/466134)) against the [Date module](http://drupal.org/project/date) from within an existing Drupal project:
```diff
$ pwd ~/workspace/drupal6/sites/all/modules/date
$ git diff --relative 1bc4aa..1c7b4e
diff --git a/date_api_elements.inc b/date_api_elements.inc
index 440bcfd..39332bf 100644
--- a/date_api_elements.inc
+++ b/date_api_elements.inc
@@ -252,7 +252,6 @@ function date_parts_element($element, $date, $format) {
$part_type = in_array($field, $element['#date_text_parts']) ? 'textfield' : 'select';
$sub_element[$field] = array(
'#weight' => $order[$field],
- '#required' => $element['#required'],
'#attributes' => array('class' => (isset($element['#attributes']['class']) ? $element['#attributes']['class'] : '') .' date-'. $field),
);
switch ($field) {
@@ -665,4 +664,5 @@ function date_convert_from_custom($date, $format) {
// Don't test for valid date, we might use this to extract
// incomplete date part info from user input.
return date_convert($final_date, DATE_ARRAY, DATE_DATETIME);
-}
\ No newline at end of file
+}
+
```
This patch would easily apply to a checkout of the date module by itself.
## Apply Patches with "git apply"
Now that a patch file has been generated, we can use `git apply` to apply the patch. If the patch was generated with plain `git diff`, then applying the patch is as simple as running `git apply `:
```diff
$ git apply 1041440_hook_cron_phpdoc.patch
$ git diff
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 4319dbf..0981438 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -516,8 +516,6 @@ function hook_entity_prepare_view($entities, $type) {
/**
* Perform periodic actions.
*
-* This hook will only be called if cron.php is run (e.g. by crontab).
-*
* Modules that require some commands to be executed periodically can
* implement hook_cron(). The engine will then call the hook whenever a cron
* run happens, as defined by the administrator. Typical tasks managed by
```
If the patch was generated with no prefix (such as from `cvs diff`), use the `-p` flag just like you would with `patch`. `git apply` has two key differences from `patch`. First, it will not apply a patch if you have other uncommitted changes in your code. Either commit your changes, or stash them with `git stash`. The other significant difference is that by default, `git apply` will not apply a patch that does not apply cleanly. When reviewing large patches, this is a great way to determine if the patch cleanly applies without needing to worry about cleaning up the incomplete patch. To force git apply to apply the patch anyways, use the `--reject` flag.
## Creating Better Patches with "git format-patch"
While `git diff` and `git apply` are significantly improved over `cvs diff` and `patch`, they pale in comparison to the power of `git format-patch`. This command doesn't just generate a diff, but provides all of the metadata needed to replicate a series of commits. The command originated from the Linux community's need to share patches over email. We can do the same thing by uploading format-patches to issue queues.
To use this command, run it with `git format-patch `, where the source branch is the branch your code branched from. For example, to generate patches that make up Drupal 8.x as compared to Drupal 7.x, checkout the 8.x branch, and run `git format-patch 7.x`. This will generate numbered files, with each corresponding to a single commit. It is also possible to put all of the commits into a single file, using `git format-patch --stdout > 12345_fix_issues.patch`. **The [`--stdout` flag is recommended](http://drupal.org/node/1054616) for all patches uploaded to issues on drupal.org.**
Let's take a look at what `git format-patch` will generate. Here's the start of [a patch that was submitted against the User Relationships module](http://drupal.org/files/issues/1116854.1-use_pound_access-7.x.patch):
`From 9191b50ad0a0f28ab24616e4b4a791ce3d1536a8 Mon Sep 17 00:00:00 2001 `
This line shows the hash of the commit. This is what will show up in your local Git repository after the patch is applied. The date doesn't mean anything, and is included so that if you're sending patches directly as emails (not an attachment, but the email itself) that [email applications recognize it as a valid email](http://kerneltrap.org/mailarchive/git/2007/2/23/239466/thread).
`From: Andrew Berry `
This is the author of the patch, which will be the information set in your [Git configuration](http://drupal.org/node/1022156).
`Date: Tue, 5 Apr 2011 09:56:20 -0400 `
This is the date the commit was made. It is not the date you created the patch, and will always stay the same.
`Subject: [PATCH 1/2] #1116854: Use #access to set visibility of user relationship mailer settings. `
The subject line contains the commit message as well as the patch number and the total number of patches generated.
`--- .../user_relationship_mailer.module | 4 +++- 1 files changed, 3 insertions(+), 1 deletions(-) `
These lines provide an easy to read summary of the files changed, and how many lines were modified in each file.
```diff
diff --git a/user_relationship_mailer/user_relationship_mailer.module b/user_relationship_mailer/user_relationship_mailer.module
index d423292..b1ac03a 100644
--- a/user_relationship_mailer/user_relationship_mailer.module
+++ b/user_relationship_mailer/user_relationship_mailer.module
@@ -184,13 +184,15 @@ function user_relationship_mailer_form_user_relationships_ui_settings_alter(&$fo
[snip]
```
Finally, we have the beginning of the patch itself. This part of the file is identical to what would be generated with `git diff`.
One limitation to keep in mind when generating patches with format-patch is that they don't include the "since" commit that you used to create the patch. When submitting patches, instead of just mentioning the branch it was created against, consider posting the hash of the starting commit. That way, if conflicting commits are made on that branch, others have a starting point to work with instead of having to fix all of the conflicts right away.
## Applying format-patches with "git am"
Now that we can generate patches with `git format-patch`, how do we apply them to our local repository?
1. Check out a branch (such as 6.x-1.x, or 7.x-1.x), or a specific commit to apply the patch to.
2. Create a new branch to keep track of the commits for this issue. It is a best practice to include the issue number in your branch name. For example, `git checkout -b 1116854/use-access-mailer-settings` will create a branch named with the issue number and a description of the issue.
3. Apply the patch with `git am --3way `
The `--3way` option tells `git am` to automatically skip patches that are currently applied to your branch. This is especially useful when it takes more than one attempt to completely address an issue. With the `--3way` switch, the development workflow becomes something like the following:
1. An issue is filed with a bug or feature request.
2. Someone creates a first attempt at a patch, and uploads the results of `git format-patch`.
3. Another contributor fixes something in the first patch (such as adding documentation) and creates a new commit on top of the previous commits. They generate a second patch with `git format-patch --stdout` that contains both their commit and the previous commits created by the first author.
4. When the original author wants to apply the new commit, they download the second patch, and apply it with `git am --3way` to your issue branch. Git will automatically skip the first two commits and put the new documentation commit on top.
5. If anyone new works on the issue, they can download and apply the last patch and get the entire series of commits.
Now, you're ready to create patches, contribute them to issues on [drupal.org](http://drupal.org/), and [review patches others have created](http://drupal.org/project/issues/drupal?text=&status=8&priorities=4&categories=All&version=All&component=All). Have any tips to improve the new patch workflow? Share them with us below!
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Hacking Views, Part 1: Basic Concepts"
url: "/articles/hacking-views-part-1-basic-concepts"
type: article
date: 2009-07-28
updated: 2014-05-15
---
# Hacking Views, Part 1: Basic Concepts
# Hacking Views, Part 1: Basic Concepts
By
[ Jeff Eaton ](/about/jeff-eaton)
July 28, 2009
The 'Views' module is one of the mainstays of Drupal site building. It allows non-programmers to build highly customized listings of data that match certain criteria, then present that data in a variety of ways. A thumbnail gallery of photos, an alphabetized listing of site contributors, and a calendar display of upcoming events are all common applications of Views.
In this series of articles, we'll be taking a quick look at the architecture of the Views module and how its pieces work together; touring the different plug-in points that Views offers developers; and building a simple 'argument handler' for Views that demonstrates how the approach looks in the real world. A bit of knowledge about SQL will be useful for the article, as well as some understanding of object-oriented programming concepts like 'inheritance', but the code samples should be simple enough to tweak even if you're not a pro.
Under the hood, the pieces of a View can be divided into two different groups: the 'data' (stuff that affects the underlying database query that Drupal uses to retrieve the information for the View) and the 'presentation' (stuff that affects how that data is displayed to a user of the web site).

### Buildin' SQL
The 'data' portions of the View are more numerous: they correspond roughly to the different pieces of a SQL 'SELECT' statement. That should come as no surprise -- at Views heart is a SQL query builder that turns all of your settings into a query against Drupal's database tables.
- '**Base Tables**', like Node or Comment or User, are the underlying kind of data that you'll be displaying. They correspond to the main database table in a query, and a given view can only have one of them. A view of 'nodes and users' would result in monstrously complex SQL, and Views doesn't attempt to solve that particular problem.
- '**Fields**' don't appear in every view: they correspond to the individual database columns in a SELECT query. If you're building a table of data, for example, you'd add one Field to the view for each column that you need to display. Some complex Views fields can correspond to multiple database columns -- the 'Node teaser' field, for example, requires both the 'teaser' and 'format' database columns in order to be displayed properly.
- '**Filters**' correspond to the WHERE clauses in a SQL query. They filter down the giant pool of data in your Drupal database to a more manageable set. When building Views of nodes, for example, it's common to add a 'Published' filter to prevent unpublished or in-progress posts from appearing. Filters that restrict the results to nodes of a particular type, or nodes posted within a certain date range, are also common. Generally, whenever a Field is available for a given Base Table, a corresponding Filter is also available.
- '**Sorts**' are straightfoward -- like Filters, they generally correspond to the existing Fields for a given Base Table. They change the order in which the final results will appear, and they correspond to SQL 'ORDER BY' clauses.
- '**Arguments**' in Views are really a special case of Filters: they restrict what will appear in a given View. Hoewver, the specific value an Argument uses to filter the results can change based on the context in which the View is displayed. Adding a 'User ID' argument to a view living at http://example.com/blog, for example, would cause Views to look for a User ID after the word '/blog' in the current URL. http://example.com/blog/1 would show blog posts by User 1, http://example.com/blog/2 would show posts by User 2, and so on.
- '**Relationships**' are not used as frequently, but are still important. When two Fields (or Sorts, Filters, etc) are selected that correspond to columns in different tables, Views is smart enough to construct JOINs between the tables automatically. Sometimes, though, a query is too ambiguous for Views to *automatically* build the connecting SQL. In those situations, defining a 'Relationship' for the view makes the correct connection between the two tables explicit. One example is a view that shows the titles of two nodes that are connected to each other via a Node Reference. You can add two 'Title' fields to the View, but you'll need to add an explicit Relationship to the View to let it know *which* of the nodes each Title field should come from.

Using those six building blocks, the Views module is able to construct most SQL SELECT queries. Modules that maintain database tables can use hook\_views\_data() to announce what base tables, fields, sorts, filters, arguments, and relationships they offer, and Views will automatically add those options to the View-building UI.
### Making it Pretty
While the Data portion of Views has what's needed to generate a SQL query, that only gets us part of the way. How should the resulting data be formatted into HTML and presented to someone visiting the site? For that matter, where should the information be displayed -- a sidebar block, a dedicated page, or perhaps an RSS feed? That's where the 'presentation' side of Views comes in.
- '**Displays**' are responsible for tying Views into the flow of a Drupal site: they control when a View's SQL query will actually be executed and how the information will ultimately be woven into the site's structure. Page displays are like any other Drupal page -- they show a View's contents at a particular URL, and can have an entry in the navigation menu. Block displays display a View's contents in a sidebar block, and Feed displays expose Views data as RSS feeds. Custom Display types are provided by some third-party modules: 'Views Attach,' for example, allows a View's data to be attached to the User Profile page or the Node view page. Every View can have multiple displays -- allowing the 'Latest blog posts' data to be reused as a block, a page, an RSS feed, and so on.
- '**Styles**' control the appearance of a View, wrapping the data that's returned from the database in HTML markup and other presentation goodness. Tables, grid layouts, unordered lists, and so on are all examples of different View styles. More complex Styles can present data in Javascript-powered slideshows, raw XML, collapsing blocks, and so on.
- Some styles also rely on '**Row Styles**' -- an additional component that handles formatting each row of data returned by the SQL query. The most common Row Style -- 'Fields' -- prints out each of the fields defined in the View's data settings. It's used when building lists of links or tables of data. The 'Node' row style, in contrast, ignores the Fields that were set up on the View and simply loads the entire node object, displaying its teaser. It's often used to create variations on Drupal's 'river of news' presentation.

### Next Time... Adventures With Plugins
Whew. All that, and we've only just begun to scratch the surface! In the next installment, we're going to take a look at how Views' object-oriented architecture implements all of the pieces we've discussed, and how it provides points for customizing through its plugin architecture.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Single Sign-on across Sub-Domains in Drupal with No Extra Modules"
url: "/articles/single-signon-across-subdomains-in-drupal-with-no-extra-modules"
type: article
date: 2010-03-01
updated: 2014-05-15
---
# Single Sign-on across Sub-Domains in Drupal with No Extra Modules
# Single Sign-on across Sub-Domains in Drupal with No Extra Modules
By
[ Nate Lampton ](/about/nate-lampton)
March 1, 2010
With the multitude of single sign-on modules out there for Drupal, it's easy to miss the fact that Drupal has a *built-in* single sign on mechanism already. No modules, no configuration, just 20 easy lines of PHP in your site's settings.php file. This solution works for a lot of clients, but the set of requirements is pretty specific as to when you can use this approach. This includes:
- The sites sharing a single log-in must be on the **same domain**. For example:
- `www.example.com`
- `forums.example.com`
- `subsite.example.com`
- You must be using **MySQL**.
- Your sites must be on the **same hardware cluster** to be able to query each other's databases.
If your site fits within those requirements, you're on your way to simple, efficient, and easy Single Sign-on!
The concept for this single sign-on approach is based around Drupal's ability to prefix database tables. As you may know, you can run multiple Drupal sites on the same MySQL database. However, most sites are not configured this way, each site is given it's own dedicated database. Drupal's table prefixing can be combined with MySQL's ability to query across databases to make a simple "shared table" across multiple sites. Then you just need to set a cookie domain so that the two sites share session information and you're done!
If that sounds a little heady, let's just look at the code. Open the settings.php file (usually located in `sites/default/settings.php`) for your two sites. These sites can be in entirely different Drupal installs, or they can be under the same Drupal installation if you're using multisite capabilities.
## Master Site Configuration
In your "Master" site (the one that the user information will be stored in), you don't need to make hardly any changes. There should be a line similar to this in your settings.php file:
```
$db_url = 'mysql://user:pass@localhost/master_database';
$db_prefix = '';
```
You don't need to change this at all. The master site stores all the user names, passwords, and sessions. However there is another line further down in settings.php that let's you specify a cookie domain. This needs to be un-commented (remove the leading # sign) and set to the name of your domain. **Make sure you include the leading period before the domain**.
```
$cookie_domain = '.example.com';
```
## Slave Site Configuration
The slave site will connect to the Master site's database for certain tables, specifically the ones that include user information. This makes it so that user's simply "log in" using the information from the master site's database.
Here's where we use MySQL's database name prefixing, where all queries to the "slave" database are simply prefixed with the name of the slave database. Same goes for the master.
```
$db_url = 'mysql://user:pass@localhost/slave_database';
$db_prefix = array(
'default' => 'name_of_slave_database.',
'users' => 'name_of_master_database.',
'sessions' => 'name_of_master_database.',
'role' => 'name_of_master_database.',
'authmap' => 'name_of_master_database.',
);
```
Then we configure the site to use the same "shared" cookie domain as the master.
```
$cookie_domain = '.example.com';
```
The method for a user logging in does not change, the user can just use the exact same user name and password on both sites, logging into one will immediately log you into all of them. Users can even change their passwords and have it work across sites, and you can still use Views to build listings of users without any changes at all. Hurray for shared cookies.
## Taking it further
Just by adding a few lines of code to your settings.php file on your different sites can make shared login a piece of cake. Note that this makes it so that other user-related information may need to also be shared. Specifically if you're using Profile module, you may also want to share the "profile\_fields" and the "profile\_values" tables.
One caveat you might encounter is that your user picture URLs are all relative to the Master site's files directory. To solve this problem, you can create a symlink from "files/pictures" on your slave sites to point to the master site's "files/pictures" directory.
```
ln -s /usr/home/example.com/sites/default/files/pictures /usr/home/subsite.example.com/sites/default/files/pictures
```
Again, this approach will only work across subdomains of the same domain and if the sites are hosted on the same set of servers. The security built into browsers prevents domains from reading the cookies of completely different domains, which is when you'll need to look into other solutions such as [OpenID Provider](http://drupal.org/project/openid_provider), [Single Sign-on](http://drupal.org/project/sso), or [Bakery](http://drupal.org/project/bakery).
This article was sponsored by our friends at [Dooce](https://dooce.com/)! You can see this in action between their shared sites:
- http://www.dooce.com
- http://community.dooce.com
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Hiding content from Drupal's search system"
url: "/articles/hiding-content-from-drupals-search-system"
type: article
date: 2007-11-26
updated: 2014-05-15
---
# Hiding content from Drupal's search system
# Hiding content from Drupal's search system
By
[ Jeff Eaton ](/about/jeff-eaton)
November 26, 2007
Drupal offers a variety of ways to integrate with the built-in search system, from connecting with third-party search systems to adding information to standard node content. In addition, Drupal's search system respects the access permissions on each piece of content -- users who can't access a particular node will never see it in the results of their searches.
What happens, though, if you want users to be able to access some content (user bio nodes, for example) if they navigate to it directly, but don't want that content to appear in search results? Drupal doesn't offer any way to do that by default, but your custom module can use the same behind-the-scenes hooks used by the security system to control exactly what search results are presented to users.
The magic happens in hook\_db\_rewrite\_sql(). Whenever Drupal code calls the db\_rewrite\_sql() function to pull information from the database, other modules can use hook\_db\_rewrite\_sql() to intercept the SQL call and add additional filters to the query. That's how modules like Organic Groups restrict access to content based on group membership: when queries pull information from the node table, it compares the groups the node is associated with to the groups the current user belongs to.
Intercepting the queries used by the search system takes a bit of extra work, though. We'll take a look at some example code and see how the funky bits work.
```
function your_module_db_rewrite_sql($query, $primary_table, $primary_field, $args) {
if ($query == '' && $primary_table == 'n' && $primary_field == 'nid' && empty($args)) {
$excluded_types = variable_get('your_module_types', array());
if (!empty($excluded_types)) {
$where = " n.type NOT IN ('". join("','", $excluded_types) ."') ";
return array('where' => $where);
}
}
}
```
Modules that implement hook\_db\_rewrite\_sql() receive a couple important pieces of information about each query. The most important is the 'primary table' parameter -- you don't want to add a SQL WHERE filter intended for nodes when the primary table is 'user', for example. Due to some curious code inside the node module, however, the query that's passed in is treated as *empty*. While that's a pretty big violation of Drupal's own coding standards, it makes it easy to intercept *just* the node search queries.
So, we first check to see whether the incoming query is empty, the table is 'n', and the primary field is 'nid'. If those conditions are matched, the rest is easy: we grab a list of node types that we want to hide from the search results and build a WHERE condition that hides them. That's it!
To make things a bit cleaner, we can add some configuration options.
```
function your_module_search($op = 'search') {
if ('admin' == $op) {
$form = array();
$form['your_module_types'] = array(
'#type' => 'select',
'#multiple' => TRUE,
'#title' => t('Exclude Node Types'),
'#default_value' => variable_get('your_module_types', array()),
'#options' => node_get_types('names'),
'#size' => 9,
'#description' => t('Node types to exclude from search results.'),
);
return $form;
}
}
function your_module_form_alter($form_id, &$form) {
if ('search_form' == $form_id) {
$excluded_types = variable_get('your_module_types', array());
$types = array_map('check_plain', node_get_types('names'));
foreach($excluded_types as $excluded_type) {
unset($types[$excluded_type]);
}
$form['advanced']['type']['#options'] = $types;
}
}
```
What do the two code snippets above do? The first one -- hook\_search() -- adds an extra form field to the search administrative settings page. It allows site admins to choose which kinds of content should be hidden. The original snippet we used to do the SQL rewrite will use that list of types to build its WHERE query.
The second snippet alters the advanced form that users see when they search for content on your site. Normally, it shows a list of all the site's content types and lets users choose what they want to see. This implementation of hook\_form\_alter(), though, removes options from that list if the admin has listed the content types as hidden. That ensures that users will never see options that are impossible to use.
That's it! The same technique can be used to hide content from specific users, content posted on Wednesdays, or any other criteria that's needed.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Show me the money!"
url: "/articles/show-me-the-money"
type: article
date: 2010-05-18
updated: 2016-04-07
---
# Show me the money!
# Show me the money!
A Lullabot Business Case Study
By
[ Liza Kindred ](/about/liza-kindred)
May 18, 2010
Last month, I had the opportunity to speak at [DrupalCon San Francisco](http://sf2010.drupal.org/). I had a great time speaking. I did a case study of Lullabot itself, in which I talked some about how the company is structured, some of our core beliefs, and about my own business ideas and strategies. (You can watch the slides and hear the audio [here](http://sf2010.drupal.org/conference/sessions/lullabot-case-study), or access the slides [here](https://www.slideshare.net/slideshow/lullabot-case-study/4141782).)
I'm a huge advocate of giving things away (creating value), and then using smart business models to capture some of that value. Part of what I talked about during this session was how to determine one's value as a Drupal shop. When I first came to Lullabot (about a month after Matt and Jeff founded the company), the company was swamped with work requests. Raising our rates was a great filtering mechanism for us, and also helped "buy" the free time that our awesome team members need to do things like [write books](http://usingdrupal.com/), co-maintain an entire release of Drupal, and maintain four billion modules. But for us, it was a total guessing game. We basically raised our rates until we reached the point where we started meeting some pricing resistance. We've done careful tweaking of our rates over time, but we're at a point (after 4 1/2 years in business) that we're confident in our rates and very confident in the value that we provide for those rates.
However, I'd like to make it easier for you. Trial and error can be messy, time-consuming and expensive. I sometimes do freelance consulting (I love helping to build something out of nothing) and I recently worked with the team at [Rapid Waters Development](http://rapidwatersdev.com/) to get their business set up for success. (Lullabot has since [acquired them](https://www.lullabot.com/articles/rapid-waters-team-joins-lullabot) - success!) We spent a lot of time figuring out pricing models. I really wished at that time that I could get my hands on some concrete information about what a variety of Drupal shops are charging... and so now I've gone ahead and gathered it.
This is a super small sample, and it is entirely unscientific. I asked 10 people to take the survey; 9 did. (Go open source mentality!) I hand-chose who I asked - I wanted the answers to come from established, credible, full time Drupal shops. I'm very grateful to those business leaders who filled out the survey. I decided against opening up the survey for anyone to take because I thought the results would get diluted if there were one-person shops or huge outsourcing companies whose Drupal services may only be a portion of their business. (If you guys think a larger survey would be valuable, let me know. I'm totally open to doing one of those, too.)
So, what did I learn?
That we're all over the place. Lullabot is a boutique shop - we charge a lot of money, we kick a lot of ass, and we give a ton back. Other shops work more with NGO's and non-profits, and by necessity need to charge different rates. Some shops may want to compete on price, and thus would want to fall more to the commodity level end of pricing. There's no right or wrong rates to charge. However, I believe that we should all know where we are on the pricing scale, so that we can plan and market ourselves accordingly.
Whatever you do with this information, I encourage you to ensure that you're adding as much value as possible, and taking good care of your people. Happy clients and comfortable employees make for more successful businesses, and they are a great way for us business peeps to contribute back to the Drupal eco-system.
The pricing only slides are [available as a PDF](https://www.lullabot.com/sites/lullabot.com/files/DrupalPricingSlides.pdf); I hope that you find them helpful!
Published in:
- [ Business ](/topics/business)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Using IRC in your browser"
url: "/articles/using-irc-in-your-browser"
type: article
date: 2009-01-13
updated: 2014-05-15
---
# Using IRC in your browser
# Using IRC in your browser
By
[ Addison Berry ](/about/addison-berry)
January 13, 2009
**Note: this video is no longer available because it is out of date. You can find a newer video [Using IRC (Internet Relay Chat)](https://drupalize.me/tutorial/using-irc-internet-relay-chat) on [Drupalize.Me](https://drupalize.me/).**
Using IRC (Internet Relay Chat) is a great way to have real-time conversations online. For many people who aren't familiar with it though, just getting into an IRC channel can be enough to deter them. This video will look at two quick and easy browser-based ways to access IRC, Mibbit.com and the ChatZilla Firefox extension. We'll look at how to get into the Drupal support channel (#drupal-support) and how to get both clients to remember the channels you like to use. There are more videos coming that cover client applications that you can install on your computer and general IRC usage, etiquette and tips.
Some handy links:
[Drupal IRC channels](http://drupal.org/irc)
[Mibbit.com](https://mibbit.com/)
[Firefox ChatZilla Add-on](https://addons.mozilla.org/en-US/firefox/addon/16)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Slow Queries? Check the Cardinality of Your MySQL Indexes"
url: "/articles/slow-queries-check-the-cardinality-of-your-mysql-indexes"
type: article
date: 2011-07-13
updated: 2021-01-12
---
# Slow Queries? Check the Cardinality of Your MySQL Indexes
# Slow Queries? Check the Cardinality of Your MySQL Indexes
More indexes doesn't always mean better performance
By
[ Andrew Berry ](/about/andrew-berry)
July 13, 2011
This week I've been heavily involved with optimizing the performance of the social networking features of a client site. The site in question uses the [User Relationships](http://drupal.org/project/user_relationships) module, a module that allows site members to connect with each other and share content within their circle of friends. The User Relationships module installs two key tables for storing relationships:
- The `{user_relationship_types}` table contains configuration data for each relationship type. Supporting multiple relationship types allows users to have different kinds of relationships, such as those that are one-way (like Twitter), or require approval (like Facebook) on the same site.
- The `{user_relationships}` table contains the actual relationships between users on the site. This table can be quite large, as it contains one row for every user relationship on the site.
How do these tables and their indexes relate to performance? **[Cardinality](https://en.wikipedia.org/wiki/Cardinality_(SQL_statements))**. In this context, cardinality refers to the number of unique values in a column. For example, primary keys such as a node ID or user ID would have very high cardinality. The mail column in the `{users}` table would also have high cardinality, but not as much as the user name or user ID as the mail column is not guaranteed to be unique at the database layer. The status column in the `{users}` table would have low cardinality, as there are only two possible values for it (zero for blocked and one for active).
### What does it mean?
How does this relate to the User Relationships module? Let's take a look at the indexes it creates for the `{user_relationship}` table from it's [hook\_schema()](http://api.drupal.org/api/drupal/developer--hooks--install.php/function/hook_schema/6) [implementation](https://drupalcode.org/project/user_relationships.git/blob/refs/heads/7.x-1.x:/user_relationships.install):
```php
$schema['user_relationships'] = array(
'fields' => array(
// snipped fields
),
'primary key' => array('requester_id', 'requestee_id', 'rtid'),
'indexes' => array(
'requester_id' => array('requester_id'),
'requestee_id' => array('requestee_id'),
'rtid' => array('rtid'),
'rid' => array('rid'),
),
);
```
This code means that when the module is installed:
1. A primary key is created of the composite of the user IDs of the users in the relationship along with the type of relationship they have.
2. An index is created for the requester\_id to allow searching for the outgoing relationships of a user.
3. An index is created for the requestee\_id to allow searching for the incoming relationships of a user.
4. An index is created for the relationship type.
5. Finally, an additional index is created for the relationship ID, which is an auto-incremented value but not the primary key.
Let's example the performance of a common query - finding the number of relationships a user has:
```
mysql> SET profiling = 1;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT COUNT(DISTINCT rid) AS count FROM user_relationships ur INNER JOIN user_relationship_types urt USING ( rtid ) WHERE (ur.requester_id = 1 OR ((ur.approved <> 1 OR ur.rtid IN (1)) AND ur.requestee_id = 1)) AND ur.approved = 1 AND ur.rtid = 1;
*************************** 1. row ***************************
count: 0
1 row in set (0.99 sec)
mysql> SHOW profiles;
*************************** 1. row ***************************
Query_ID: 1
Duration: 0.98712800
Query: SELECT COUNT(DISTINCT rid) AS count FROM user_relationships ur INNER JOIN user_relationship_types urt USING ( rtid ) WHERE (ur.requester_id = 1 OR ((ur.approved <> 1 OR ur.rtid IN (1)) AND ur.requestee_id = 1)) AND ur.approved = 1 AND ur.rtid = 1
1 row in set (0.00 sec)
mysql> SET profiling = 0;
Query OK, 0 rows affected (0.00 sec)
```
Nearly a second! EXPLAIN says:
```
mysql> EXPLAIN SELECT COUNT(DISTINCT rid) AS count FROM user_relationships ur INNER JOIN user_relationship_types urt USING ( rtid ) WHERE (ur.requester_id = 1 OR ((ur.approved <> 1 OR ur.rtid IN (1)) AND ur.requestee_id = 1)) AND ur.approved = 1 AND ur.rtid = 1;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: urt
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: const
rows: 1
Extra: Using index
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: ur
type: ref
possible_keys: PRIMARY,rtid,requester_id,requestee_id
key: rtid
key_len: 4
ref: const
rows: 109924
Extra: Using where
```
The good news is, neither temporary tables or filesorts are used for the query. As well, the second table is able to use a [ref](https://dev.mysql.com/doc/refman/5.1/en/explain-output.html) join type, which is reasonably fast. So why is the query taking so long to run? The key is in the key *rtid*:
```
mysql> SELECT COUNT(1) FROM user_relationships;
*************************** 1. row ***************************
COUNT(1): 219406
1 row in set (0.15 sec)
mysql> SHOW INDEXES FROM user_relationships;
*************************** 5. row ***************************
Table: user_relationships
Non_unique: 1
Key_name: rtid
Seq_in_index: 1
Column_name: rtid
Collation: A
Cardinality: 6
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
7 rows in set (0.02 sec)
mysql> SELECT DISTINCT rtid FROM user_relationships;
*************************** 1. row ***************************
rtid: 1
1 row in set (0.00 sec)
```
The index indicates are cardinality of 7 (which is an estimate based on the number of rows in the table) for the total of 219406 rows. Yet, rtid (the relationship type ID) is set to 1 for every single relationship. This means that the rtid index is not only useless, but actively slowing down every query on this table! Time to bite the bullet and drop the index:
```
mysql> ALTER TABLE user_relationships DROP INDEX rtid;
Query OK, 0 rows affected (0.09 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT COUNT(DISTINCT rid) AS count FROM user_relationships ur INNER JOIN user_relationship_types urt USING ( rtid ) WHERE (ur.requester_id = 1 OR ((ur.approved <> 1 OR ur.rtid IN (1)) AND ur.requestee_id = 1)) AND ur.approved = 1 AND ur.rtid = 1;
*************************** 1. row ***************************
count: 0
1 row in set (0.48 sec)
mysql> SHOW profiles;
*************************** 1. row ***************************
Query_ID: 1
Duration: 0.49973000
Query: SELECT COUNT(DISTINCT rid) AS count FROM user_relationships ur INNER JOIN user_relationship_types urt USING ( rtid ) WHERE (ur.requester_id = 1 OR ((ur.approved <> 1 OR ur.rtid IN (1)) AND ur.requestee_id = 1)) AND ur.approved = 1 AND ur.rtid = 1
1 row in set (0.01 sec)
```
Great! The query time has been cut nearly in half. It still seems a little slow. Perhaps it's because the primary key still contains rtid?
```
mysql> ALTER TABLE user_relationships DROP PRIMARY KEY;
Query OK, 219406 rows affected (15.23 sec)
Records: 219406 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE user_relationships ADD INDEX (requester_id, requestee_id);
Query OK, 0 rows affected (2.55 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT COUNT(DISTINCT rid) AS count FROM user_relationships ur INNER JOIN user_relationship_types urt USING ( rtid ) WHERE (ur.requester_id = 1 OR ((ur.approved <> 1 OR ur.rtid IN (1)) AND ur.requestee_id = 1)) AND ur.approved = 1 AND ur.rtid = 1;
*************************** 1. row ***************************
count: 0
1 row in set (0.01 sec)
mysql> SHOW PROFILES;
*************************** 1. row ***************************
Query_ID: 1
Duration: 0.00850100
Query: SELECT COUNT(DISTINCT rid) AS count FROM user_relationships ur INNER JOIN user_relationship_types urt USING ( rtid ) WHERE (ur.requester_id = 1 OR ((ur.approved <> 1 OR ur.rtid IN (1)) AND ur.requestee_id = 1)) AND ur.approved = 1 AND ur.rtid = 1
1 row in set (0.00 sec)
```
### The takeaway
With some careful analysis of our indexes and data, we've sped up a server-killing query by nearly 100x. Looking for other ways to speed up your Drupal site? Check out other articles on [speeding up Views with slave databases](https://www.lullabot.com/articles/querying-a-slave-database-with-views) and [absorbing heavy traffic with Varnish](https://www.lullabot.com/articles/configuring-varnish-for-highavailability-with-multiple-web-servers), watch Lullabot's [Performance and Scalability DVD,](http://store.lullabot.com/products/drupal-performance-scalability) or [have us check out your slow site for hands-on optimization.](https://www.lullabot.com/what-we-do#strategy)
Published in:
- [ Performance and Scalability ](/topics/performance-and-scalability)
- [ System Administration ](/topics/system-administration)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Introducing Videola"
url: "/articles/introducing-videola"
type: article
date: 2011-06-14
updated: 2020-01-22
---
# Introducing Videola
# Introducing Videola
Open Source IPTV with Ecommerce: alpha ready for testing
By
[ Blake Hall ](/about/blake-hall)
June 14, 2011
Lullabot is happy to announce the alpha release of [Videola](http://videola.tv), the Drupal-based IPTV platform that weâve been building for [Drupalize.Me](https://drupalize.me/). Weâve had a lot of inquiries about this enterprise-level video management system and video delivery platform, so weâre excited to finally make Videola available. That said, this is the alpha version, and we invite you to test it, submit issues, and help contribute to the next version. We're currently hosting the [Videola codebase on Github](https://github.com/Videola/videola). For those of you developers who can't wait to dive in, the easiest way to get started (assuming you have relatively recent versions of drush, drush make and git installed) is:
```
drush make https://raw.github.com/Videola/videola/master/videola_starter.make videola
```
This will download Drupal, the Videola installation profile, and all the other required modules to a videola directory on your machine.
## Videola in a Nutshell
While planning Drupalize.Me, we knew we wanted to build a flexible, general use video platform that we could turn into a distribution. As a baseline we wanted it to be capable of both e-commerce and IPTV allowing paid-access or free-access video websites which can serve video to the desktop, mobile, or television-based devices. Our goal is that using the distribution, you could create your own Netflix On-Demand style (subscription), Hulu style (ad supported), or Blockbuster / Amazon style (rental) streaming video websites with your own video content. Videola is currently oriented toward curated, editorial, some-to-many video sites â as opposed to many-to-many user generated content sites such as YouTube. We're also building IPTV layers in the form of apps for mobile and television-based devices such as the Roku player. Here's a rundown of where we are now:
#### Videola does:
- Ecommerce setup for recurring subscription payments
- ImageCache presets to resize your video stills
- Video node types
- Video views and organization - by date (recent first), by popularity, by category
- Per-user Video queue
- Ejector Seat and Session Limit modules can be used to prevent account sharing
#### Technologies we're using:
- Drupal 6 - Yes, we will eventually move to Drupal 7. You're welcome to help with this effort.
- Ubercart
- Local video hosting - Videola supports pluggable video backends, so support for Ooyala, Brightcove, etc is possible (with more examples forthcoming)
#### Videola doesn't (yet) do:
- CDN integration
- Pay-per-view access
- On-demand purchasing
- Support in-stream advertising and branding bumpers
## How We Created Videola
To get Videola off the ground, three members of the Drupalize.Me team: [Joe](https://www.lullabot.com/who-we-are/joe-shindelar), [Michelle](https://www.lullabot.com/about) and [myself](https://www.lullabot.com/who-we-are/blake-hall), got together with [Matt](https://www.lullabot.com/about/matt-westgate) and [Jeff](https://www.lullabot.com/about/jeff-robbins) at the [Lullabot Activity Centerâ¢](https://www.flickr.com/photos/jjeff/sets/72157626485383892/) in Providence for a three day sprint. We started by coming up with a list of basic features we needed to include in our first alpha release and setting up a [drush make](https://github.com/Videola/videola/blob/master/videola.make) file.

Features allowed us to export quite a bit of the basic configuration needed for Videola to code. The Videola Core feature contains the Imagecache presets used for video stills, a global context, and the popular videos view. This feature also relies on two new custom modules (already available on [github](https://github.com/Videola/), soon to be released on drupal.org): Ejector Seat and Session Limit. In combination, they are used to ensure that each user on the site is only allowed to have one active session at any given time and automatically logs the user out if a new session is started from a different browser/location. The Videola Video feature provides the most important content type for the site. In this alpha release, the included video content type contains fields for stills, video length, chapter markers -- and most important -- a file upload for the video file itself. Videola is designed so that this feature is swappable, provided the replacement feature implements a content type with the machine name "video." Future versions of Videola may include features that support streaming providers such as Ooyala or Brightcove. This module also provides a couple of hooks so that the total number of hours or minutes of video on the site can be used as an input filter tag, and another hook to alter jwplayer configuration. Three other features provide the bulk of the video display and organization. The Videola Browser feature captures the taxonomy used to categorize videos and the views used to display them. The Videola Dashboard feature provides both anonymous and authenticated front page views. The Videola Queue feature provides a flag users can use to place videos in their queue, which is prominently displayed on the dashboard.

The one major piece of Videola that couldn't be nicely captured using Features is Ubercart configuration. The Videola Ubercart feature is equal parts Strongarm settings and install hook setup code. As part of the installation process for the Ubercart feature, a subscriber role is set up, a subscription product class is created along with a Membership node, its attributes and options.

An extra configuration screen during the install process allows a custom price to be set for monthly, bi-annual, and annual subscriptions.

## Your Turn
We've tried to provide solid documentation for what's what in Videola in the [README](https://github.com/Videola/videola/blob/master/README.md) and [INSTALL](https://github.com/Videola/videola/blob/master/INSTALL.txt) files that come with the profile. This includes directions for getting started with Drush make, or the list of modules you'll need to get started. For now, weâll be using the [issue tracker within GitHub](https://github.com/Videola/videola/issues) to manage issues and handle pull requests. If you've got questions, bugs, or you'd like to contribute to the project with fixes, suggestions, and code for new features, we'd love your involvement! Just jump in with [issues, fixes, and ideas](https://github.com/Videola/videola/issues), [documentation](https://github.com/Videola/videola/wiki). We'd look forward to hearing from you. For more information, check out http://videola.tv/ and let us know what you think!
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal data imports with Migrate and Table Wizard"
url: "/articles/drupal-data-imports-with-migrate-and-table-wizard"
type: article
date: 2009-10-25
updated: 2016-04-07
---
# Drupal data imports with Migrate and Table Wizard
# Drupal data imports with Migrate and Table Wizard
Importing data into Drupal in three easy steps!
By
[ Angie Byron ](/about/angie-byron)
October 25, 2009
If you haven't yet heard the buzz that's been building since [Drupalcon DC](http://dc2009.drupalcon.org/session/migration-not-just-birds) in March about the fabulous [Migrate](http://drupal.org/project/migrate) and [Table Wizard](http://drupal.org/project/tw) modules, written by the smarties at [Cyrve](http://cyrve.com/), then here are a few questions for you:
- Does the phrase "data migration" conjure up images of being repeatedly stabbed in the knee with a rusty fork? (which would of course be a far more enjoyable experience!)
- Have you spent countless hours sifting through record after record of your clients' legacy data, pining for an easy way to catalog it all so you (and they!) can both tell what's *really* important to pull over?
- Do you lose years off of your life every time you attempt a bulk migration, hoping for the best that there are no horrific bugs that need to be sorted out afterwards that you didn't catch in testing?
- Have you had it up to here with having to go and find separate modules, each with totally different interfaces and levels of bugginess, for importing nodes, taxonomy, users, and so on?
If you answered yes to any of these questions, then the Migrate and Table Wizard modules are for you! Read on to learn how they work and try a "hands on" example.
(Note: This article is written against the current -dev releases of both Table Wizard and Migrate, which will eventually become Migrate 6.x-1.0 and Table Wizard 6.x-1.2. Final screen shots may vary.)
## Overview: Importing data into Drupal in three easy steps!
Yes, really! Only three of them! Are you salivating all over yourself, yet? ;)
### Step 1: Get your stuff into a MySQL or PostgreSQL\* database.
The first step is getting whatever external data you have into MySQL or PostgreSQL database tables (or hopefully, just about *any* database type when these modules are ported to Drupal 7). A quick web search for "x to SQL converter" or similar will reveal lots of tools to assist with this step. And in fact, Table Wizard module itself comes with an optional module called "Table Wizard Import Delimited Files" which can handle things like comma-separated values (CSV) files for you.
These incoming database tables can either be added Drupal's database (mind your table prefixes if you go this route -- 'users' is a popular table name! ;)), or in an external database by adjusting your $db\_url in settings.php as described in the [external tables section of the Table Wizard documentation](http://drupal.org/node/452374#external-tables).
There are a few important caveats here:
1. Due to limitations in the pre-Drupal 7 database abstraction layer, the destination database type *must* be the same as Drupal's. In other words, if your Drupal site is installed in a MySQL database, your stuff needs to be imported into a MySQL database, too.
2. For more advanced types of migrations, such as importing hierarchical data (we'll talk about this in the next article), there is currently a limitation in Table Wizard module where these database tables must be within Drupal's database. This is being discussed in Drupal.org issue [\#610128: Can't add external and internal tables' columns to the same view](http://drupal.org/node/610128).
3. PostgreSQL support may be iffy. It needs testing, and is a blocker to a 1.0 release of Migrate module. If you are PostgreSQL-inclined, please help out at Drupal.org issue [\#392398: PostgreSQL support](http://drupal.org/node/392398)
### Step 2: Use Table Wizard module to expose database tables as Views.
Once the data is in database tables, Table Wizard module comes in. It Views-enables (exposes to [Views module](http://drupal.org/project/views)) any table's data. This carries with it a number of immediately awesome side-effects:
- You can do anything to this incoming data that you can do to a view: sort it, filter it, add or remove fields, alter the fields' output...
- You can form relationships between two different tables and create Views which combine the results from multiple data sets.
- You can even use Table Wizard as a general tool for Views-enabling your own custom modules' data!
But more than just providing this views awesomeness, Table Wizard module also provides a methodology and process around doing data imports, through its incredibly helpful "analyze" screen. In addition to displaying a wide variety of incredibly helpful information about your table's data, including recommendations on data types and field lengths, it also provides a "Comments" text area for each column. Through the use of comments, you as the site builder can work directly with your client (who knows their data best) to collaborate on the site's migration strategy: mark unimportant columns or tables as "Ignored," note the important data transformation tasks that need to occur during the import on certain columns, document any weird tweakiness that happened during practice runs, and so on. This collaborative workflow provides a fully transparent view into the site's migration process, which does wonders for the comfort level of both parties during exceptionally large imports.
### Step 3: Use Migrate module to map a View of external data to native Drupal data.
Next, we turn to Migrate module. In Migrate module, you can define "content sets", which are essentially mappings between fields coming from Views, and fields attached to internal Drupal data types. For example, you can map the "article\_title" field in an external "articles" table to the "Node: Title" field of an "Article" content type in Drupal. Migrate natively supports importing nodes, taxonomy terms, users, comments, profile data, and even has some support for contributed modules such as FileField and Content Profile. If these data types aren't enough for you, there are also hooks for defining your own.
Migrate module also has a variety of options for testing the imports to ensure they're solid before you pull the trigger "for real," and even has support for [Drush](http://drupal.org/project/drush) integration, so you can perform massive imports from the command line instead of the browser. There are also hooks for performing actions or otherwise massaging the incoming data before, after, and during a migration. *Sweet!*
Ok, enough overview. Let's see 'em in action!
## Migrate and Table Wizard hands-on example
Here is a simple hands-on example to show how to import the hypothetical products from a legacy database into native Drupal nodes. Through the process, you'll be exposed to most of the Migrate and Table Wizard module administrative screens.
### Preliminary set up
Before you can go through the example, you first need to do some basic steps.
1. Download the following modules and put them in your Drupal 6 site's sites/all/modules directory:
- [Table Wizard](http://drupal.org/project/tw)
- [Migrate](http://drupal.org/project/migrate)
- [Schema](http://drupal.org/project/schema)
- [Views](http://drupal.org/project/views)
- [CCK](http://drupal.org/project/cck) (to play along with the example)
2. Enable the modules from *Administer >> Site building >> Modules* (admin/build/modules):
- "CCK" package: Content, Content Copy, Number, Text
- "Database" package: Schema, Table Wizard
- "Development" package: Migrate
- "Views" package: Views, Views UI
3. Now, we need to import our legacy content into our database. Download [legacy\_products.sql.txt](https://www.lullabot.com/files/legacy_products.sql_.txt) and import it into your Drupal site's database using a tool like PHPMyAdmin. (Note: This file is a dump from MySQL; it might need some massaging for PostgreSQL.)
4. Finally, we must create a content type to hold the incoming data. Download [cck\_product.txt](https://www.lullabot.com/files/cck_product.txt), then go to *Administer >> Content management >> Content types >> Import* (admin/content/types/import). Copy and paste the contents of the file and click "Import" to create a "Product" content type in your Drupal site to hold the incoming data.
### Preparing data for import with Table Wizard module
With our legacy data safely imported into our Drupal database, we can now begin the second step: using Table Wizard to expose a view of our incoming data.
1. Head to *Administer >> Content management >> Table wizard* (admin/content/tw) and expand the "Add tables" fieldset.
2. Select the "legacy\_products" table from the list. The rest of the settings can be left at their defaults. Click the "Add tables" button.

A list of possible tables that can be made Views-enabled by Table Wizard module.
3. After a brief progress bar while the table's contents are analyzed, you arrive back at the main Table Wizard screen. Here, you'll find two main columns: "Table name", which allows you to configure options around the table's structure, and "View name", which provides a listing of the table's contents as a view. You'll also see a count of the number of records within the table.

Table Wizard's interface for added tables.
4. Begin by clicking "legacy\_products" under the "Table name" column to bring up the "Analysis" screen, which provides overview information about the data coming in.
[ ](https://www.lullabot.com/files/tw-analyze.png)

Table Wizard's table analysis screen.
In our sample data, there is one extraneous column that we don't care about: internal\_flag. This is some kind of holdover from the legacy data, but it's not something we need to import into Drupal. Check its **Ignore** flag, and submit the form. Now the field won't be visible in the generated view, and we won't see it later when we go to do our data migration.
5. Now, either by clicking "View table contents" or returning back to the main Table Wizard screen and clicking on the "legacy\_products" link under the "View name" column, you can see the actual contents of the table, minus the "internal\_flag" column we ignored in the previous step. This is just a straight-up Views module view, and can be edited just as any normal view you create.

The View generated by Table Wizard module of legacy product data.
### Importing data into Drupal with Migrate module
Once your view is set up, it's time to migrate that data! This section will discuss setting up a "content set" in Migrate module to map the view to internal Drupal data structures, and how to actually pull the trigger on the migration itself.
1. Head over to *Administer >> Content management >> Migrate >> Content sets* (admin/content/migrate/content\_sets). Here, you can see a list of native Drupal types: node, comment, taxonomy, user, etc. as well as source views from which to import content. Source views that start with "tw" come from Table Wizard module. Fill in the following settings to map our "legacy\_products" view to our "Product" content type: Description of the content set Legacy product import Destination Node: Product Source view from which to import content tw: legacy\_products (legacy\_products)

Defining a content set in Migrate module.
2. If you scroll down on the next screen, you'll see a series of fields for mapping incoming data from the "Source field" (coming from the view) to a "Destination field" within Drupal. There are also text fields for adding a default value if one is not specified. We can use this to make all of our imported content show up as authored by the super user account (user 1), as opposed to anonymous.
Set up the following mappings. The rest can be left at their default values. Source field Default value Destination field <none> 1 Node: Authored by (uid) name Node: Title description Node: Body description Node: Teaser price CCK: Price value sku CCK: SKU Number value

Setting up the field mapping for the incoming product data
3. Once the mapping is done to your liking, save the form and head to *Administer >> Content management >> Migrate >> Dashboard* (admin/content/migrate/dashboard). Here, you can initiate the migration process, and also track statistics about the migration such as its progress (how many items imported vs. left unimported, and when the last import attempt was made).

Migrate module's content import dashboard Check the "Import" checkbox, and click Submit. After a brief pause, you should receive notice that 4 items were imported, and the number of rows in the "Unimported" column should now read 0:

Post-migration results shown in the Migrate dashboard.
4. Now, it's time to view the fruits of our labour! Head to *Administer >> Content management >> Content* (admin/content/node). You should now see four freshly-imported product nodes! Go ahead and click on them to spot-check the results.

Content administration screen showing newly imported content.
You may have noticed one important detail about this migration: the imported nodes are all set to unpublished, so no non-administrative users can see them! This is actually a good thing; you don't want content that was accidentally imported incorrectly to immediately appear to your site's end-users, accidentally get indexed by search engines, etc.
5. Once we've checked to make sure that our content imported properly, it's time to do the migration "for real." Return to the Migration module dashboard at *Administer >> Content management >> Migrate >> Dashboard* (admin/content/migrate/dashboard). By checking the "Clear" button and clicking "Submit," Migrate module will *delete* all of the records it previously imported, taking us back to a clean slate where we can start again. This is an awesome feature, as it gives you complete freedom to test and re-test (and re-test again....) any migration jobs.
6. Let's make one last tweak to our content set to set the default published state of incoming nodes. Head back to *Administer >> Content management >> Migrate >> Content sets* (admin/content/migrate/content\_sets) and click on "Legacy product import" to return to the field mapping screen. Leave the values as-is, but next to "Node: Published" enter a "Default value" of 1.
7. Time for the final migration! Return to the Migration module dashboard at *Administer >> Content management >> Migrate >> Dashboard* (admin/content/migrate/dashboard) and once again check "Import" and click "Submit." Then head back to *Administer >> Content management >> Content* (admin/content/node) to view the results. Voila! Freshly imported product nodes, visible to our site's end users.

A sample node from the incoming content.
## Summary
This article introduced the Migrate and Table Wizard modules, and provided an overview of the content import method they utilize: first getting data into MySQL/PostgreSQL tables, then exposing those database tables to Views, and finally mapping the views to internal Drupal data types and triggering the migration process. We then walked through an example import of legacy product data into Drupal nodes with attached CCK fields.
Most "real world" data import jobs require a bit more tweaking, and a follow-up article will explain how to import more advanced data sets, such as hierarchical data and multi-valued fields. However, starting with some basics allows us to step through most of the Migrate and Table Wizard screens and learn how they work. Hopefully this example has helped demonstrate the power of Migrate and Table Wizard modules, and you'll be able to add it as a critical tool for your next data import job.
Happy migrating! :)
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Content Syndication Using Services and Feeds"
url: "/articles/content-syndication-using-services-and-feeds"
type: article
date: 2012-10-11
updated: 2014-05-15
---
# Content Syndication Using Services and Feeds
# Content Syndication Using Services and Feeds
Some "Highlights" of an Approach to Content Syndication
By
[ Karen Stevenson ](/about/karen-stevenson)
October 11, 2012
I have run into a number of clients that want to create a system to syndicate content to multiple sites. There are several ways to set a system like that up, perhaps using the Migrate or Deploy modules. But itâs also possible to do this using Services and Views on the content source and Feeds on the consuming sites.
The development team at [Highlights for Children](https://shop.highlights.com) is diving into Drupal in a big way, and Lullabot has been helping them. They are building a suite of Drupal sites that need a way to consume content from a central Drupal source. The idea of solving this problem using Services and Feeds was a good fit for their requirements, so we worked through the details of how to get this accomplished. Highlights wanted to share the recipe for doing this with the community, and I pulled the details together into this article. We made the example a little more generic, so it would apply to other situations, and focused on ways that you can accomplish this without code. The result is a general recipe that we hope will be widely useful, rather than an exact description of the way they ended up using these tools.
Those who arenât trying to solve this particular problem may still find it interesting to see an example of how to configure and use Services, Views, and Feeds together. Services and Feeds are two very powerful and interesting modules, but sometimes they can be confusing to configure, and a step-by-step illustration may make it more clear.
## Terminology
In this recipe we have two different Drupal sites. One of them contains content that can be shared (we call that the âSourceâ) and the other needs to consume that content (we call this the âDestinationâ). The Source will share its content using Services and Services Views. The Destination will consume the content using Feeds.
### Modules needed on the Source
- CTools (http://drupal.org/project/ctools)
- Services and Rest Server (http://drupal.org/project/services)
- Services Views (http://drupal.org/project/services\_views)
- Views (http://drupal.org/project/views)
### Modules needed on the Destination
- Feeds (http://drupal.org/project/feeds)
- Feeds xPath Parser (http://drupal.org/project/feeds\_xpathparser)
- CTools (http://drupal.org/project/ctools)
- Job Scheduler (http://drupal.org/project/job\_scheduler)
- Views (http://drupal.org/project/views)
- Feeds Tamper (http://drupal.org/project/feeds\_tamper)
## Prepare the Source Content
First, create the content type(s) and content on the source. If the content needs to have references to other content, use the EntityReference module to create the reference fields (http://drupal.org/project/entityreference).
### Set up Services on the Source
Enable the following modules on the source site:
- CTools (http://drupal.org/project/ctools)
- Services and Rest Server (http://drupal.org/project/services)
- Services Views (http://drupal.org/project/services\_views)
- Views (http://drupal.org/project/views)
The Rest Server requires that you add spyc.php to sites/all/modules/services/server/lib. You can get that file from http://code.google.com/p/spyc/.
Create a view of the content you want to syndicate. Instead of creating a page display, create a âServicesâ display. On the Services display, use the style option to create an unformatted list of fields. Then add each field that you want to move to the Destination to the list of fields in the display.
When editing the field, set up a custom value key with the name you want to use for this value in the xml field, keeping in mind that you want to be sure that each value is unique in the feed.
 | Highlights Hub.jpg")
As you add the fields, you can see in the preview an array of the values that will be displayed in the XML. Many fields in the field API use the entity id to retrieve their values, so you will only see that value in this array. Donât worry, it will expose the right field value in the XML.
 | Highlights Hub-1.jpg")
Give this view a path. This will be the services path for this view.
 | Highlights Hub-2.jpg")
Next go to admin/structure/services and create a new service. Give it a name, use the REST server, and give it a âPath to endpointâ of ârestâ.
After it has been created you will see a link next to it to âEdit Resourcesâ. That will bring up a screen like the following that shows the possible resources for this services. You will see a resource for âViewsâ and for the path you created in the view. Check both of them.

You can see if this is working by navigating to that path, like http://example.com/rest/articles. You should see something like the following:

You can see the output as xml by going to http://example.com/rest/articles.xml, as json by going to http://example.com/rest/articles.json, etc.
Once you have confirmed that your XML service is working correctly, you can switch to the destination.
## Prepare the Destination
Next, create the content type(s) on the destination. If the content needs to have references to other content, use the EntityReference module to create the reference fields (http://drupal.org/project/entityreference). Weâre going to use Feeds to populate the content from the XML we just created on the Source.
### Set up Feeds
Enable the following modules on the Destination:
- Feeds (http://drupal.org/project/feeds)
- Feeds xPath Parser (http://drupal.org/project/feeds\_xpathparser)
- CTools (http://drupal.org/project/ctools)
- Job Scheduler (http://drupal.org/project/job\_scheduler)
- Views (http://drupal.org/project/views)
Create a new content type for the feed (this is not the same as the content type we will use for the nodes that the feed will create). It only needs a title, no body, so you can remove the body field.
Go to admin/structure/feeds and click the link to âAdd importerâ. Give the item a name and description.
Once the importer is created, you will see that it can be edited.

Edit the importer and you will see that you can set up various components. We will attach it to the Feed content type we just created, use the HTTP Fetcher to retrieve it from the XML link we just created on the Source, use the xPath XML Parser to deconstruct the values and move them into right fields, and the Node Processor to create nodes from the results.

Create the node mapping before you configure the xPath parser. This is somewhat confusing. First set up the Node processor to create nodes of whatever content type you want to contain the imported values, in this example that is âArticleâ.
Once you have done that, use the Mapping link to identify what will go into each field in that content type. When using xPath parser, we are going to populate each value with an xPath value. So we need to create a mapping item for each target field that gets its value from xPath. This may look odd, but it is correct. You will end up with a list of fields that all have the same source, the xPath parser. It looks like the following screenshot:

The other tricky part of the configuration is the xPath parser, especially if youâve never used xPath. xPath is a standard for locating values in a XML file. If youâre not familiar with xPath, there is reference material at http://w3schools.com/xPath.
We need to identify an xPath identifier for the âContextâ, which is the place in the XML that represents an individual node, and then one for each of the fields within that node. The configuration would up looking something like the following:

## Import the Content
Weâre finally ready! Create a new Feed node. Input the services path we created above. Be sure to append â.xmlâ to the path so the parser knows it is dealing with XML.
Once the feed node has been created, you will see an âImportâ tab on it. Click on that to actually import the data. We enabled Views so we can also see a âLogâ tab on the feed node. That tab shows us a view of the log messages that were created when trying to import the feed.
### References, A Special Problem
Moving content that has references in it from one site to another creates a special problem. The reference field on the source site is pointing to the nid of the referenced material, but it is using the nid from the source site. When you move all this content to another site, it will no longer have the same nid. Therefore, references to that material need to be updated to contain the right nid for that item on the destination.
For example. You might have a node 70 on the source that has a reference to node 89 on the source. When you move these two pieces of content to another site, the item that was node 70 on the source might become node 650, and the item that was node 89 might become node 777. The destination reference to node 89 needs to be updated so it now points to node 777.
Weâre using Feeds to pull in the content from the Source site. Each field on our destination site is managed using a handler that understands where its value belongs on that particular type of field. We need the Entityreference handler to be smart enough to figure out which value is actually needed on the Destination.
To do that we currently need an EntityReference patch, from http://drupal.org/node/1616680. Once that is applied, EntityReference will analyze each value that is passed into it, look through the Feeds tables to see if the referenced node has already been created. If it has, it will swap in the nid of the node that was created from that value. If it canât find information that tells it that node has already been created, it will wipe out the value, because a reference to the wrong or a non-existing node would not work correctly.
This process will work best if the referenced material is a different content type. That makes it possible to pull the referenced material before the content that references it, so that all the referenced material exists, to keep the reference links working.
If that is not possible, the alternative is to run the migration twice. By the second pass all the nodes will exist and the links can be created. For this second pass to work correctly, you have to choose the option to âUpdate existing nodesâ in the Node processor settings.

## Images, Another Special Problem
Another problem with using Feeds and Services to do this trying to find a way to get images to import correctly from the central server. If images are available at publicly available urls, the following method will work.
In the view of the image field, set the field up to âDisplay download path instead of file storage URI â. This will create XML that displays the image like âpublic://field/image/imagefield\_O0ivdK.pngâ. That isnât quite what we need to access it from the Destination. So on the Destination we need to add one more module to our mix, the Feeds Tamper module (http://drupal.org/project/feeds\_tamper).
Feeds Tamper lets us massage our Feed values before trying to do something with them. In this case we want to replace âpublic://â in the image path with the actual path to the image on the Source, like âhttp://example.com/sites/default/files/â.
Once Feeds Tamper is enabled, you will see a âTamperâ tab on the Feeds importer. It will show us a list of all the fields that have been defined. Select the field that contains the image field, and then choose to use a Regex to replace the value. It will look something like the following:

Now when you import nodes that have images, the images will be retrieved from the full image url and be properly transformed into images on the Destination site.
## Conclusion
This should get you started on creating a simple, no-code, content syndication system. I want to thank the team from [Highlights for Children](https://shop.highlights.com) for their willingness to share this information with the Drupal community. As noted above, there are other ways to solve this problem, in particular by using the Migrate or Deploy modules, but this is the approach that seemed to fit their needs the best.
If you havenât used these modules before and have been wondering how they can work together, you may want to try this recipe out.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Module Monday: ImageField Focus"
url: "/articles/module-monday-imagefield-focus"
type: article
date: 2013-08-05
updated: 2014-05-15
---
# Module Monday: ImageField Focus
# Module Monday: ImageField Focus
Give content editors more control over image cropping
By
[ Jeff Eaton ](/about/jeff-eaton)
August 5, 2013
Drupal image fields allow content editors to upload photos and pictures without tedious manual cropping and scaling. One posted, images are piped through a series of automatic cropping and scaling presets, ensuring everything is fast and consistent. Unfortunately, all that automation can be a problem when content creators *do* need to tweak how an image will appear at different sizes. When that's the case, the [Imagefield Focus](https://drupal.org/project/imagefield_focus) module can help.

Once installed, ImageField focus gives site builders a new option when setting up the rules for those automatically-generated image derivatives. Imagefield Focus adds "smart" versions of the standard Scale and Crop actions that take into account an image's "focus point" -- a portion of the image that should *always* be visible, even when it's scaled down and trimmed to fit other dimensions. Editors can specify that focus region when uploading an image using a simple Javascript widget; if no focus is specified, the normal cropping and scaling behaviors take over.

Although it doesn't give editors *explicit* control over the precise appearance of every version of an uploaded image, [Imagefield Focus](https://drupal.org/project/imagefield_focus) does the next best thing. It's a quick and easy addition to most sites, and can dramatically improve the quality of small thumbnails when used judiciously.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Handling \"git pull\" Automatic Merges"
url: "/articles/handling-git-pull-automatic-merges"
type: article
date: 2011-08-03
updated: 2014-05-15
---
# Handling "git pull" Automatic Merges
# Handling "git pull" Automatic Merges
"Only YOU can prevent useless merges"
By
[ Andrew Berry ](/about/andrew-berry)
August 3, 2011
If you're just starting out with Git, you'll inevitably run into commits into your feature branches like the following:
> Merge branch 'test' of git.lullabot.com:lbcom into test
What are these commits, and how did they get created? Usually, it's the result of adding a commit to your local copy of a branch, and then pulling upstream changes into that branch. Since your local commit isn't on the remote repository yet, when `git pull` runs `git merge origin/[branch] [branch]`, it will automatically do a "recursive" merge and create a commit with the remote changes. Then, when you push your changes up, you end up with both a merge from the remote integration branch into your local branch, and a merge from your feature branch into the integration branch.
Let's take a look at an example of how this situation can happen, and a way to resolve it cleanly. First, let's create two temporary git repositories: "upstream", to represent the remote repository, and "downstream", to represent your local clone of the repository.
`~/ $ cd /tmp tmp/ $ git init upstream Initialized empty Git repository in /private/tmp/upstream/.git/ tmp/ $ cd upstream upstream/ $ git config --local receive.denyCurrentBranch ignore # This allows us to push into upstream even when it has a branch checked out upstream/ $ echo 'Demo for how to handle upstream commits after you have merged into the upstream branch.' > README.txt upstream/ $ git add README.txt upstream/ $ git commit -m 'Adding a README file.' [master (root-commit) b8b6630] Adding a README file. 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 README.txt upstream/ $ cd /tmp tmp/ $ git clone upstream downstream Cloning into downstream... done. `
Now that we have our upstream repository with one commit on master, and a downstream clone of it, let's add another commit to the upstream repository:
`tmp/ $ cd upstream upstream/ $ echo 'Adding another upstream commit.' >> README.txt upstream/ $ git add README.txt upstream/ $ git commit -m 'Adding an upstream commit.' [master 9e625ab] Adding an upstream commit. 1 files changed, 1 insertions(+), 0 deletions(-) `
The next step is to add a feature branch and merge commit to the downstream clone. This simulates parallel development between two different developers:
`upstream/ $ cd /tmp/downstream downstream/ $ git checkout -b 1234/awesome-feature-branch Switched to a new branch '1234/awesome-feature-branch' downstream/ $ echo 'Adding a downstream commit before pulling into my local master branch.' >> README.txt downstream/ $ git add README.txt downstream/ $ git commit -m 'Adding a downstream commit.' [1234/awesome-feature-branch dff13db] Adding a downstream commit. 1 files changed, 1 insertions(+), 0 deletions(-) downstream/ $ git checkout master Switched to branch 'master' downstream/ $ git merge --no-ff 1234/awesome-feature-branch Merge made by recursive. README.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) `
We've completed our feature branch, and have merged it to our local copy of master. Time to push up our merge and share it with the world!
`downstream/ $ git push To /tmp/upstream ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to '/tmp/upstream' To prevent you from losing history, non-fast-forward updates were rejected Merge the remote changes (e.g. 'git pull') before pushing again. See the 'Note about fast-forwards' section of 'git push --help' for details. downstream/ $ git pull remote: Counting objects: 5, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /tmp/upstream b8b6630..9e625ab master -> origin/master Auto-merging README.txt CONFLICT (content): Merge conflict in README.txt Automatic merge failed; fix conflicts and then commit the result. downstream/ $ vim README.txt downstream/ $ git add README.txt downstream/ $ git commit [master 46208d7] Merge branch 'master' of /tmp/upstream downstream/ $ git push Counting objects: 11, done. Delta compression using up to 2 threads. Compressing objects: 100% (5/5), done. Writing objects: 100% (7/7), 779 bytes, done. Total 7 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (7/7), done. To /tmp/upstream 9e625ab..46208d7 master -> master `
What does our history graph look like now?
```
downstream/ $ git lg
* 46208d7 - (HEAD, origin/master, origin/HEAD, master) Merge branch 'master' of /tmp/upstream (2011-07-29 14:49:38 -0400)
* | 1bffd57 - Merge branch '1234/awesome-feature-branch' (2011-07-29 14:37:17 -0400)
|\ \
| |/
|/|
| * dff13db - (1234/awesome-feature-branch) Adding a downstream commit. (2011-07-29 14:35:01 -0400)
|/
* b8b6630 - Adding a README file. (2011-07-29 14:32:11 -0400)
```
That's pretty confusing. How could we have done this better? Instead of using `git pull`, let's use `git pull --ff-only`. Better yet, let's alias that command to `git pl` by running `git config --global alias.pl 'pull --ff-only'`. The following was done after I undid the above merges in both repositories using `git reset`. *Never do `git reset` on a real public branch!*
`downstream/ $ git pl From /tmp/upstream b8b6630..9e625ab master -> origin/master fatal: Not possible to fast-forward, aborting. downstream/ $ git lg * 9de839b - (HEAD, master) Merge branch '1234/awesome-feature-branch' (2011-07-29 14:52:21 -0400) |\ | * dff13db - (1234/awesome-feature-branch) Adding a downstream commit. (2011-07-29 14:35:01 -0400) |/ | * 9e625ab - (origin/master, origin/HEAD) Adding an upstream commit. (2011-07-29 14:33:55 -0400) |/ * b8b6630 - Adding a README file. (2011-07-29 14:32:11 -0400) downstream/ $ git reset --hard origin/master HEAD is now at 9e625ab Adding an upstream commit. downstream/ $ git merge --no-ff 1234/awesome-feature-branch Auto-merging README.txt CONFLICT (content): Merge conflict in README.txt Resolved 'README.txt' using previous resolution. Automatic merge failed; fix conflicts and then commit the result. downstream/ $ vim README.txt downstream/ $ git add README.txt downstream/ $ git commit [master b8d200d] Merge branch '1234/awesome-feature-branch' downstream/ $ git push Counting objects: 10, done. Delta compression using up to 2 threads. Compressing objects: 100% (4/4), done. Writing objects: 100% (6/6), 674 bytes, done. Total 6 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (6/6), done. To /tmp/upstream 9e625ab..b8d200d master -> master `
What does our repository look like now?
`downstream/ $ git lg * cf13636 - (HEAD, origin/master, origin/HEAD, master) Merge branch '1234/awesome-feature-branch' (2011-08-01 20:44:09 -0400) |\ | * dff13db - (1234/awesome-feature-branch) Adding a downstream commit. (2011-07-29 14:35:01 -0400) * | 9e625ab - Adding an upstream commit. (2011-07-29 14:33:55 -0400) |/ * 9568144 - Adding a README file. (2011-08-01 20:41:38 -0400) `
## The Takeaway
- Try to prevent extra merge commits when they don't show anything useful about the development of a feature branch.
- Don't use `git pull` by default, and if you do, be prepared to undo local merge commits with `git reset --hard HEAD^`. Use the `git pl` alias above to simplify this.
- If you do run into a situation where someone else has pushed a commit to the integration branch before you, use `git reset --hard origin/[branchname]` on your local copy of the integration branch to remove your merge and create a new one at the tip of the branch.
## Footnote
For the command-line-addicted, my alias for `git lg` in ~/.gitconfig is:
`[alias] lg = log --all --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%ci) %C(bold blue)<%an>%Creset' --abbrev-commit`
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "10 Commandments of Modern Web Design"
url: "/articles/10-commandments-of-modern-web-design"
type: article
date: 2013-02-07
updated: 2016-03-30
---
# 10 Commandments of Modern Web Design
# 10 Commandments of Modern Web Design
Design Principles for a Multi-device World
By
[ Jared Ponchot ](/about/jared-ponchot)
February 7, 2013
Albert Einstein famously said, "Any intelligent fool can make things bigger, more complex, and more violent. It takes a touch of genius -- and a lot of courage -- to move in the opposite direction." I would argue that a huge part of that genius that Einstein refers to can be found in clarity of purpose and principles.
We all wind up in situations where we're focusing on technical details, implementation and points of process, and missing the bigger picture. I confess I've been there far too often. When we find ourselves in those situations as designers, it's important to have some guiding principles we can remind ourselves of and even share with our team and colleagues. Guiding principles can help get everyone on the same page and make it easier to work through the details of process and implementation. They're no panacea, but they've certainly helped me maintain my sanity.
Below I've documented some of my emerging, fundamental design principles. These principles have helped guide me in this brave new world of a bazillion devices and amazing possibilities. Hopefully they'll be helpful to you as you hone your design process, document your own principles, and face challenges along the way.
## 1. The mobile web is important!
*Secret: 98% of the following three paragraphs I learned directly from [Luke Wroblewski](https://www.lukew.com/). If you need help making the case for a focus on mobile, read his writing, see him speak, get in touch with him!*
Why care so much about mobile in our design process? By Q1 of 2012 [Apple released numbers](https://thenextweb.com:443/news/there-are-now-more-iphones-sold-than-babies-born-in-the-world-every-day) that showed there were now more iPhones sold every day than babies born in the entire world (300k babies to 402k iPhones)! That was just iPhones, there were actually 562k iOS devices (which includes iPod Touch and iPad) sold each day at that time. By Q1 2012 we'd also reached [700k Android devices activated per day](https://www.cnet.com/news/8301-1035_3-57345925-94/google-activating-700k-android-devices-every-day/), 200k Nokia smartphones and 143k Blackberry devices. According to Morgan Stanley Research, by 1990 there were 100M+ desktop internet users. By the early 2000's we had reached 1B+ desktop internet users. Today that number of desktop internet users is only slightly higher than it was in the early 2000's, yet the number of mobile internet users is now 10B+! The number of mobile devices on the planet surpassed the number of humans on the planet nearly two years before Morgan Stanley's research predicted it would, which means mobile is not only ubiquitous, it's growing faster than our expectations.
But wait, there's more! In Q1 of 2012 [Facebook announced](https://developers.facebook.com/blog/post/2012/02/27/helping-improve-the-mobile-web/) they were seeing more people accessing Facebook on the mobile web than from ALL of their top mobile native apps combined! Facebook also released data suggesting that mobile web and app users invested noticeably more time on site than all of their Desktop web users combined. In Q3 of 2011 Nielsen US released their research on mobile users showing that of the millions and millions of mobile users across all platforms, significantly more were using the mobile web as opposed to native apps when given the choice (57M vs 49M).
## 2. Create once, publish everywhere.
Editorial teams need a singular, simple workflow to produce content once that then gets distributed efficiently and effectively to all device types. Editorial teams need to be focused on content quality, NOT things like device level placement, layout and aesthetic style. When [developing your content model](https://alistapart.com/article/content-modelling-a-master-skill/), model content types on core editorial and business needs, with an eye towards multi-channel reuse. You can then use those building blocks in the design process. This will ensure that editors aren't forced to become "designers by default". Ideas about form, structure and presentation that create new and more complex processes for editorial teams should be viewed with skepticism and caution. Anything that slows the editorial process, without adding significant content value, damages the core value in your product. A [COPE](http://blog.programmableweb.com/2009/10/13/cope-create-once-publish-everywhere/) approach (Create once, publish everywhere), with a consistent content model and simple data feeds that can be used by web-based widgets, apps, and business partners, helps facilitate rapid experimentation and innovation. It ensures that experimentation can happen at "the edges" without requiring foundational infrastructure changes.
## 3. Editorial workflow is important!
It's very easy for design teams to become focused on the consumption experience for content on a website, while completely ignoring how said content is created, reviewed, edited, curated and published. Great consumption experiences begin with great creation experiences. Spend time with the authors, reviewers, editors and publishers early in your design process. Watch what they do. Learn about the content they're producing. Gain an understanding of things like volume (how much of it do they produce), frequency (how often do they produce it) and average length (how much content makes up a single piece) for every type of content they're producing. As a designer, you can't create innovative ideas for new components and interaction methods without really understanding the content, and the best way to understand the content is to spend time with the people who create and nurture it.
The second part of this principle is that bad or painful editorial workflows create content problems. Also, eliminating editorial workflow pain points makes happy clients. You may not be able to solve all the problems of an editorial workflow process as a designer, but you can play your part in the process by treating it as important.
## 4. Release early and often.
> "Write drunk, edit sober." â Ernest Hemingway
Always err on the side of the simplest viable product for each release (see [KISS principle](https://en.wikipedia.org/wiki/KISS_principle) as well as [*Getting Real*](https://basecamp.com/gettingreal)). Make quick decisions, make something, find out how users interact with it and what they're valuing. Discover pain points. Adapt. In a competitive market place we need to iterate quickly and fail gracefully. Failing is necessary for innovation, and we can't fail till we try something. Create a culture of rapid experimentation as opposed to analytical paralysis.
## 5. Make existential design decisions based on data, â¨not assumptions.
By "existential design decisions" I mean decisions about whether a particular piece of content or component should exist on the screen. The basic rule here is don't remove things from a mobile experience because you assume mobile users don't want it. Conversely, don't add additional elements to a desktop experience because you assume those users want "enhanced experiences." Begin by delivering one content model and architecture across all devices, and then let real user data drive device specific optimization and customization.
Mobile users will tell us what they're wanting as they use things (or don't use things). Their interaction patterns, values and preferences can guide optimization and customization, but not until we have them. We need to release something and watch people use it before we form assumptions (see earlier release early and often principle).
Begin with the basic question of "Is this valuable for users?", not "Is this valuable to users on a particular device type or screen size?". While we may make some assumptions about hierarchical discrepancies from one device type to another, always start from the assumption that if it's important to users, it's important to ALL users.
It's worth noting that gathering web-based metrics about the behavior of mobile users is easier than logging and tracking the detailed interactions of mobile app users. The mobile web experience can lead the way for us, providing the data we need to understand user values and interactions. Mobile users continue to defy expectations as to what they will do and want to do on their mobile devices. A common frustration for mobile web users happens when assumptions are made about what mobile users do NOT want or need from a desktop experience. It's extremely important that we not limit mobile users based on these assumptions. Creating tailored experiences with unique content models and components for different devices can create significant user experience problems. For example, lets imagine google indexes the desktop version of a website, and provides links to said content on mobile devices based on a search. If those mobile devices then redirect to a tailored site with a limited content model, editing out the content that was searched against, confusion and user frustration ensues. We must never dumb down or limit a desktop experience and call it a mobile experience!
## 6. Design from content outward (not device type or display inward).
Focus first on delivering the best and simplest possible experience of a complete content model across all devices. Design should begin by uncovering the most valuable type(s) of content, and designing an experience for those. All subsequent displays and views into that content should follow. For example, a news site could begin by determining the most valuable type(s) of news content they provide to their consumers. A design team can then begin researching, wireframing, prototyping and brain storming around the consumption experience of a representative piece of content from each of those types. Once that is fleshed out, the focus can shift to the various structural channels through which parts of that content type are displayed (e.g. a homepage, a top level category landing page, etc.).
## 7. Nothing's more important than knowing what's important.
> "Design is the conscious effort to impose a meaningful order." â Victor Papanek
Design is about helping people understand what's really important and meaningful. That's beautiful. Embrace it! Discover and understand the relative importance of each type of content, the pieces that make up that type of content, and the channels through which that content flows. You can't begin to apply visual hierarchy in design without first knowing the content hierarchy. Design decisions should begin with broad hierarchy evaluations. Develop a components list for each screen (a list of the discreet pieces or chunks of content that exist on the page) and assign a relative hierarchy (e.g. a 1, 2 or 3) to each component in the list. After all that, you can begin to work things out visually with placement, proportion, and style.
## 8. Design mobile first.
*Once again, [Luke Wroblewski](https://www.lukew.com/) has shined a spotlight on this and helped me understand it.*
Designing "mobile first" means that we *embrace* the constraints of a tiny screen early in our design process. We evaluate our content model, components list and hierarchy first with that tiny screen in mind. Once we've established that, we then ask if there are ways that hierarchy changes or interactions can be enhanced for users with screen sizes and bandwidth capabilities beyond mobile. The constraints of the mobile screen size help enhance focus during the design process and keep design teams more closely aligned with whatever the core product value is. It's like packing first in a carry-on suitcase to discover what you REALLY want to bring. Often, you'll find that those extra things you put in your larger suitcase never get worn or used.
This does NOT mean that the visual experience can't be impressive. Remember, in many ways mobile devices have MORE capabilities than what's common among desktop devices. Things like device positioning, motion, location detection, multi-touch, gyroscope, and audio, video and photo input are common among mobile devices. Design teams may actually create more innovative and rich experiences focusing on mobile first during their design process.
## 9. Optimize, then customize.
After we actually make and release something, and have real user data to drive the next round of iteration and innovation, we need a way to prioritize that iteration. When both optimizations (e.g. technical solutions to serve up smaller file sizes or more appropriate ad sizes) and customizations (e.g. ideas about changes or enhancements to hierarchy, content or features) are being considered, optimizations should *almost* always be prioritized over customizations. Great experiences come from the ease and speed with which users can access, interact with, and contribute to content, and that ease and speed are very important. Mobile users continue to defy our assumptions about what they want to do on mobile devices, but they almost always want to do it faster and with greater ease.
## 10. Create and maintain a visual language (NOT a myriad of distinct designs).
Design teams need to produce a *flexible* visual language that can provide stylistic guidance across a myriad of screen sizes. There are some formal processes and design tools that can help you do this (e.g. [element collages](https://danielmall.com/articles/rif-element-collages/), [style tiles](https://styletil.es/), [web style guides](https://www.maban.co.uk/66/)), but the core principle is to establish a visual language that can allow for quick design decisions across all breakpoints. This approach reinforces the "release early and often" principle above. Having a style guide and other tools to guide visual decisions, rather than a collection of concrete designs tied to specific device widths and scenarios, means that new experimental designs don't have to chart their own course. A design process that takes a tailored approach, providing a myriad of custom static comps can dramatically limit your ability to quickly respond and innovate.
Published in:
- [ Digital & Content Strategy ](/topics/content-strategy)
- [ UX & Design ](/topics/design-and-ux)
- [ Front-end Development ](/topics/frontend-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Avoiding the Template.php of Doom (or, Overriding Theme Functions in Modules) "
url: "/articles/avoiding-the-templatephp-of-doom-or-overriding-theme-functions-in-modules"
type: article
date: 2008-06-16
updated: 2014-05-15
---
# Avoiding the Template.php of Doom (or, Overriding Theme Functions in Modules)
# Avoiding the Template.php of Doom (or, Overriding Theme Functions in Modules)
By
[ Jeff Eaton ](/about/jeff-eaton)
June 16, 2008
Drupal's theming system offers developers and designers a flexible way to override default HTML output when specific portions of the page are rendered. Everything from the name of the currently logged in user to the HTML markup of the entire page can be customized by a plugin "theme".
Unfortunately, this system can be its own worst enemy. Themes are very powerful, but in many cases they're the only place where specific output can be changed without hacking core. Because of this, themes on highly customized production sites can easily turn into code-monsters, carrying the weight of making 'Drupal' look like 'My Awesome Site.'
This can make maintenance difficult, and it also makes sharing these tweaks with other Drupal developers tricky. In fact, some downloadable modules also come with instructions on how to modify a theme to 'complete' the module's work. Wouldn't it be great if certain re-usable theme overrides could be packaged up and distributed as part of any Drupal? As it turns out, that *is* possible. In this article, we'll be exploring two ways to do it: a tweaky, hacky approach for Drupal 5, and a clean and elegant approach that's only possible in Drupal 6.
### Under the Hood
Before getting into the details, we'll look at *how* Drupal allows themes to override HTML rendering. This mechanism will be the key to our sneaky tricks.
Whenever 'themable' HTML is being generated, Drupal modules first assemble the basic data that should pre presented (an array of numbers, a content node...), then call the theme() function. For example:
```php
$node = node_load(1); // Load node id 1 from the database
$output = theme('node', $node); // This generates themed HTML
print $output;
```
The first paramater passed into the theme() function is the type of data being themed, while the second parameter is the 'thing' itself. When that function is called, Drupal walks through the following process:
1. **Does the theme handle it?**
The currently installed theme is first in line to render the object to HTML. Drupal checks for a function named *theme-name*\_*object-type*(), and if it exists, calls it. For example, the Garland theme uses the function garland\_breadcrumb() to control how the breadcrumb trail is displayed.
2. **Does the theme engine handle it?**
Next in line is the current 'theme engine.' In most cases, this is Drupal's default PHPTemplate theming engine. Smarty and PHPTal are other possibile engines. As with themes, Drupal checks for a function named *theme-engine-name*\_*object-type*(), and if it exists, calls it. The PHPTemplate engine uses the function phptemplate\_node() to control how nodes are displayed.
3. **Let a module handle it.**
Finally, if no overrides are found, Drupal checks for a function named theme\_*object-type*() and calls it if it exists. These default theme functions are usually provided by modules to offer default HTML output for objects in case no one overrides them.
This approach is very flexible: it gives themes and the underlying theme engines a chance to override the HTML, lets modules provide a 'default' style of output, and it makes the complexities of the overriding process invisible to a developer who just wants to print out a node (or any other themable object) on a page. The only problem is that it doesn't provide a way for another *module* to jump in between steps 2 and 3, overriding the default HTML.
### Drupal 5: Sneaky, Sneaky Hacks
In Drupal 5, there's no officially supported way to overcome this limitation, There is, however, a crafty trick you can use to override theme functions in your modules. Take a look back at step 2 in the explanation of Drupal's overriding process, again. Drupal checks to see whether a function named *theme-engine-name*\_*object-type*() exists in order to see if a theme engine wants to override the rendering. If that function name exists, Drupal will use it -- even if it's implemented in *your module*, not the actual theme engine.
What does that mean? If your module implements the function phptemplate\_username(), it will be treated as if it's the theme engine in step 2, overriding the default markup provided by Drupal core, without making any changes to the theme itself. Voila!
The downside, of course, is that if the theme engine you're using *does* provide its own override, no module can play this trick: the function name already exists, and trying to define it again in your module will cause PHP errors. It can still be a useful way to isolate site-specific chunks of theme code in a way that's easy to track, enable or disable, and so on.
### Drupal 6: The Land of Milk and Honey
In Drupal 6, things are a bit different. The same basic hierarchy is still in place: first themes, then theme engines, then modules all get opportunities to render an object to HTML. However, Drupal now caches the information about what function should be used in an internal "theme registry." This saves Drupal the work of 'discovering' who's in charge each time the theme() function is called.
In addition to saving time, though, this cached "registry" of theme functions is something that modules can *modify* using the hook\_theme\_registry\_alter() function. What does that mean? While a module can't insert itself between steps 2 and 3 in the discovery process, it *can* step in after the discovery process is complete, and *replace* the default function from step 1 with its own version -- even if it doesn't follow the naming conventions Drupal expects.
Let's take a quick look at how this works, stealing a snippet of code from the [WordPress Comments](http://drupal.org/project/wp_comments) module. It's a module that intercepts Drupal's default rendering of all form elements to tweak the appearance of labels and 'required' flags on certain forms.
```php
function wp_comments_theme_registry_alter(&$theme_registry) {
if (!empty($theme_registry['form_element'])) {
$theme_registry['form_element']['function'] = 'wp_comments_form_element';
}
}
function wp_comments_form_element($element, $value) {
// Here, we provide our customized version of the
// theme_form_element function from theme.inc...
}
```
The above code is pretty straightforward: in hook\_theme\_registry\_alter(), it first checks to be sure that the form\_element theme data is properly defined, then swaps in its own custom function (wp\_comments\_form\_element) in place of the default one (theme\_form\_element).
The beautiful part of this system is that it continues to work cleanly with custom themes: if a theme overrides the form\_element theming code as well, it will still take precedence over wp\_comments' version. In addition, there's no chance of colliding function names, as it relies on the theme registry rather than 'magic' function names like phptemplate\_form\_element().
### Wrapup
Drupal's theming system provides powerful tools for building clean HTML markup and layered designs. In Drupal 6, the new theme registry makes the process easier to maintain, and safer to tweak. I'm looking forward to the coming year, as more of Drupal's contributed modules migrate to version 6 and take advantage of its additional capabilities!
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Tip: Show the last git commit in the site footer"
url: "/articles/tip-show-the-last-git-commit-in-the-site-footer"
type: article
date: 2011-09-27
updated: 2014-05-15
---
# Tip: Show the last git commit in the site footer
# Tip: Show the last git commit in the site footer
By
[ Andrew Berry ](/about/andrew-berry)
September 27, 2011
Here's a quick tip for anyone using Git to version control their Drupal sites. When looking at a development or QA instance of a site, it's useful to be able to see at a glance what the last commit was. With a touch of settings.php code, we can add this information to our site footer.
`// Add the current git revision and date to the site footer.$conf['site_footer'] = '
'; `
This code gives us the following footer:

For some of our sites, we also have local branches on development servers that contain specific code for that server. This contains updates to robots.txt or .htaccess to restrict access to the site. Using a local branch breaks the above code, as it will always show the last local commit instead of the last commit that will eventually reach production. Combining `git merge-base` and the backtick operator allows us to show the proper commit message.
```
// Add the current git revision and date to the site footer.
$conf['site_footer'] = '
[0] => warning: Invalid argument supplied for foreach() in /modules/node/node.module on line 504.
[1] => error
)`
)
?>
This tells me that in /includes/common.inc on line 552, the function drupal\_set\_message was called, and was passed in two arguments. If I check the API documentation for [drupal\_set\_message](http://api.drupal.org/api/5/function/drupal_set_message), I can see that the first argument is the message to display, and the second is the type of message.
If I follow down the list, #1 tells me that drupal\_set\_message was called by error\_handler because of something that happened on line 504 in node.module. (Duh, I knew that one already.)
\#2 says that this was triggered by a call to node\_load from custom\_module.module on line 10. Great! Now I know that custom\_module.module has something to do with the problem. That helps narrow the problem down significantly.
But, as an added bonus, I *also* know that node\_load was passed an empty value into it as an argument, where normally this would be a node ID like 56. That's not going to go over well... unless I tell it what to load, how is Drupal not going to puke all over itself?
\#3 helps me narrow it down even further.. I know now that this was caused by a call to the custom\_module\_hook\_form\_alter function.
I can keep reading; the backtrace will detail all of the function calls that happened, all the way as far as index.php. But I have enough information now to start hunting for the bug.
If I look at my hook\_form\_alter in custom\_module.module, around line 10, I might see something like this:
```php
if (arg(0) == 'node') {
$node = node_load(arg(1));
}
```
Note: I didn't actually write such code, but needed an easy example. ;) After a bit of head-slapping, I realize that I'm on the path ?q=node, so there is no arg(1), therefore it's passing a NULL value into node\_load(). Further, my code blindly assumes that I'm on a path like node/34, but doesn't check to make sure I'm not on a path like node/add/blog. Eek. Let's try this again:
```php
if (arg(0) == 'node' && is_numeric(arg(1)) {
$node = node_load(arg(1));
}
```
Voila! No more errors. For trickier bits, and doing something like debugging form.inc ;) you probably want to step up and make a real-time debugger part of your developer arsenal. But to help you nail down the cause of an error quickly and easily, this can be a useful tip.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Keeping Drupal's Files Safe"
url: "/articles/keeping-drupals-files-safe"
type: article
date: 2011-07-20
updated: 2014-05-15
---
# Keeping Drupal's Files Safe
# Keeping Drupal's Files Safe
The Black Art of File Permissions
By
[ James Sansbury ](/about/james-sansbury)
July 20, 2011
When Drupal users deploy their first (or second, or tenth...) site to a real web server, one of the most common points of confusion is the proper access permissions for the *files* directory and *settings.php*. Because the files directory stores uploaded content from the site's users, badly configured permissions are a potential security risk. Lock it down too tightly, though, and managing backups or future migrations can be a pain.
My standard starting point when creating a new Drupal site on a server is to create or select an existing user that is a part of the web server group (typically the Apache group), and give ownership of all Drupal files to that user. On Ubuntu, these are the commands to get that set up:
```
(
# Create a new example user.
useradd -s /bin/bash -m example;
# Now add that user to the Apache group. On Ubuntu/Debian this group is usually
# called www-data, on CentOS it's usually apache.
usermod -a -G www-data example;
# Set up a password for this user.
passwd example;
)
```
Once I have that set up, I'll log in as the user and install Drupal at /var/www/example/docroot or a similar path, then create the files directory by hand and copy over the settings.php file. Since we log in as our example user before copying in Drupal, our file ownership and permissions should automatically be properly configured on all the core Drupal files and scripts (including .htaccess files).
```
su - example
cd docroot
cp sites/default/default.settings.php sites/default/settings.php
# Temporarily give the web server write permissions to settings.php
chgrp www-data sites/default/settings.php
chmod g+w sites/default/settings.php
```
Now let's set up the files directory.
```
# Create the directory.
mkdir sites/default/files
# Now set the group to the Apache group. -R means recursive, and -v means
# verbose mode.
chgrp -Rv www-data sites/default/files
```
Next we'll set up permissions so that the web server can always write to any file that is in this directory. We do this by using 2775 in our chmod command. The 2 means that the group id will be preserved for any new files created in this directory. What that means is that www--data will always be the group on any files, thereby ensuring that web server and the user will both always have write permissions to any new files that are placed in this directory. The first 7 means that the owner (example) can R (Read) W (Write) and X (Execute) any files in here. The second 7 means that group (www-data) can also R W and X any files in this directory. Finally, the 5 means that other users can R and X files, but not write.
```
chmod 2775 sites/default/files
```
If there are any existing files in this directory, be sure the web server has write perms on them.
```
chmod g+w -R sites/default/files
```
Now Drupal is ready to be installed. When finished, it is **VERY** important to come back to settings.php and ensure that all users only have read permissions.
```
chmod 444 sites/default/settings.php
```
That's it! This set up will keep uploaded files from being executed and settings.php from being accessed improperly, *and* sidestep annoying lockouts that prevent you from writing, changing, or removing user-uploaded files.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Android from a Drupal Perspective"
url: "/articles/android-from-a-drupal-perspective"
type: article
date: 2013-08-07
updated: 2014-05-15
---
# Android from a Drupal Perspective
# Android from a Drupal Perspective
By
[ Andrew Berry ](/about/andrew-berry)
August 7, 2013
Iâve been spending some time learning Android app development. I was initially attracted to it as I liked the openness of the [various](https://play.google.com/) [Android](https://www.amazon.com/mobile-apps/b?ie=UTF8&node=2350149011) [distribution](https://f-droid.org/) [channels](https://www.nvidia.com/en-us/geforce-now/) compared to the [heavy-handed approach Apple takes with iOS](https://en.wikipedia.org/wiki/Approval_of_iOS_apps#Notable_rejected_apps). While Iâm still relatively new to Android development, Iâve made a few observations that I thought would be interesting to those in the Drupal community.
# Android âforksâ standard Java APIs
Android uses [Dalvik](https://sites.google.com/site/io/dalvik-vm-internals), a re-implementation of the standard Java Virtual Machine to run apps and substantial portions of the Android operating system. The [Android SDK](https://developer.android.com/sdk/index.html) includes most of the standard Java APIs, packaged under the `java.*` namespace.
Where things get interesting is where the Android API, packaged under the `android.*` namespace, offers similar or enhanced functionality of the standard Java APIs. For example, Android includes XML utilities under `android.util.xml`. Likewise, Java provides (under the Java Extensions namespace) XML utilities under `javax.xml`. In fact, the Android API offers enhancements or alternatives to many core Java APIs.
This is very similar to how Drupal works, where we have enhancements and alternatives in the `drupal_` functions. Many of the [array](https://api.drupal.org/api/drupal/includes!bootstrap.inc/function/drupal_array_merge_deep/7), [file](https://api.drupal.org/api/drupal/includes!file.inc/function/drupal_chmod/7), and [string](https://api.drupal.org/api/drupal/includes!unicode.inc/function/drupal_strlen/7) functions mirror core PHP functionality. Re-implementing language APIs isnât necessarily a bad thing, especially where the language APIs have critical flaws. However, in both Android and Drupal it adds additional complexity for new developers to learn as they have to research each API alternative and decide what is best for their use case.
# Statically typed, done right
Much of the PHP world is going through a change where the dynamically typed nature of PHP is being directed to semi-typed code. While variables themselves do not have a [declared type](https://www.php.net/manual/en/language.types.type-juggling.php), Drupal (and Symfony) now use [type hinting](https://www.php.net/manual/en/language.oop5.typehinting.php) to enforce parameter types on method calls. Take a look at [ConfigImporter::\_\_construct()](https://drupalcode.org/project/drupal.git/blob/6718550cda5757d511a4f8e541cdaaaaa0f1422d:/core/lib/Drupal/Core/Config/ConfigImporter.php); every method parameter has an explicit type.
This semi-static nature of PHP code limits the effectiveness of static code analysis. Using an IDE like Eclipse shows whatâs possible with a static language like Java. For example, it can detect misassignments in variable or return types as you write them in the code itself. Exceptions tend to be more specific (no `catch(Exception $e)`) while also being easier to detect earlier in the development process as each method must document what exceptions it throws.
[Generics](https://docs.oracle.com/javase/tutorial/java/generics/) in particular solve a pain point of a dynamically-typed language like PHP. Load up any one of your production Drupal sites and check the watchdog log for warnings and notices (assuming they are logged at all). Odds are, a good number of them are from trying to iterate over non-arrays or non-objects, or are the result of a random integer or string being stuck into an array of entities or fields. Java solves this by allowing variables and methods to not just declare that they return a map, but that the map keys and values must be of a specified type. If Drupal 7 was written in Java, the declaration for hook\_menu() might be something like:
```
// We return a map (like a PHP array with named keys) where the keys are strings and they point to a map.
public HashMap mymodule_menu() {
â¦
}
```
Donât get me wrong; Iâm not saying that we should abandon dynamically typed languages and that all languages should be a [re-implementation of Java](https://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29). But, as Drupal developers, itâs important we keep an eye on what the rest of the programming world is doing so we donât find ourselves behind current best practices.
# Dynamic objects, done right
Of course, all of this strictness over types and the preference for explicit getter / setter methods in Java leads to a tonne of boilerplate code. Reflection is possible in Java, but itâs nowhere near as easy as in PHP. We get used to being able to iterate over object properties, or using strings as method names or variables. Being able to use arrays as shorthand for accessing a set of object properties in a loop lets us write common methods in PHP in four or five lines. For example, imagine a scenario where we are loading a node from a remote service where we canât ensure that the data is complete. Writing this validation in PHP could be very simple:
```php
$properties = array(ânidâ, âtitleâ, âauthorâ);
foreach ($properties as $property) {
if (!isset($node->{$property} || empty($node->{$property}))) {
throw new MissingPropertyException(âRequired $property is not set.â);
}
}
```
In Java, odds are youâll end up inlining each if statement, increasing the possibility of bugs or simple copy-paste errors. Sometimes, itâs easy to look at PHP code like this and focus on how much more opaque it is. But, when it comes down to it, in the real world code like this is just too useful to not miss when using other, stricter languages.
# Stepping into the Database API time machine
Like many mobile and desktop application APIs, Android offers a persistent storage layer backed by [SQLite](https://www.sqlite.org/). As a PHP and web developer, this sounds great! Most skills for database management should apply, even if weâre used to using a feature-rich database like MySQL or Postgres. Unfortunately, Androidâs database APIs and examples seem like a step back to the days when PHP developers used [mysql\_query](https://www.php.net/manual/en/function.mysql-query.php) as the primary method of database interaction.
The first issue youâll run into when youâre setting up your tables to store data. Unlike Drupal with itâs [Schema API](https://drupal.org/node/146843), Android creates tables by [executing a SQL string](https://developer.android.com/guide/topics/data/data-storage.html#db) manually created in your code. Since itâs Java, string concatenation isnât nearly as simple as with PHP, leading to code thatâs difficult to read and littered with string constants. While the [android.database.sqlite](https://developer.android.com/reference/android/database/sqlite/package-summary.html) package offers methods for most common database queries, it is missing some functionality that Drupal developers will immediately notice. Most notably is execute [MergeQueries](https://api.drupal.org/api/drupal/includes!database!query.inc/class/MergeQuery/7). Fetching results is done with the [SQLiteCursor](https://developer.android.com/reference/android/database/sqlite/SQLiteCursor.html) class, which is serviceable but doesnât have some of the convenience methods Drupal developers are used to such as [fetchAll](https://api.drupal.org/api/drupal/includes!database!prefetch.inc/function/DatabaseStatementPrefetch%3A%3AfetchAll/7)().
Androidâs API includes a [SQLiteQueryBuilder](https://developer.android.com/reference/android/database/sqlite/SQLiteQueryBuilder.html#query%28android.database.sqlite.SQLiteDatabase,%20java.lang.String%5B%5D,%20java.lang.String,%20java.lang.String%5B%5D,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String%29) which is great for dynamically constructing SELECT queries. Unfortunately, itâs limited to *only* SELECT queries. Want to dynamically construct an INSERT or UPDATE query? Back to raw manipulation of a query string, just like the dreaded [db\_rewrite\_sql()](https://api.drupal.org/api/drupal/includes!database.inc/function/db_rewrite_sql/6).
Sometimes we like to complain about the abstraction presented by [db\_select()](https://api.drupal.org/api/drupal/includes!database!prefetch.inc/function/DatabaseStatementPrefetch%3A%3AfetchAll/7) and friends. Using Androidâs DB APIs is a great reminder of just how developer friendly Drupal 7âs database layer is.
# Gaining Perspective
Itâs been an interesting experience to dig into a completely different language, API, and application paradigm after spending many years focusing on both Drupal and the web in general. Iâm sure there will be more striking similarities and differences I run across as I keep exploring and learning. Have you learned something that made your Drupal-influenced mind shocked (or made your jaw drop) while learning a new language or API? Let us know in the comments!
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "What makes a successful consulting project?"
url: "/articles/what-makes-a-successful-consulting-project"
type: article
date: 2014-01-29
updated: 2021-01-12
---
# What makes a successful consulting project?
# What makes a successful consulting project?
When a client doesn't have time for a glamorous, blue-sky project, what's a consultant to do?
By
[ Greg Dunlap ](/about/greg-dunlap)
January 29, 2014
Soon after I started working at Lullabot, I got my first client, and like all clients this one had a problem. They were a university whose site was running on Drupal 6: 2500 page nodes filled with HTML. The layout was largely managed through the WYSIWYG, and the quality of the HTML was all over the place. They wanted to take this site and migrate it to Drupal 7, with a properly designed content model and responsive layout, possibly using Panels.
In six months.
With one developer on staff.
### Cautious optimism
Despite the difficult demands and daunting schedule, I was excited! It is relatively rare to be approached by someone with a solid technical background who wants to do what is architecturally right for their organization. We started off trying to get a handle on what content was out there, and what the content types and fields might look like. As we progressed, it became apparent that each department was really doing their own thing, and any attempt to build a content model was going to require some discussion to get them all on the same page.
At the same time, we were discussing issues around migration of the content, and it became apparent that getting these HTML blobs into fields was going to be a big problem. In some cases, if the HTML is tightly structured, you can automate this process by scraping the HTML and extracting the data. However in this case, with no consistency at all, turning this HTML into fielded data was going to be an almost completely manual task.
### Reality
Only a couple weeks into the project I realized that the schedule was completely unrealistic for what they were attempting to do. So I sat down with the client and we started talking about their priorities. I knew something had to give, but you can't figure out what until you know what is most important. As we talked, it became apparent that in this case, the schedule was a 100% hard dependency - the new site needed to be launched in time for the start of the next school year. Not only that, people were reasonably happy using the site, with the exception of pain around media handling.
Given this, I recommended that they simply migrate their existing architecture to Drupal 7. This would reduce the number of unknowns to a very small number (mostly related to individual module upgrades) and would give them a very basic migration path. In order to start getting some more structure around their layouts, they would start using Panelizer in some cases (like landing pages) which would give their editors more freedom to place blocks of content without having to hand code HTML. On top of that, we now also had time to address some of the problems around media handling with the addition of some modules that were new for Drupal 7, and a bit of custom code.
Many devs would look at this solution and shake their heads. You've taken a site that was not much more than hand-coded HTML shoved into a CMS, and turned it into more of the same. What a waste, what a failure!
I would respectfully disagree. As consultants, our job is not to make a site with the best possible architecture, but to make a site with the best possible architecture *within the framework of the client's priorities.* Knowing the kind of site this client wanted to build, I was a little reluctant to propose the solution I did, even though I knew it was the best of all the available solutions. While this client was disappointed that they couldn't build the site the way they wanted, they were also hugely relieved to have a plan that looked manageable and achievable. It allowed them to build the site in a way that enabled future upgrades as time permitted, but didn't force the investment immediately.
### Success
What does a successful consulting project look like? It is a juggling act, and to some extent the rules are different for every one. One of the most important things that we as consultants can do, especially when we are devs or architects at heart, is to leave our own priorities at the door and focus on the client. What are their priorities? What are their pain points? What are their criteria for success? Taking the time to pull all of this data out of the client, and using it to craft a solution, is really the heart of our job, and for me personally, it is what gives me the most joy and satisfaction.
This is where the real puzzles are solved, where you can make the most of your experience, where you can take all the data you have, and craft something the client didn't even know they wanted in the first place. Now you have a plan that makes sense and meet's the client's goals, both spoken and unspoken. That, my friends, is what success looks like.
Published in:
- [ Business ](/topics/business)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Install CVS on Mac OSX"
url: "/articles/install-cvs-on-mac-osx"
type: article
date: 2007-07-15
updated: 2014-05-15
---
# Install CVS on Mac OSX
# Install CVS on Mac OSX
By
[ Addison Berry ](/about/addison-berry)
July 15, 2007
**NOTE: This video is no longer available as it contains outdated content.**
CVS is the system that Drupal.org uses to maintain all of the code used for both core and contributed modules. If you want to be involved with development, either coding or testing, CVS is a must-have in your tool box.
This video will walk you through the steps for installing the CVS command line client on your Mac OS X computer using the free Apple XCode Tools package. It shows you how to find the package you need, install it and verify the installation.
While this video is for Mac only, the plan is to create similar videos for other operating systems as well. Stay tuned!
**[Watch the video](https://www.lullabot.com/files/MacCVS.mp4)** (.mp4, 11.6 MB)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Building Views Query Plugins, Part 3"
url: "/articles/building-views-query-plugins-part-3"
type: article
date: 2013-09-11
updated: 2014-05-15
---
# Building Views Query Plugins, Part 3
# Building Views Query Plugins, Part 3
Exposing options and configuration
By
[ Greg Dunlap ](/about/greg-dunlap)
September 11, 2013
Welcome to the third part of our series on writing Views query plugins! In part 1, we talked about the kind of thought and design work that needs to be done before coding on the plugin begins. In part 2, we went through the basics of actually writing a query plugin. In this final chapter, we will investigate some enhancements to make your plugin more polished and flexible.
## Exposing configuration options
In part 2, we hardcoded things like the ID of the Flickr group we wanted to retrieve photos from, and the number of photos to retrieve. Obviously it would be better to expose these things as configuration options for the user to control.
In order to define configuration options for your plugin you need to add two methods to its class: option\_definition() and options\_form() (yes, the first is singular and the second is plural.) option\_definition() provides metadata about the options your plugin provides, and options\_form() provides form elements to be used in the Views UI for setting or modifying these options. Let's look at some code.
```php
function option_definition() {
$options = parent::option_definition();
$options['num_photos'] = array(
'default' => '20',
);
$options['group_id'] = array(
'default' => '',
);
return $options;
}
```
As you can see, option\_definition() is just an info hook, providing data about our options. The only required piece of data we need to provide is a default value, but there are several other options available including special handling for booleans and translations. Check out the [full API description](https://api.drupal.org/api/views/includes!base.inc/function/views_object%3A%3Aoption_definition/7) for more detail. The one important thing to note is that at the beginning of the function we are calling the parent. This ensures that any options defined by the base class are carried forward into ours. Forgetting to call the parent is a very common source of problems with Views plugins.
```php
function options_form(&$form, &$form_state) {
$form = parent:: options_form($form, $form_state);
$form['num_photos'] = array(
'#type' => 'textfield',
'#title' => t('Number of photos'),
'#description' => t('The number of photos that should be returned from the specified group.'),
'#default_value' => $this->options['num_photos'],
);
$form['group_id'] = array(
'#type' => 'textfield',
'#title' => t('Flickr group ID'),
'#description' => t('The ID of the Flickr group you want to pull photos from. This is a string of the format ######@N00, and it can be found in the URL of your group\'s "Invite Friends" page.'),
'#default_value' => $this->options['group_id'],
);
}
```
Assuming you've done everything correctly, you should now be able the see the following form at Advanced -> Query Settings.

Having implemented these forms, we now need to be able to retrieve the saved values and use them in our query. These values are stored in the 'options' array on your view, and the individual options are keyed just as they are in your form definition (just as if they were being referred to in $form in FAPI).
```php
function execute(&$view) {
$flickr = flickrapi_phpFlickr();
$photos = $flickr->groups_pools_getPhotos($this->options['group_id'], NULL, NULL, NULL, NULL, $this->options['num_photos']);
foreach ($photos['photos']['photo'] as $photo) {
$row = new stdClass;
$photo_id = $photo['id'];
$info = $flickr->photos_getInfo($photo_id);
$row->title = $info['photo']['title'];
$view->result[] = $row;
}
}
```
Functionally, of course, this is exactly the same as the last version. However, it is much more flexible and empowers your users to make the changes they need to.
## Displaying images
Now we're retrieving data from Flickr, but we're still waiting for the stuff that is the whole point of this exercise: the images! There are a couple things we need to do to make this happen. We need to extend the query code to get the image data out of the Flickr API, and as we discussed in [Part 1](https://www.lullabot.com/articles/building-views-query-plugins), getting all the data we need to display an image is a bit of a challenge given Flickr's API. We'll need to do the following:
- Call [flickr.groups.pool.getPhotos](https://www.flickr.com/services/api/flickr.groups.pools.getPhotos.html) to get a list of photos in the group.
- Iterate through each photo retrieved to get its ID.
- Call [flickr.photos.getSizes](https://www.flickr.com/services/api/flickr.photos.getSizes.html) for the photo and choose the size we want. The list of sizes is unpredictable, but every photo has an 'Original' size so we will always choose that one.
We will also need to create a new field handler to display the images.
Let's create the field handler first. The setup is the same thing we did before with the Title. First we add an entry to hook\_views\_data() in flickr\_group\_photos.views.inc to describe the field we are making available.
```php
$data['flickr_group_photos']['image'] = array(
'title' => t('Image'),
'help' => t('The actual image from Flickr.'),
'field' => array(
'handler' => 'flickr_group_photos_field_image',
),
);
```
Then we create a new handler called 'flickr\_group\_photos\_field\_image' as we have named it above. We will put this in a file called flickr\_group\_photos\_field\_image.inc in our handlers directory.
```php
/**
* @file
* Views field handler for Flickr group images.
*/
/**
* Views field handler for Flickr group images.
*/
class flickr_group_photos_field_image extends views_handler_field {
/**
* Called to add the field to a query.
*/
function query() {
$this->field_alias = $this->real_field;
}
}
```
Pretty much the same as our text field handler, but this is just going to return the text of whatever image URL we have, and that isn't what we want. We want to display the actual image! In order to do that we need to override the render() function and rewrite the data we're returning. This function should return the HTML we want to be displayed when we add this field to our view. So we could do something like this.
```php
/**
* Render the field.
*
* @param $values
* The values retrieved from the database.
*/
function render($values) {
$image_info = array(
'path' => $values->{$this->field},
);
$return = theme('image', $image_info);
}
```
The most notable thing is how we retrieve the data from our field. The render() function recieves an object with all the data for a specific row in our view, and we retrieve the property named the same as our field, which we retrieve from our instance of the handler object. This makes the code a little more portable since we aren't just hardcoding the name of our field in there. Then we pass this path to theme\_image() to generate the output.
This will work, however its not really optimal because it will display the image in its original size, and that will rarely be what we want. We could add the 'width' and 'height' keys to the $image\_info array, but that is really suboptimal when we have no idea what our source images will look like. What we really want to do is apply an image style to our image! In theory this would be pretty simple, however Drupal's image styles only work on images that are stored locally, and not having any locally stored files was sort of the entire point of this exercise.
Contrib to the rescue! The [Imagecache External module](https://drupal.org/project/imagecache_external) allows you to use core's image styles on external images. Phew. We can implement this in our field by calling theme('imagecache\_external') with the path to our image, and the style we want to apply. Here's the newly modified code.
```php
/**
* Render the field.
*
* @param $values
* The values retrieved from the database.
*/
function render($values) {
$image_info = array(
'path' => $values->{$this->field},
'style_name' => 'thumbnail',
);
$return = theme('imagecache_external', $image_info);
}
```
And finally, let's not forget to add this class to our .info file!
```php
files[] = handlers/flickr_group_photos_field_image.inc
```
If you've done everything correctly to this point, you should be able to go into an appropriate view, click Fields->Add, and see the Flickr Groups: Image field available to be added. If you try and add it and get the 'Broken or missing handler' error, then something is mostly likely improperly named somewhere along the way.

OK so the field type is in place, now we need to get the data from our query plugin. This just involves retrieving the new data we need, and saving it to an appropriately named property in our row object.
```php
function execute(&$view) {
$flickr = flickrapi_phpFlickr();
$photos = $flickr->groups_pools_getPhotos($this->options['group_id'], NULL, NULL, NULL, NULL, $this->options['num_photos']);
foreach ($photos['photos']['photo'] as $photo) {
$row = new stdClass;
$photo_id = $photo['id'];
$info = $flickr->photos_getInfo($photo_id);
$row->title = $info['photo']['title'];
$sizes = $flickr->photos_getSizes($photo_id);
foreach ($sizes as $size) {
if ($size['label'] == 'Original') {
$row->image = $size['source'];
}
}
$view->result[] = $row;
}
}
```
As you can see we've added another loop where we iterate over the available sizes until we hit the one labeled 'Original', and we use the 'source' property of that size as our image property on the row. Pretty simple stuff in the end. Once again, getting the data out of Flickr and into the view is the simple part. It's the pieces that surround and support that which take most of the work.
So having done all this, and clearing cache of course, you should now be able to see titles AND images in your view!

## Field options
There's one more thing that's irritating in this code: the image style is hardcoded into the field handler. Wouldn't it be nicer if we could choose which image style we want? Thankfully, fields support options forms just like queries do. In fact, pretty much all views handlers and plugins support this functionality. Just add this code to your flickr\_group\_photos\_field\_image class.
```php
function option_definition() {
$options = parent::option_definition();
$options['image_style'] = array('' => '-');
return $options;
}
function options_form(&$form, &$form_state) {
// Offer a list of image styles for the user to choose from.
parent::options_form($form, $form_state);
$form['image_style'] = array(
'#title' => t('Image style'),
'#type' => 'select',
'#default_value' => $this->options['image_style'],
'#options' => image_style_options(FALSE),
);
}
```
Not much to explain there: it looks like the query options we implemented above. After adding this code, you should have an option to choose an image style when you add a Flickr Groups: Image field to your view.
We will also need to tweak the field rendering to use the image style the user has chosen like so.
```php
function render($values) {
$image_info = array(
'path' => $values->{$this->field},
'style_name' => $this->options['image_style'],
);
$return = theme('imagecache_external', $image_info);
}
```
Now we can have nicely styled images and their titles! Things are really looking nice now, aren't they?
## Wrapup
We've covered a *lot* in this series, and there's so much more we can dig into! While we've looked at a lot of code, I don't think that any of it has been horribly complicated or mind-bending. It's mostly a matter of knowing what to put where, with a healthy dose of planning to make sure our data fits into the Views paradigm properly. In summary, the steps are:
- Make a plan of attack, taking into account the data you're retrieving and the way Views expects to use it.
- Create field handlers for your data.
- Write remote queries to retrieve your data and store it in rows in the view object.
There's a lot of work in those steps, but after running through it a couple times the architecture makes a lot of sense.
## Get the code!
I've made the code from this article [available on Github](https://github.com/heyrocker/flickr_group_photos)! In addition to the functionality described here, it makes a couple more fields available and integrates them into the query engine. Feel free to fork and send pull requests if you find anything wrong or want to add more features.
Thanks for reading and following along. Now, go forth and consume APIs!
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Import/Export Large MYSQL Databases"
url: "/articles/importexport-large-mysql-databases"
type: article
date: 2009-05-15
updated: 2021-01-12
---
# Import/Export Large MYSQL Databases
# Import/Export Large MYSQL Databases
Managing large MYSQL databases from the command line.
By
[ Karen Stevenson ](/about/karen-stevenson)
May 15, 2009
When working with MYSQL I often use phpMyAdmin, which is a nice GUI way to manipulate my database. But some operations won't work in phpMyAdmin when the database is too large. In particular, you can't import or export really large databases using phpMyAdmin. So sometimes you need to do things on the command line. So I thought I'd document some of the command line snippets we use frequently. In the following, replace \[USERNAME\] with your mysql username, \[DBNAME\] with your database name, \[/path\_to\_file/DBNAME\] with the path and name of the file used for the database dump, and \[/path\_to\_mysql/\] with the path to mysql bin (like /Applications/MAMP/Library/bin/).
## Copy/Export a Large Database
MYSQL has no 'Copy' function. You create a copy by dumping the database with mysqldump. To dump the database and gzip it at the same time, use the following. This will prompt you for your password.
```
mysqldump -u [USERNAME] -p [DBNAME] | gzip > [/path_to_file/DBNAME].sql.gz
```
## Import a Large Database
If you want to replace the database with a fresh dump created by the above process, do the following. First, unzip the file.
```
gzip -d [/path_to_file/DBNAME].sql.gz
```
Get to a mysql prompt (you will be asked for your password.)
```
[/path_to_mysql/]mysql -u [USERNAME] -p
```
Then do the following to wipe out the old database and replace it with the new dump:
```
SHOW DATABASES;
DROP DATABASE [DBNAME];
CREATE DATABASE [DBNAME];
USE [DBNAME];
SOURCE [/path_to_file/DBNAME].sql;
```
## Conditional Dumps
Sometimes the search index is huge and you want to omit it from the dump. Do so with:
```
mysqldump -u [USERNAME] -p [DBNAME] --ignore-table=[DBNAME].search_index | gzip > [/path_to_file/DBNAME].sql.gz
```
There are actually a number of tables you could exclude, like the sessions table, the watchdog table and all the cache\* tables. But if you use the above technique to destroy and recreate the database after doing this, you will be missing all those excluded tables. So you will want to do a two step process instead: First, create a backup with ONLY the table information, no data.
```
mysqldump -u [USERNAME] -p [DBNAME] --no-data | gzip > [/path_to_file/DBNAME].info.sql.gz
```
Then create a backup, including only data from the tables you need.
```
[path_to_mysql/]mysqldump -u [USERNAME] -p [DBNAME] --no-create-info --ignore-table=[DBNAME].search_index --ignore-table=[DBNAME].cache --ignore-table=[DBNAME].cache_block --ignore-table=[DBNAME].cache_content --ignore-table=[DBNAME].cache_filter --ignore-table=[DBNAME].cache_form --ignore-table=[DBNAME].cache_menu --ignore-table=[DBNAME].cache_mollom --ignore-table=[DBNAME].cache_page --ignore-table=[DBNAME].cache_pathdst --ignore-table=[DBNAME].cache_pathsrc --ignore-table=[DBNAME].cache_views | gzip > [/path_to_file/DBNAME].data.sql.gz;
```
Well that's a lot of typing. Wouldn't it be nice if there was a wildcard we could use instead of typing out all those cache\_ tables? Well there is!! You can do:
```
[path_to_mysql/]mysqldump -u [USERNAME] -p [DBNAME] --no-create-info --ignore-table=[DBNAME].search_index --ignore-table=[DBNAME].cache% | gzip > [/path_to_file/DBNAME].data.sql.gz;
```
After doing this, just import the two files as above, first the one with only the table info, and then the data. Result, a (relatively) small database with all the optional tables emptied out. Note that the wildcard trick above is not documented anywhere that I can see, so you'll want to test that it works in your setup.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "A Beginner's Guide to Caching Data in Drupal 7"
url: "/articles/a-beginners-guide-to-caching-data-in-drupal-7"
type: article
date: 2011-08-10
updated: 2014-05-15
---
# A Beginner's Guide to Caching Data in Drupal 7
# A Beginner's Guide to Caching Data in Drupal 7
By
[ Jeff Eaton ](/about/jeff-eaton)
August 10, 2011
Building complicated, dynamic content in Drupal is easy, but it can come at a price. A lot of the stuff that makes a site engaging can spell 'performance nightmare' under heavy load, thrashing the database to perform complex queries and expensive calculations every time a user looks at a node or loads a particular page.
One solution is to turn on page caching on Drupal's performance options administration page. That speeds things up for anonymous users by caching the output of each page, greatly reducing the number of DB queries needed when they hit the site. That doesn't help with logged in users, however: because page level caching is an all-or-nothing affair, it only works for the standardized, always-the-same view that anonymous users see when they arrive.
Eventually there comes a time when you have to dig in to your code, identify the database access hot spots, and add caching yourself. Fortunately, Drupal's built-in caching APIs and some simple guidelines can make that task easy.
### The basics
The first rule of optimization and caching is this: never do something time consuming twice if you can hold onto the results and re-use them. Let's look at a simple example of that principle in action:
```php
function my_module_function() {
$my_data = &drupal_static(__FUNCTION__);
if (!isset($my_data)) {
// Do your expensive calculations here, and populate $my_data
// with the correct stuff..
}
return $my_data;
}
```
The important part to look at in this function is the variable named $my\_data; we're initializing it with an odd-looking call to `drupal_static()`. The `drupal_static()` function is new to Drupal 7, and provides functions with a temporary "storage bin" for data that should stick around even after they're done executing. `drupal_static()` will return an empty value the first time we call it, but any changes to the variable will be preserved when the function is called again. That means that our function can check if the variable is already populated, and return it immediately without doing any more work.
This pattern appears all over the place in Drupal -- including important functions like node\_load(). Calling node\_load() for a particular node ID requires database hits the first time, but the resulting information is kept in a static variable for the duration of the page load. That way, displaying a node once in a list, a second time in a block, and a third time in a list of related links (for example) doesn't require three full trips to the database.
In Drupal 6, these static variables were created using the PHP 'static' keyword rather than the drupal\_static() function (see the [Drupal 6 version of this article](https://www.lullabot.com/articles/a-beginners-guide-to-caching-data-in-drupal-6) for an example). It was also common to provide a $reset parameter on each function that used this pattern, giving modules that needed the freshest information a way to bypass the caching code. While that approach still works in Drupal 7, drupal\_static() allows the process to be centralized. When modules need absolutely fresh data, they can call drupal\_static\_reset() to clear out any temporarily cached information.
### Making it stick: Drupal's cache functions
You might notice that the static variable technique only stores data for the duration of a single page load. For even better performance, it's often possible to cache data in a more permanent fashion...
```php
function my_module_function() {
$my_data = &drupal_static(__FUNCTION__);
if (!isset($my_data)) {
if ($cache = cache_get('my_module_data')) {
$my_data = $cache->data;
}
else {
// Do your expensive calculations here, and populate $my_data
// with the correct stuff..
cache_set('my_module_data', $my_data, 'cache');
}
}
return $my_data;
}
```
This version of the function still uses the static variable, but it adds another layer: database caching. Drupal's APIs provide three key functions you'll need to be familiar with: [cache\_get()](http://api.drupal.org/cache_get), [cache\_set()](http://api.drupal.org/cache_set), and [cache\_clear\_all()](http://api.drupal.org/cache_clear_all). Let's look at how they're used.
After the initial check of the static variable, this function looks in Drupal's cache for data stored with a particular key. If it finds it, $my\_data is set to $cache->data and we're done. Combined with the static variable, future calls during this page request won't even need to call cache\_get()!
If no cached version is found, the function does the actual work of generating the data. Then it saves it TO the cache so future requests will find it. The key that you pass in as the first parameter can by anything you choose, though it's important to avoid colliding with any other modules' keys. Starting the key with the name of your module is always a good idea.
The end result? A slick little function that saves time whenever it can -- first checking for an in-memory copy of the data, then checking the cache, and finally calculating it from scratch if necessary. You'll see this pattern a lot if you dig into the guts of data-intensive Drupal modules.
### Keeping up to date
What happens, though, if the data that you've cached becomes outdated and needs to be recalculated? By default, cached information stays around until some module explicitly calls the cache\_clear\_all() function, emptying out your record. If your data is updated sporadically, you might consider simply calling cache\_clear\_all('my\_module\_data', 'cache') each time you save the changes to it. If you're caching quite a few pieces of data (perhaps versions of a particular block for each role on the site), there's a third 'wildcard' parameter:
<?php cache\_clear\_all('my\_module', 'cache', TRUE); ?>
This clears out all the cache values whose keys start with 'my\_module'.
If you don't need your cached data to be perfectly up-to-the-second, but you want to keep it reasonably fresh, you can also pass in an expiration date to the cache\_set() function. For example:
<?php cache\_set('my\_module\_data', $my\_data, 'cache', time() + 360); ?>
The final parameter is a unix timestamp value representing the 'expiration date' of the cache data. The easiest way to calculate it is to use the time() function, and add the data's desired lifetime in seconds. Expired entries will be automatically discarded as they pass that date.
### Controlling where cached data is stored
You might have noticed that cache\_set()'s third parameter is 'cache' -- the name of the table that stores the default cache data. If you're storing large amounts of data in the cache, you can set up your own dedicated cache table and pass its name into the function. That will help keep your cache lookups speedy no matter what other modules are sticking into their own tables. The Views module uses that technique to maintain full control over when its cache data is cleared.
The easiest place to set up a custom cache table is in your module's install file, in the `hook_schema()` function. It's where all of the custom tables used by your module are defined, and you can even make use of one of Drupal's internal helper functions to simplify the process.
```php
function mymodule_schema() {
$schema['cache_mymodule'] = drupal_get_schema_unprocessed('system', 'cache');
return $schema;
}
```
Using the `drupal_get_schema_unprocessed()` function, the code above retrieves the definition of the System module's standard Cache table, and creates a clone of it named 'cache\_mymodule'. Prefixing the name of custom cache tables with the word 'cache' is common practice in Drupal, and helps keep the assorted cache tables organized.
If you're really hoping to squeeze the most out of your server, Drupal also supports the use of alternative caching systems. By changing a single line in your site's settings.php file, you can point it to different implementations of the standard cache\_set(), cache\_get(), and cache\_clear\_all() functions. The most popular integration is with the open source [memcached](http://drupal.org/project/memcache) project, but other approaches are possible (such as a file-based cache or against PHP's APC). As long as you've used the standard Drupal caching functions, your module's code won't have to be altered.
### Advanced caching with renderable content
In Drupal 7, "renderable arrays" are used extensively when building the contents of each page for display. Modules can define page elements like blocks, tables, forms, and even nodes as structured arrays; when the time comes to render the page to HTML, Drupal automatically uses the `drupal_render()` function to process them, calling the theme layer and other helper functions automatically. Some complex page elements, though, can take quite a bit of time to render into HTML. By adding a special #cache property onto the renderable element, you can instruct the `drupal_render()` function to cache and reuse the rendered HTML each time the page element is built.
```php
$content['my_content'] = array(
'#cache' => array(
'cid' => 'my_module_data',
'bin' => 'cache',
'expire' => time() + 360,
),
// Other element properties go here...
);
```
The #cache property contains a list of values that mirror the parameters you would pass to the `cache_get()` and `cache_set()` if you were calling them manually. For more information on how caching of renderable elements works, check out the detailed documentation for [the drupal\_render() function on api.drupal.org](http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_render/7).
### A few caveats
Like all good things, it's possible to overdo it with caching. Sometimes, it just doesn't make sense -- if you're looking up a single record from a table, saving the result to a database cache is silly. Using the [Devel](http://drupal.org/project/devel) module is a good way to spot the functions where caching will pay off: it can log the queries that are used on your site and highlight the ones that are slow, or the ones that are repeated numerous times on each page.
Other times, the data you're using will just be a bad fit for the standard caching system. If you need to join cached data in SQL queries, for example, cache\_set()'s practice of string data as a serialized string will be a problem. In those cases, you'll need to come up with a solution that's specific to your module. VotingAPI maintains one table full of individual votes and another table full of calculated results (averages, sums, etc.) for quick joining when sorting and filtering nodes.
Finally, it's important to remember that the cache is not long term storage! Since other modules can call cache\_clear\_all() and wipe it out, you should never put something into it if you can't recalculate it again using the original source data.
### Go west, young Drupaler!
Congratulations: you now have a powerful set of tools to speed up your code! Go forth, and optimize.
*Note: This article is an updated version of an earlier article, and deals specifically with the Drupal 7 API. If you're working with an older version of Drupal, [see the Drupal 4 and 5](https://www.lullabot.com/articles/a-beginners-guide-to-caching-data) or [Drupal 6](https://www.lullabot.com/articles/a-beginners-guide-to-caching-data-in-drupal-6) of this article.*
Published in:
- [ Performance and Scalability ](/topics/performance-and-scalability)
- [ System Administration ](/topics/system-administration)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Your Javascript should expose APIs, too!"
url: "/articles/your-javascript-should-expose-apis-too"
type: article
date: 2013-01-31
updated: 2021-01-12
---
# Your Javascript should expose APIs, too!
# Your Javascript should expose APIs, too!
Replicating module\_invoke\_all and drupal\_alter in Javascript
By
[ Joe Shindelar ](/about/joe-shindelar)
January 31, 2013
If you've ever written a Drupal module before you're likely familiar with Drupal's hook system. I'm not going to go in to details about how the hook system works, or why this particular pattern was chosen by Drupal's developers. What's important here is *what this systems allows module developers to accomplish*.
At its most basic, the hook system is what allows me to write a module that enhances or extends Drupal -- without ever having to modify a line of someone else's code. I can, for example, modify the list of blocks that are available on a given page by simply implementing a "hook" function in PHP that modifies the information that was already set up. This approach is one of the things that makes Drupal incredibly flexible!
When you're writing your own custom modules, it is customary to expose these types of hooks for other modules, too. That way, other developers can come along and make minor modifications or feature enhancements to your module by "piggybacking" on your module's functionality, rather than hacking your code. It also means that you don't have to anticipate every possible use case for your code: by providing these extension points, you allow future developers to extend it.
Drupal makes it really easy for modules developers to do this, and it provides a set of helper functions that allow you to easily broadcast these "I have a hook! Who wants to tie into it?" announcements to the world.
Check out the docs for [module\_invoke\_all()](http://api.drupal.org/api/drupal/includes%21module.inc/function/module_invoke_all/7), [module\_invoke()](http://api.drupal.org/api/drupal/includes%21module.inc/function/module_invoke/7) and [drupal\_alter()](http://api.drupal.org/api/drupal/includes%21module.inc/function/drupal_alter/7) to learn more.
## The case for APIs
Now, that's all well and good, but what if the functionality I want people to be able to alter or events I want people to be able to react to are encapsulated in *Javascript?* This is where Drupal breaks down a bit and we're left to our own devices. Drupal provides a simple mechanism for modules to essentially register a bit of code that they would like to be executed whenever Drupal.attachBehavoirs is called. This happens when the DOM is fully loaded and Drupal's Javascript code has been properly initialized, and anytime new elements have been added to the DOM via AJAX. And that's about it.
For most cases where Javascript needs to interact with Drupal this works just fine. What you're likely really after is some element in the DOM anyway so you can do your sweet web 2.0 fadeIn().
Sometimes, though, your Javascript needs are more complex than adding visual pizzaz. Consider this; You've been asked to write a module that integrates a video player from a third party site into Drupal. The video service offers a straightforward Javascript based embed option. All you have to do is include their Javascript file on the page and call the player.setup() method, passing in an embed code to the player so that it knows which video to play. Easy enough, and a common pattern.
Let's say the setup() method takes not only an embed code but also an array of additional paramaters to configure how the player appears and behaves. Some of those paramaters are callback functions -- the name of an additional Javascript function that should be called when certian things happen. Some examples of this might be 'onCreate' when the player is embeded and ready to start playback, 'onPause' when someone clicks the player's play/pause button, and so on. For our example we'll assume that we're implementing an 'onCreate' callback. It should be triggered by the video player after it's been embedded, and is ready for playback to start. (Another common example of something like this the jQuery.ajax, which can take 'success' and 'error' callbacks. Which one gets called depends on the result of the Ajax request.)
This should be simple, right? Just set the callback to 'Drupal.myModule.onCreate' and write the corresponding function in your mymodule.js file!
Except... Later on in the project, Kyle comes along and is told to implement an unrelated piece of functionality that *also* fade a DOM element in on the page **after** the video player has been embeded. Now two different functions both need to fire when the Video player has been created. You can't just pass in a second 'onCreate' callback function to the player.setup() method -- it only allows one value! So now Kyle is stuck trying to jam his unrelated Javascript in to your Drupal.myModule.onCreate function. Blam! You've got a mess of unrelated, hard to maintain code!
A better way of handling this would be for your module to re-broadcast the 'onCreate' callback to give other code a chance to respond to it as well. You could take it one step farther and implement a system that sends out a notification when the 'onCallback' event occurs, and subscribe to it with any functions that need it. That approach would be a lot like the module\_invoke\_all() function in Drupal's PHP API.
Lucky for you, there are all kinds of ways to do this in Javascript! I'll outline two of them below.
## The Drupal Way
One way of solving the problem is to replicate the Drupal.behaviors system provided by core. That's actually pretty straightforward. You need to:
- Create a well known place for someone to register their objects or functions.
- Write a short snippet of Javascript that will loop through and execute these registered functions.
- Call this Javascript at the appropriate time.
- Ensure that your module's Javascript is loaded before that of other modules.
In your javascript code, you'll need to create a standard object that other modules can go to when they register their functions. In core, this is Drupal.behaviors. We'll create our own new object for this example.
```
var MyModule = MyModule || {};
MyModule.callbacks = {};
```
Then you'll need an easy way to call and execute any registered callbacks.
```
MyModule.executeCallbacks = function(data) {
$.each(MyModule.callbacks, function(key, callback) {
if ($.isFunction(callback)) {
callback(data);
}
});
}
```
What this code does is loop over all the functions collected in MyModule.callbacks and executes them. Pretty simple, really! It works well for notifying any code of some "event" as long as you remember to call the MyModule.executeCallbacks() method at the appropriate times.
Now, any other module can register callback functions that will be called by the MyModule.executeCallbacks() method:
```
MyModule.callbacks.theirModuleOnCreate = function() {
// Do some sweet Javascript stuff here ...
}
```
Put it all together by implementing your onCreate callback (the code we wanted to implement at the very beginning of this exercise!) and call the new code.
```
MyModule.onCreate = function() {
// Give all modules that have registered a callback a chance to respond.
MyModule.executeCallbacks();
}
```
Pretty painless. Just make sure your module's Javascript file is loaded before any others: in Drupal, you can do that by changing the weight of your module to -10, or something similar. If you don't do that, you'll end up with warnings about "MyModule.callbacks being undefined" when someone else's Javascript is loaded first, and tries to register a callback with your object.
This approach is easy to implement, but it still has some problems.
- It's a major "Drupalism." For anyone familiar with Javascript but not with Drupal's way of doing things, it's a conceptual hurdle that needs to be overcome before understanding how to add a new behavior.
- If one behavior fails, the execution stops: anything that hasn't be executed will not get called, and you're dependent on others to write code that doesn't fail.
- There is no easy way to remove a behavior added by someone else's code, or to overwrite the way that Drupal core does something. Don't like the table drag javascript? The only way around it is [Monkey Patching](https://en.wikipedia.org/wiki/Monkey_patch).
## An alternative way
Another approach that's a bit more "Javascripty" is to use the [jQuery.trigger()](https://api.jquery.com/trigger/) and [jQuery.bind()](https://api.jquery.com/bind/) methods. With them, you can create custom events that other modules can listen for and react too. It's a lot like using jQuery to intercept the 'click' event on a link, perform some custom action, then allowing the link to continue with it's processing. In this case, though, we'll be triggering our own custom event on a DOM element. To do this you need to:
- Call jQuery.trigger on an object or DOM element in order to broadcast an event.
- Use jQuery.bind on an object or DOM element to register a listener for an event.
- Wash, rinse & repeat ...
As usual, the code samples below would go inside of your module's mymodule.js file and be included on the page when necessary via the drupal\_add\_js() PHP function.
Inside of our module's .onCreate callback, we use the jQuery.trigger() method to trigger our custom event and alert all listeners that they should go ahead and do their thing. It's not necessary to prefix our event names with 'myModule.' but it does lead to cleaner code. (It also makes it easier to unbind all of the events associated with a particular module in one step.) This approach is functionally equivalent to calling the MyModule.executeCallbacks() method from the previous example. We're telling anyone that wants to participate that now is the time to do it!
```
MyModule.onCreate = function() {
// Trigger an event on the document object.
$(document).trigger('myModule.onCreate');
}
```
The second piece of this puzzle is using the jQuery.bind() method to add an event listener that will be triggered any time our custom event is triggered. Each event listener receives the jQuery.Event object as the first argument. The code below is equivalent to the bit above where we register our callback with MyModule.callbacks.theirModule = {}
```
$(document).bind('myModule.onCreate', function(event) {
// Do my fancy sliding effect here ...
});
```
Any number of modules can bind to the custom event, and respond to the onCreate callback event, without ever having to modify your module's Javascript.
Another technique that I've used in the past is to create a drupal\_alter() style functionality in Javascript. This would allow others to modify the parameters that my code passes to a third party's API. It's easy to do, so since you can pass an array of additional arguments to the jQuery.trigger() method. They'll be passed along to any listeners added with jQuery.bind(). And, since complex data types in Javascript are inherently passed by reference, the listener can make changes to the incoming parameters and they'll be reflected upstream. Something like the following would do the trick.
```
MyModule.createWidget = function() {
var parameters = {width: 250, height: 100, onCallback: 'MyModule.onCreate'};
// Allow other modules to alter the parameters.
$(document).trigger('myModule.alterParameters', [parameters]);
superAwesomeWidgetAPI().setup(parameters);
}
```
Then anyone else could bind to the new 'myModule.alterParameters' event and receive the parameters object as an additional argument. The first argument for any function using jQuery.bind() to listen to an event is always the [jQuery.event](https://api.jquery.com/category/events/event-object/) object.
```
$(document).bind('myModule.alterParameters', function(e, parameters) {
// Here I can change parameters and it will be reflected in the function that triggered this event.
parameters.width = 350;
});
```
While this method isn't perfect either, I like that it's closer to the Javascript programming patterns used in the broader world outside of Drupal. This means it's easier for someone not familiar with Drupal to understand my code and to quickly figure out how to work with it.
It does, however, still exhibit some of the same problems as the Drupal.behaviors method. Notably the fact that if any one listener has code that fails the whole system breaks down. In addition, you have to trigger and bind to events on either a DOM element or other Javascript object.
### Summary.
Drupal itself doesn't come with a Javascript equivalent to the module\_invoke\_all() function, but there are a lot of ways that we can implement a similar system ourselves. When you run in to this problem in your development, I encourage you to use the second approach outlined: it has all the same capabilities of the Drupal.behaviors approach, with less code and a shallower learning curve.
These are by no means the only methods for accomplishing this sort of task in Javascript. Another for example would be the popular publish/suscribe pattern, but we'll wait to explore those in another article! Whichever approach you choose, it's important to build for future flexibility, just as you would with your PHP code.
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ Front-end Development ](/topics/frontend-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Creating Awesome New Icons"
url: "/articles/creating-awesome-new-icons"
type: article
date: 2006-10-03
updated: 2019-02-04
---
# Creating Awesome New Icons
# Creating Awesome New Icons
Create, Explore, Expand! Learn how to create new, great-looking icons for your web project!
By
[ Nate Lampton ](/about/nate-lampton)
October 3, 2006
Create, Explore, Expand! Learn how to create new, great-looking icons for your web project! We take a look at the Lullacons Icon Pack and detail how you can modify the [free source files](https://www.lullabot.com/articles/creating-awesome-new-icons) or create new icons of your own!

### The Icon Challenge
We've all been there before. You've checked Google images for a small sized images. You just need a simple icon; maybe an arrow, file, or bullet and you don't have one that will fit just right. So you fire up Gimp, Photoshop, and yes... even Microsoft Paint to crank out that 16 pixel masterpiece.
#### Route 1 - Pencil Masterpiece
The canvas is small, but working with that pencil tool at 1600% you think you know what's going on. Obviously you start with a black outline and then fill in with color. After slaving away for a few minutes you zoom out, and your icon looks... like crap. The edges are hard and the details are hard to discern. Move on to route 2.
#### Route 2 - The Image Scale
Another common attempt is taking a larger icon or picture, popping it into your favorite image editor and scaling it down. The result is often the opposite of route 1, instead of harsh lines and colors, you have a completely blurry image. The outline is fuzzed around the edges, which makes it difficult to use on different colored backgrounds. Worse, it looks just like what it is: a scaled down image.
### All About the Vectors (Route 3)
To solve the problems of the previous methods, there is a happy medium. Using vector-based tools, you can add a lot of detail and let the computer handle the scale issue. Your icons come out looking sharp and the way you expect. Best of all, you can scale an image smaller **and larger** without sacrificing image quality. In the Lullacons Icons Pack, the small icons were created first. Then we decided a larger version of the 'info', 'warning', and 'alert' icons might be useful. Because the icons are assembled using vectors, up scaling was a trivial matter.
Original 16x16 IconScaled Raster (Bitmap) IconScaled Vector Icon



Raster images are based on pixel information, and your image editor needs to estimate what other pixels need to be created to scale an image up or down. Vector images are based on entirely mathematics, making such estimations unnecessary. If you're not familiar with the difference between vector and raster images, this [introduction to vectors by Mike Doughty](http://www.sketchpad.net/basics1.htm) might help you get up to speed.
### Layer Styles
Now we're going to get into some vendor-specific methods. Although other applications may provide similar functionality, I'll focus on Photoshop for this tutorial. Layer styles provide a very easy way to add otherwise difficult effects to your icons. Letting Photoshop handle the rendering also makes it to create reproducible effects, which you can use throughout a series of icons. Watch the video below for an example of setting up a vector graphic with a few layer styles.
Creating a Vector Based Icon and Applying Layer Styles
### Pixel Masking and Saving for Web
Vector based icons get us most of the way to where we want to be. Unfortunately, vector icons still need a little bit of final cleanup before they're ready for the web. Oftentimes you'll want your icons to have a transparent background, so you can use them on any color site. The PNG format will soon become the premier image format for layout when Microsoft adds support for alpha channel transparency in IE7. Until then we're stuck with just one-bit transparency, like you can create with the 8-bit PNG format or a GIF.
Unmasked IconMasked IconMasked Icons Example



Unmasked icon on a color background

Masked icon on a color background

Unmasked icon with alpha transparency
There are two tricks to getting your icons to work on a variety of backgrounds. The first is cleaning up unnecessary pixels from the outside of your image. Use a layer mask to hide these unnecessary pixels. The second trick is applying a neutral color as your background matte when you save the image for web.
## Applying a Mask and Saving the Icon for Web
### The Lullacon Pack Style
The Lullacon Icons have a certain 'look-and-feel' across the board. If you're interested in contributing your own icons, here are a few guidelines for creating new icons.
#### Icon Creation Guidelines:
- Create all shapes as vector paths
- Use color or gradient overlays for all color
- 110° is the magic number, use it for the angle of all bevels and shadows
- Use Vectors! Try to avoid using the pencil and brush tools whenever possible.
#### Borders:
- All icons use a 1px gray border
- Use 'stroke' style to apply border around the icon
- Stroke should only apply to the outside of the icon, no gray lines inside the icon
- The final border around icons should be 'approximately' 40% brightness. (Saturation and Hue should be 0)
- The bevel of the icon should NOT apply to the border, this is usually accomplished by creating a copy of the icon shape and applying the border to it, then set the opacity of the fill down to 0%
#### Shadows:
- No shadows
#### Bevel:
- 16x16 icons use a 2px bevel (use 1px if the total icon size is appropriately small)
- A larger bevel may to achieve a 'round' look, such as in the user icon
- 110° lighting angle, 30° (default) altitude angle
- Highlight mode screen (default) 75% opacity (default)
- Shadow mode multiply (default) 20% opacity
#### Color:
- There are very few regulations on color, just be consistent with your choices :)
- The 'Color Palette.psd' file included lists 5 common colors in the Lullacons Pack. Feel free to add new colors for your own reference.
- Tiny icons (10x10 pixels) usually are white-background based
#### Gradients:
- Subtle gradients may optional be used for visual appeal, though use should be limited
- Use linear gradients for 2D surfaces (calendar, document, envelope, etc)
- Use circular gradients for rounded surfaces (spheres, user bodies, etc)
#### Cleanup and Exporting:
- The most efficient method for creating an icon that works universally on any background is masking the entire icon in Photoshop to reduce unwanted edges. Then export with a 50% gray matte on the icon to eliminate the white 'halo' that occurs when you save an image with 1 bit transparency.
### Playing with the Source
Let's reap the benefits of a well designed icon. Check out how easy it is to create new color variation in this video.
## Creating a New Color Variation
The Lullacons Icon Pack is licensed under the [GNU General Public License](http://www.gnu.org/copyleft/gpl.html). This means that you can use the icons for any purpose, so long as you leave the copyright intact. If you make any modifications to the icon source files, you must also make those files available.
### Download
- [Download the Lullacons Icon Source Files](https://www.lullabot.com/files/Lullacons_Source.zip)
- [Go to the Introductory Lullacons Article](https://www.lullabot.com/articles/free-gpl-icons-lullacons-pack-1)
- [Read the Lullacons Icons License ](https://www.lullabot.com/files/lullacons-readme.txt)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Creating a Simple Chrome Extension"
url: "/articles/creating-a-simple-chrome-extension"
type: article
date: 2014-03-06
updated: 2021-01-12
---
# Creating a Simple Chrome Extension
# Creating a Simple Chrome Extension
Add Drupal API search to Chrome's Omnibar in just a few easy steps
By
[ Carwin Young ](/about/carwin-young)
March 6, 2014
As a front-end developer there are a lot of different technologies to keep up with. Whether Iâm working with AngularJS or trying to set up Grunt tasks, I find myself having to look up a lot of things. To make that easier on myself, I decided to write Google Chrome extensions to simplify the process. It turns out thatâs pretty darn easy! In this article, Iâll show you how to create a simple one that hooks into Chrome's Omnibar to search the Drupal API with a simple keyword.

There are only two files youâll need to create to make a Chrome extension: manifest.json and background.js. (I told you it was simple!) We'll be going through the creation process step by step, and you can take a look at a finished version of the extension over on [GitHub](https://github.com/carwin/drupal-api-chrome).
## manifest.json
The first file we need is manifest.json. The file that contains all of the information about your extension, and every Google Chrome extensions needs one. This is where youâll define its name, specify which version of Chrome it requires, and link any scripts that your extension will make use of.
```
{
"name": "Drupal API Search",
"description": "Add support to the omnibox to search the Drupal API",
"omnibox": {
"keyword": "dapi"
},
"icons": {
"16": "icon.png"
},
"background": {
"scripts": ["background.js"]
},
"version": "1.0",
"minimum_chrome_version": "9",
"manifest_version": 2
}
```
The majority of this manifestâs contents are fairly self-explanatory, but the two we care most about right now are the âomniboxâ and âbackgroundâ settings. The âomniboxâ setting describes what keyword a user can type into the Chrome's omnibox to trigger our extension. Here, weâre setting our extension to initialize with the keyword âdapiâ (Short for 'Drupal API'). The âbackgroundâ settings describe pages or scripts that need to run in the background to make the extension work. Since this extension just redirects the current Chrome tab, thereâs no need to define an html page for it. You can find out more about Background Pages [here](https://developer.chrome.com/extensions/background_pages.html). The background.js file is where all the magic happens for this extension, but you arenât limited to using a single background script. You could add as many scripts as you need for your extension to work. One of my other extensions searches the Compass documentation. For that extension, I created a suggestions.js file to store the suggestion titles and URLs that I wanted to make available. The Drupal API extension we're making won't require that, though.
## background.js
Next we need to create a background.js file. It should contain a function to reset the default suggestion text.
```
function resetDefaultSuggestion() {
chrome.omnibox.setDefaultSuggestion({
description: 'dapi: Search the Drupal API for %s'
});
}
resetDefaultSuggestion();
```
This function makes use of the setDefaultSuggestion() method which, as you might expect, sets the default suggestion. Because we'll be resetting the Omnibox's suggestion text like this in a number of places, sticking it into this function will save some time. Note that we're calling the function right after we define it, so that the suggestion text is set as soon as the script is loaded. Since we arenât going to set up any pre-defined suggestions (Thatâd be a big job for our little extension!) we'll simply add some descriptive help-text to let the user know what youâre doing as they enter a search term.

Next, weâll add another re-usable function to background.js to handle the actual navigation to the Drupal API site.
```
function navigate(url) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.update(tabs[0].id, {url: url});
});
}
```
This function only takes one argument, a URL, and locates the current tab of the current Chrome window before navigating to the URL passed as the argument.
## Omnibox Events
The Omnibox API comes with some handy events we can listen on such as: onInputStarted, onInputChanged, and onInputCancelled. Were we doing something more complicated than a quick search, we might add some logic to onInputChanged to show some suggestions and maybe call our resetDefaultSuggestion() function in onInputCancelled to wipe out the suggestions when the input disappears. For now though, we only need one event: onInputEntered. That event fires when the user confirms their input in the omnibox, by pressing the Enter/Return key or clicking the Go icon in the right side of the omnibox. When the user enters the query theyâd like to search for, weâll call our navigate() function and send them on their way using the search URL for http://api.drupal.org and appending their input as the text to be searched.
```
chrome.omnibox.onInputEntered.addListener(function(text) {
navigate("https://api.drupal.org/api/drupal/7/search/" + text);
});
```
## Testing it out
Now that we have all the pieces in place we can finally give our extension a test drive. To do this, all we need do is navigate to Chromeâs extension management page: chrome://extensions. Once there, make sure the box marked âDeveloper modeâ is checked, then choose âLoad unpacked extension...â from the list of buttons that show up. Point it to the directory on your computer where you've been working on this extension, and voila! Your extension is running! Try it out by typing âdapiâ into the omnibox and hitting tab.

The folder your extension lives in should only need the two files mentioned in this article: manifest.json and background.js. If you're particular about things, you might throw an icon in the mix as well; you can set the name of the icon Chrome should use when displaying your extension in manifest.json.
## Next Steps
Creating Chrome omnibox extensions like this is exceedingly simple. Once youâve made one, youâll probably be inclined to whip up a slew of others to handle your daily search tasks. If you come up with something really handy, I encourage you to put it up on the Chrome Web Store for others to use too. Check the related links section below for more information on that. If youâd like to contribute to this particular project you can find it on [GitHub.](https://github.com/carwin/drupal-api-chrome) If youâd rather just use it without all the hassle, you can install it straight from the [Chrome Web Store](https://chromewebstore.google.com/detail/empty-title/fndfibkbfdaglikocmggomgliaegfhlo).
Published in:
- [ Front-end Development ](/topics/frontend-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Setting up SSL Offloading (Termination) on an F5 Big-IP Load Balancer"
url: "/articles/setting-up-ssl-offloading-termination-on-an-f5-bigip-load-balancer"
type: article
date: 2012-05-09
updated: 2021-01-12
---
# Setting up SSL Offloading (Termination) on an F5 Big-IP Load Balancer
# Setting up SSL Offloading (Termination) on an F5 Big-IP Load Balancer
Hardware-based SSL decryption allows web servers (Apache, nginx, Varnish) to focus on serving content.
By
[ Nate Lampton ](/about/nate-lampton)
May 9, 2012
At Lullabot several of our clients have invested in powerful (but incredibly expensive) F5 Big-IP Load Balancers. One of the primary reasons for investing in an F5 is for the purpose of SSL Offloading, that is, converting external HTTPS traffic into normal HTTP traffic so that your web servers don't need to do the work themselves. HTTPS requests (and more specifically, the SSL handshaking to start the connection) is incredibly expensive, often on the magnitude of at least 10 times slower than normal HTTP requests.
In a quick, largely unscientific test, here are two Apache Bench results against a stock Apache install, one with SSL and one without. Just serving up a static text file:
```
ab -c 100 -n 100 http://localhost/EXAMPLE.txt
Requests per second: 611.21 [#/sec] (mean)
Time per request: 163.609 [ms] (mean)
Time per request: 1.636 [ms] (mean, across all concurrent requests)
Transfer rate: 816.54 [Kbytes/sec] received
ab -c 100 -n 100 https://localhost/EXAMPLE.txt
Requests per second: 48.52 [#/sec] (mean)
Time per request: 2060.857 [ms] (mean)
Time per request: 20.609 [ms] (mean, across all concurrent requests)
Transfer rate: 64.82 [Kbytes/sec] received
```
Yikes, HTTPS is 12 times slower than HTTP! Not to mention more processor intensive. Comparing against nginx or Varnish, the slowness ratio increases as they serve HTTP traffic even faster. Now there are certainly ways to speed up SSL (using faster cyphers for example), but the fact remains that SSL is expensive. By using hardware-level decryption at the load balancer, the web server software (or reverse-proxy software like nginx or Varnish) can focus on serving pages.
## Getting Started
For those not familiar with a Big-IP load balancer's administration, most of the configuration is done via a web interface, accessible via the device's IP address.
[](https://www.lullabot.com/sites/default/files/u10/1-landing-page.png)

The Big-IP Administrative interface
The navigation for the site is located in the left-hand column.
## Adding SSL Certificates
The first thing you need to do to get SSL termination set up is to install the SSL certificate onto the machine. This is done by navigating to Local Traffic -> SSL Certificates -> Import. You must import the .key and the .crt files obtained from your Certificate Authority (i.e. Verisign, Comodo, etc.) separately with the same "Name" property. So give your certificate and key a name, usually matching the domain name, such as "example-com" or "example-com-wildcard". Upload the .key file as a Key, and the .crt file as a Certificate; both using the same value in the Name field.
[](https://www.lullabot.com/sites/default/files/u10/2-add-cert.png)

Adding an SSL Certificate
After finishing, the list of SSL Certificates should include your certificate and key in the list as a single entry, meaning they're associated with each other.
[](https://www.lullabot.com/sites/default/files/u10/3-cert-list.png)

After adding an SSL Certificate
## Set up SSL Profile
Now that our SSL certificate is uploaded into the load balancer, we need to create an SSL profile that utilizes the certificate. Visit Local Traffic -> Profiles -> SSL -> Client. The term "Client" means traffic between the outside world and the load balancer (conversely "Server" means traffic between your internal servers and the load balancer). Click the "Create..." button to add a new profile.
Give your profile a name (it can be the same as the certificate if you like), such as "example-com-wildcard". Leave the Parent profile as the default "clientssl". Check the box for custom options, then select your Certificate and Key that should be used to communicate with your end-user browsers. Leave all the other defaults.
[](https://www.lullabot.com/sites/default/files/u10/4-ssl-profle.png)

Adding the SSL Profile
## Set up the Virtual Server
F5 Load Balancers use a concept of a "Virtual Server" to accept connections at a certain IP address and hostname. I won't go into the details here and assume you already have a Virtual Server for HTTP.
If you already have a Virtual Server for HTTPS, edit it. If not, create a new virtual server with these settings:
```
Name: [same as your HTTP virual server, with "https" added somewhere]
Destination Type: Host
Destination Address: [same as your HTTP virtual server]
Service Port: 443, HTTPS
HTTP Profile: http
SSL Profile (Client): example-com-wildcard
SSL Profile (Server): None
SNAT Pool: [same as your HTTP virtual server]
```
[](https://www.lullabot.com/sites/default/files/u10/5-https-virtual-server.png)

Configuring the HTTPS Virtual Server
The most important part of this configuration is selecting an SSL Profile for the "Client", but not for the "Server". This is all that is needed to actually "enable" SSL termination. The F5 is actually decrypting all incoming traffic no matter what, but by selecting "None" for the Server-side profile, the traffic simply is not re-encrypted before communicating with the back-end servers.
**Don't skip this**: Just because you have SSL termination enabled on this virtual server, you still need to point it at the correct location. If you're editing an existing virtual machine, it is probably currently pointing at a pool of servers on port 443. In the case of Apache, it will throw an error page, refusing to serve insecure HTTP pages over a secure port (443). To fix this (or set it up if this is a new virtual machine), click the "Resources" tab on the new virtual machine.
Under the "Load Balancing" section, select the same "Default Pool" option as you are using for your HTTP virtual machine. This makes it so that both HTTP and traffic that was formerly HTTPS come into the same port on your backend servers.
[](https://www.lullabot.com/sites/default/files/u10/6-virtual-server-pool.png)

Setting the Load Balancing Pool to match the HTTP Virtual Server
## Identifying HTTPS traffic in your application
The only problem with the above approach to SSL termination is that all traffic getting to your web application is now over HTTP. This is a problem because often times security checks on the page will enforce an HTTPS connection and possibly attempt to redirect the user to HTTPS. In order for the application to avoid redirects like this, we need to inform the web server that the contents of the request were *previously encrypted* over HTTPS, even though they aren't any more.
To do this, it's recommended to set up an iRule that sets a special header. Visit Local Traffic -> iRules -> iRule List. Click the "Create..." button.
In the new iRule, give it a name such as "https-offloaded-header". In the rule contents, use the following code:
```
##
# Notify the backend servers that this traffic was SSL offloaded by the F5.
##
when HTTP_REQUEST {
HTTP::header insert "X-Forwarded-Proto" "https";
}
```
Save the iRule, then head back over to your virtual server under Local Traffic -> Virtual Servers -> Virtual Server List and click on your HTTPS virtual server. Under the "Resources" tab, click "Manage..." in the iRules section.
Move your new iRule from the "Available" list into the "Enabled" list. Moving it to the top of the rule list is also a good idea if you're doing any kind of HTTP/HTTPS redirects on your load balancer as setting headers after doing a redirect can cause pages to be undeliverable. Click "Finished" when done.
Now we're setting a special HTTP header on requests that have been SSL offloaded onto the F5. In your application, check this header in your code. For a PHP application, you may want to use this header to set the $\_SERVER\['HTTPS'\] super-global. And even more specifically for Drupal (since that's what we do here at Lullabot), you would probably include code like this in your settings.php file.
```php
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
$_SERVER['HTTPS'] = 'On';
}
```
Since most PHP applications (including Drupal) check if they're on an HTTPS page by checking this variable, no further changes are necessary to the application.
If you're looking for more information on setting up an F5, Googling usually will not turn up too much because F5 has put the bulk of their documentation on a private site, for which you must first register (for free thankfully), at https://devcentral.f5.com/wiki.
*Updated 5/10/2012: Switched iRule to using "X-Forwarded-Proto".*
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Custom paging for views"
url: "/articles/custom-paging-for-views"
type: article
date: 2006-06-25
updated: 2016-04-07
---
# Custom paging for views
# Custom paging for views
Adding custom limited lists to views
By
[ Angie Byron ](/about/angie-byron)
June 25, 2006
### Introduction
Angie/webchick here, and this is my very first article on the Lullabot site :)
One of the projects I'm working on at the moment is [The World](https://theworld.org/), an audio news magazine co-produced by BBC World Service, PRI and WGBH Boston. They put out a new show every week day, and the content is divided into separate sections (represented by taxonomy) such as Global Hit, Geo Quiz, and so on.
One of the requirements was to show only the content for a given day when clicking on certain sections of the site, and allow next/previous links to take you backward and forward in the list. This is in contrast to the default Drupal pager, which merely goes by X nodes per page. Further, there should not be any "dead" links; the next/previous links should always point to a date with valid content.
So this required a few things:
1. A list of nodes, filtered by taxonomy term.
2. A method for filtering this list by a given date.
3. A way to show the most recent show's content if no date is specified.
4. Logic to figure out what the next/previous dates are (we can't do a simple "current day +/- 1").
5. Code to display the pager.
6. Some way to tie it all together.
Here's the solution I came up with -- source code is attached at the end.
### The basic stuff
The operative word in the above narrative is **list**. Anytime you need "lists of stuff," you should immediately think of [Views](http://drupal.org/project/views). Download and enable the module, then click **administer >> views** to get started.
The preliminary setup was pretty straight-forward:
- Name: **taxonomy\_by\_date**
- Access: **anonymous user** and **authenticated user**
- Description: **View a list of taxonomy filtered by date**
- Check **Provide page view** (expand the **Page** fieldset)
- URL: **taxonomy\_by\_date**
- View Type: **Teaser List**
- Uncheck **Use pager**
### Fun with Arguments
The first requirement, setting up a taxonomy-filtered node list, is super easy. Expand the **Arguments** fieldset, select **Taxonomy: Term ID** from the list, and tell it to **Display all values** if a term isn't specified. Now taxonomy\_by\_date/1 will show you all of the nodes tagged with term 1, taxonomy\_by\_date/2 will show you all of the nodes tagged with term 2, and just taxonomy\_by\_date by itself will show everything.
Next, we need a way to restrict those lists further by date. So let's add a second **argument**, this time **Node: Posted Full Date** and again **Display all values** if none is specified. Now, you can go to a URL like taxonomy\_by\_date/2/20060622 to show only the content tagged with taxonomy term 2 that was created on June 22, 2006.
However, that's not quite what we want; we want to show the most recently published content if a date isn't specified. So how do we attack the problem of needing to "inject" an argument into a view where one is lacking?
The answer lies in the **Argument handling code** section. This is a really handy Views feature which allows you to make changes to the view *on-the-fly, as it's being built*! I wrote up a [handbook page](http://drupal.org/node/70145) which explains this functionality in a bit more detail. For now though, let's check out some code:
```
// Default to term 1 if none was set
if (!$args[0]) {
$args[0] = 1;
}
// Default to most recent date if none was set
if (!$args[1]) {
$timezone = _views_get_timezone();
$latest = db_result(db_query("
SELECT DATE_FORMAT(FROM_UNIXTIME(n.created+$timezone), '%Y%m%%d')
FROM {node} n
INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid = %d
ORDER BY n.created DESC LIMIT 1", $args[0]));
$args[1] = $latest;
}
return $args;
```
`$args` here is an array of the arguments that are passed into the current view. So in a URL like taxonomy\_by\_date/2/20060622, `$args[0]` would be 2, and `$args[1]` would be 20060622. This code checks to see if both `$args[0]` and `$args[1]` are set; if not, it gives them default values (1 for term and the most recent date, respectively).
We're using `db_result()` here because we're only interested in one value: the date if the newest content in that term formatted as YYYYMMDD.
Note this weird little bit: `'%Y%m%%d'` -- there are two %%'s here in order to escape `%d` because that has significance in `db_query` -- it indicates the value should be replaced with something numeric. Also, note that the code is *not* between `` ... this is intentional; doing so throws an error.
So now, going to taxonomy\_by\_date/ will automatically show us all the most recent content in term 1. Sweet!
### More Fun with the Date Pager
Now, the tricky part... how to get those next/previous date links in there?
I struggled with this for a couple days and eventually came up with placing code for the pager in the **Footer** text of the **Page** section, with **PHP code** as the input format. At this point, the view is built and is accessible via the global variable `$GLOBALS['current_view']`. Here's the code:
```php
// Get current view object and its arguments
$view = $GLOBALS['current_view'];
$args = $view->args;
$term = $args[0];
// Retrieve array of unique dates
$timezone = _views_get_timezone();
$result = db_query("
SELECT DISTINCT DATE_FORMAT(FROM_UNIXTIME(n.created+$timezone), '%Y%m%%d')
AS date
FROM {node} n
INNER JOIN {term_node} tn ON n.nid = tn.nid
WHERE tn.tid = %d ORDER BY n.created DESC", $term);
while ($date = db_fetch_object($result)) {
$date_list[] = $date->date;
}
// Find current and last positions
$current = array_search($args[1], $date_list);
$last = count($date_list) - 1;
// Find previous date
if ($current == 0) {
$prev = NULL;
}
else {
$prev = $date_list[$current-1];
}
// Find next date
if ($current == $last) {
$next = NULL;
}
else {
$next = $date_list[$current+1];
}
print theme('date_pager', $prev, $next, $term);
```
Essentially what we're doing is grabbing a list of **each** unique date that a node was created, and tossing that into an array. We figure out if we're on the first or the last date in the list, and pass the next/previous links into the date pager, respectively.
Finally, here's the theme\_date\_pager function (I just stuck this in a small custom module):
```php
/**
* Displays date pager at the bottom of the taxonomy_by_date view
*
* @param $prev
* A string containing the previous date, in the form of
* YYYYMMDD, or NULL if no previous date
* @param $next
* A string containing the next date, in the form of
* YYYYMMDD, or NULL if no next date
* @param $term
* The term that's being filtered
* @return
* A string containing the HTML of the date pager
*
* @ingroup themeable
*/
function theme_date_pager($prev, $next, $term) {
$output = '';
$links = '';
if ($prev) {
$links .= l(t('< ') . format_date(strtotime($prev), 'custom', 'F j, Y'),
'taxonomy_by_date/'. $term .'/'. $prev,
array('class' => 'pager-previous', 'title' => t('Go to previous date')));
}
if ($next) {
$links .= l(format_date(strtotime($next), 'custom', 'F j, Y') . t(' >'),
'taxonomy_by_date/'. $term .'/'. $next,
array('class' => 'pager-next', 'title' => t('Go to next date')));
}
if (!empty($links)) {
$output .= '';
$output .= ''. $links .'';
$output .= '';
}
return $output;
}
```
This displays links like: < June 16, 2006 June 14, 2006 >
And there you have it! As promised, you can also download [an export of the view](https://www.lullabot.com/files/taxonomy_by_date.view_.txt).
### What next?
After talking with Eaton a bit on IRC, we agreed that it seems like some type of "browser" view type could come in very handy for handling various requirements that come up. I've created a ["browser" view type feature request](http://drupal.org/node/70779) at Drupal.org to discuss that. If anyone has any implementation ideas, feel free to jump in!
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Git Best Practices: History Viewing Tips and Displaying Branch Context"
url: "/articles/git-best-practices-history-viewing-tips-and-displaying-branch-context"
type: article
date: 2011-03-31
updated: 2023-11-02
---
# Git Best Practices: History Viewing Tips and Displaying Branch Context
# Git Best Practices: History Viewing Tips and Displaying Branch Context
What's happenin'?
By
[ Jerad Bitner ](/about/jerad-bitner)
March 31, 2011
The Git version control tool provides a number of ways to examine the history of a particular file or directory. When rolling a new version of a Drupal module, or documenting changes that have been made to a site, capturing that information can save quite a bit of time. In a *perfect* world, you would already have a /patches directory in your repository root with the individual changes and a README.txt describing each one, or even a complete Drush .make file that captures all of the module versions and patch files used to build your project. But the world isn't always perfect... And when the time comes to track down what changes have been made to the code in your repository, using Git's history log is the solution. The quickest way to review history is through command line.
```
$ git log []
```
This prints out a quick list in the following format:
```
commit 86ea8974821d6adaa198901b0fd5a3c046ade59c
Author: Jerad Bitner
Date: Wed Mar 23 19:37:20 2011 -0600
adding the '/user' path with the same form id and using the paths as the selectors in the jquery
commit 7e965dd170d812324bc65833f67cc33d3dddc375
Author: Jerad Bitner
Date: Wed Mar 23 16:52:09 2011 -0600
reload the window after a form submission
```
Nice for a quick view, but maybe you want something a little prettier.
```
$ git log --oneline []
```
This prints out a quick list in an even shorter format:
```
86ea897 adding the '/user' path with the same form id and using the paths as the selectors in the jquery
7e965dd reload the window after a form submission
927f2b6 adding an administrative page to customize width/height per form - also an alter hook to add other forms to the list (maybe I should call this modalframe_forms?)
c5979f2 initial commit of modalframe_login
```
I also like to use [Gitx](http://gitx.frim.nl/) for my history viewing needs. It's a nice little GUI tool for the Mac that makes history viewing a more pleasurable experience IMHO. With it, you can quickly see branch history, merges, and any timeline changes. It also has command-line integration so that you can execute it from within your Git repository's checkout.
```
$ cd ~/git.drupal.org/modalframe_login
$ gitx
```
If you click on the 'Tree View' button at the bottom, you can get a list of all of the files and directories within your repository, and by right clicking on any file or directory, can then view the full history of just that file or directory.
So if you need to check what's going on with just one file or directory, Gitx is great. There are many other awesome GUI's out there now for Git, but I tend to stick to the basics.
### What branch am I on?
Another great tool for visualizing where I am is a simple script in my ~/.bash\_profile that parses the current directory for a .git directory and if found, will print out the name of the branch in my shell's command prompt.
```
##############
## Bash prompt
function parse_git_branch {
git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
}
function proml {
local BLUE="\[\033[0;34m\]"
local RED="\[\033[0;31m\]"
local LIGHT_RED="\[\033[1;31m\]"
local GREEN="\[\033[0;32m\]"
local LIGHT_GREEN="\[\033[1;32m\]"
local WHITE="\[\033[1;37m\]"
local LIGHT_GRAY="\[\033[0;37m\]"
case $TERM in
xterm*)
TITLEBAR='\[\033]0;\u@\h:\w\007\]'
;;
*)
TITLEBAR=""
;;
esac
PS1="${TITLEBAR}\
$BLUE[$RED\$(date +%H:%M)$BLUE]\
$BLUE[$RED\u@\h:\w$GREEN\$(parse_git_branch)$BLUE]\
$GREEN\$ "
PS2='> '
PS4='+ '
}
proml
```
The result of which is to turn this:
```
[08:19][sirkitree@sirkitbox:~/git.drupal.org/modalframe_login]$
```
into this:
```
[08:20][sirkitree@sirkitbox:~/git.drupal.org/modalframe_login(master)]$
```
and if I switch into the 6.x-1.x branch from the master branch, then you can see that the command line indicator shows me this on each new line:
```
[08:21][sirkitree@sirkitbox:~/git.drupal.org/modalframe_login(master)]$ git checkout 6.x-1.x
[08:21][sirkitree@sirkitbox:~/git.drupal.org/modalframe_login(6.x-1.x)]$
```
Branch indicators have really become an integral part of my workflow, one I don't think I could live without. I hope you find these tips useful, and I'll be bringing you more as a part of a series as I work with Git on Drupal.org. Do you have any tips to share with us?
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "PhpStorm for Drupal"
url: "/articles/phpstorm-for-drupal"
type: article
date: 2013-04-04
updated: 2021-01-12
---
# PhpStorm for Drupal
# PhpStorm for Drupal
Setting up PhpStorm for Drupal's Coding Standards
By
[ Angus Mak ](/about/angus-mak)
April 4, 2013
Debugging Drupal modules and themes (or Drupal core itself) can be challenging without a good IDE. After using numerous IDE and text editors, [PhpStorm](https://www.jetbrains.com/phpstorm) has earned its place as my primary IDE for almost anything Drupal-related. By default, PhpStorm is as Drupal friendly as most other IDEs. However, some of its default syntax and formatting settings conflict with the [Drupal Coding Standards](http://drupal.org/coding-standards). Here are a few tips to make PhpStorm play even better with Drupal.
## Keymap
Although not related to Drupal, the Keymap is the first thing I change. By default, PhpStorm comes with keyboard shortcuts I find very unnatural. Under Preferences, scroll down to select Keymap on the left, and select the keymap that suits your needs. Mac OS X 10.5+ feels the most intuitive to me. You can also further customize the keyboard shortcuts to each of the actions.

## Syntax and Formatting
Next, let's fix the code style. Drupal recommends less than 80 characters per line, and PhpStorm lets us set that as its default.

This gives you a nice solid line in the editor showing where the 80 character limit is. I leave the "Wrap when typing reaches right margin" setting unchecked; otherwise, PhpStorm will automatically insert line breaks when I hit 80 characters. Drupal's coding standards *do* allow more than 80 characters in some situations, so it's best not to require it.
## Syntax
Next, we'll work on PHP syntax. PhpStorm conveniently comes with a predefined style for Drupal that we can use as a starting point.

PhpStorm's "Tabs and Indents" settings should all be set to *two characters* to match Drupal's indentation standards. On its "Spaces" tab, make sure the "After type cast" option is selected.

On the "Wrapping and Braces tab", make sure these settings are set correctly:
- *Keep control statements in one line* should be unchecked
- *Place braces in class declaration* should be unchecked
- *Place braces in function declaration* should be unchecked
- *Always force braces for if() statements* should be checked
- *Force braces for while() statements* should be checked
- *Else on new line for if() statements* should be checked
- *'While' on new line for do...while() statements* should be checked
- *'Catch' on new line for for try() statements* should be checked
- *Chop down Array initializer if long* should be checked
- *New line after ( for Array initializer* should be checked
- *Place ) on new line for Array initializer* should be checked


Finally, on the "Other" tab, be sure that "Convert True/False to uppercase" and "Convert Null to Uppercase" are checked as well. Those changes should be all we need to match Drupal's PHP syntax. You might also want to set up syntax for other languages like HTML, JavaScript, CSS and any CSS preprocessors you may use.
## PHP CodeSniffer
I like to install the PHP CodeSniffer to give myself extra warnings about some Drupal Coding Standards violations. To set that up for yourself, follow the [instructions to install PHP CodeSniffer](http://drupal.org/node/1419988) and the Drupal coding standards definitions. Once you have phpcs ready, set up PhpStorm's Code Sniffer settings to point at `/usr/bin/phpcs`. Also, make sure PhpStorm is set up to use the PHP Code Sniffer for code inspection in its "Inspections" settings.

Now you should get some nice warnings as reminders for keeping up the coding standards.

## Tips and Tricks
If changing the font size is not something you often do, I would also recommend turning off the "Mouse wheel zoom" feature. I noticed a lot of accidental zooming in and out when using Magic Mouse on OSX. Even when the Mouse Wheel zoom is turned off, I can still use Pinch-to-Zoom on the trackpad when I need to. You can also set up your own keyboard shortcut under Keymap in Preferences.

## Debugging
One of the biggest advantages to using an IDE like PhpStorm is integration with a good PHP debugger. If you want to XDebug to examine Drupal's internals, see [our article about configuring Xdebug](https://www.lullabot.com/articles/configuring-xdebug-on-osx-mountain-lion). It should give you everything you need to set up PhpStorm for serious debugging.
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "A Drupal Module Developer's Guide to SimpleTest"
url: "/articles/a-drupal-module-developers-guide-to-simpletest"
type: article
date: 2008-01-01
updated: 2016-04-07
---
# A Drupal Module Developer's Guide to SimpleTest
# A Drupal Module Developer's Guide to SimpleTest
Unit testing with Drupal
By
[ Angie Byron ](/about/angie-byron)
January 1, 2008
## Introduction
Have you ever experienced any of the following symptoms?
- You have changed code at one place in your module that broke things in another place and you spent *way* too long trying to track down the problem (or worse, didn't find it at all until your users started yelling?).
- You frequently (or not frequently enough ;)) waste dozens of minutes clicking around on all kinds of forms in order to test your module.
- Your module doesn't have enough users to act as a QA team, so basically you're it.
- You are paranoid to change anything in your code once it's working since there are ooky edge cases, and you can't ever remember what they all are.
- Thinking about developing your module gives you nausea and/or shortness of breath. ;)
If so, this article describes a cure: creating unit test coverage for your module using [SimpleTest](http://drupal.org/project/simpletest).
[Revision Moderation](http://drupal.org/project/revision_moderation) is a module I wrote about a year ago to accomplish one "simple" task: leave the existing copy of a node there when new revisions are created, so that later revisions are not immediately visible until they're approved. People are depending on this module to protect their sites from vandalism, so it's vitally important that the module is working as expected at all times.
I was therefore experiencing all of the above, and finally got sick of it and decided to do something about it. Here's how I created unit test coverage for Revision Moderation module, and how you can do the same for your module. Note #1: If you have not yet read Robert's excellent article [An Introduction to Unit Testing in Drupal](https://www.lullabot.com/articles/an-introduction-to-unit-testing-in-drupal), please do so! This article assumes you have the basics of that one down, have SimpleTest module installed, yadda yadda.
Note #2: These tests are against the 6.x version of the module. Some small things will be different for 5.x, such as permission names.
Note #3: The full .test file is available as an attachment below. Use it to follow along with the code snippets, or to copy/paste from liberally when creating unit tests for your own modules.
## Sketching out some skeleton code
There are two components to SimpleTest-enabling a Drupal module: implementing hook\_simpletest in your module so that SimpleTest can execute its tests, and creating one or more .test files which hold the tests themselves.
Here's the hook\_simpletest I added to revision\_moderation.module, which tells it to look in a sub-directory of revision\_moderation called "tests" for any test files.
```php
/**
* Implementation of hook_simpletest().
*/
function revision_moderation_simpletest() {
$dir = drupal_get_path('module', 'revision_moderation') .'/tests';
$tests = file_scan_directory($dir, '\.test$');
return array_keys($tests);
}
```
Then, in your module's *tests* directory create a .test file to hold some stub code for your tests:
```php
// $Id$
/**
* Unit tests for Revision Moderation module.
*/
class RevisionModerationTest extends DrupalTestCase {
/**
* Drupal SimpleTest method: return metadata about the test.
*/
function get_info() {
return array(
'name' => t('Revision Moderation'),
'desc' => t('Executes test suite for Revision Moderation module.'),
'group' => t('Revision Moderation module'),
);
}
}
```
Now, when you go to **Administer >> Site building >> SimpleTest unit testing** (admin/build/simpletest), you should see a section for your module:

## So what does your module *do*, anyway?
The first task is just to make a simple list of things that your module does. This list will translate directly to SimpleTest tests.
For Revision Moderation module, this list is relatively small:
- If a given content type has the "Revisions go into moderation" checkbox checked, the currently published version should *stay* published, while the user's changes should go into a new revision.
- If a user has created a revision, and that revision hasn't yet been published, the node edit form should fetch the unpublished revision for that particular user when populating the fields.
- If the "Exempt administrators from revision moderation" checkbox is enabled, then any users with "administer nodes" permissions should have their revisions published immediately.
Add some stub functions to your .test file at the bottom to act as place-holders for each of your tests. The naming convention dictates that they must start with lowercase "test" and then initial capitals to describe what will be tested within.
```php
/**
* Ensure moderated revisions are not immediately published.
*/
function testModeration() {
// TODO: Put some code here.
}
/**
* On edit, ensure that in-moderation revision shows up in the form.
*/
function testModerationEdit() {
// TODO: Put some code here.
}
/**
* Ensure exemption option is working properly.
*/
function testModerationExemption() {
// TODO: Put some code here.
}
```
We'll start filling these out in more detail in a moment.
## Setting the stage: Preparation tasks
Often times, there need to be certain things in place before testing can begin. For example, Revision Moderation module needs the following:
- The Revision Moderation module has to be enabled.
- We need a "moderated" content type with both the "Revisions go into moderation" and "Create new revision" options enabled.
- We also need an "unmoderated" content type \*without\* "Revisions go into moderation", but with "Create new revision."
- Finally, we need two different users: "normal" users whose revisions go into moderation, and "administrator" users who can bypass revision moderation if the option is set.
We can use the built-in SimpleTest function `setUp()` to handle these kinds of preparation tasks. This function executes before each test is run. If your module requires setup tasks, copy and paste the following after your `get_info()` function:
```php
/**
* SimpleTest core method: code run before each and every test method.
*
* Optional. You only need this if you have setup tasks.
*/
function setUp() {
// TODO: Put your code here.
// Always call the setUp() function from the parent class.
parent::setUp();
}
```
There is also a sister function to `setUp()` called `tearDown()` which can handle undoing some of the tasks done by the tests. By using the SimpleTest API functions, however, most of this is handled for you. This function is executed after each test is run.
```php
/**
* SimpleTest core method: code run after each and every test method.
*
* Optional. You only need this if you have setup tasks.
*/
function tearDown() {
// TODO: Put your code here.
// Always call the tearDown() function from the parent class.
parent::tearDown();
}
```
If there are certain values you're going to need to access from every test, you can create variables for them. For example, the tests for Revision Moderation module need access to four different values: the piece of moderated content, the piece of unmoderated content, the normal user, and the administrative user. I therefore added the following variables above the `get_info()` function:
```php
/**
* A global piece of moderated content.
*/
var $moderated_content;
/**
* A piece of unmoderated content.
*/
var $unmoderated_content;
/**
* A global basic user who is subject to moderation.
*/
var $basic_user;
/**
* A global administrative user who may bypass moderation.
*/
var $admin_user;
```
These will get populated a little bit later.
## A Tour of SimpleTest Functions
Here's Revision Moderation module's setUp() function in bite-sized chunks, so you can get a sense of how to do similar things in your own modules.
### Enabling/Disabling modules
The DrupalTestCase object comes with two methods to handle enabling or disabling modules: `drupalModuleEnable()` and `drupalModuleDisable()`, respectively. You enable modules if they're required in order to execute your tests, and you disable modules if they're known to cause conflicts.
For example, here's a line of code in `setUp()` which enables the Revision Moderation module:
```php
// Make sure that Revision Moderation module is enabled.
$this->drupalModuleEnable('revision_moderation');
```
An astute reader might point out that Drupal already has functions for enabling and disabling modules: `module_enable()` and `module_disable()`. So why not just use those? Because these special functions will keep track of the state of the modules before the tests ran and ensure that they're returned to that state when they complete.
### Setting variables
Often you need to do things like set publishing options on a content type, or toggle on a setting from your module. Use the `drupalVariableSet()` function.
Here are the lines of code to set Revision Moderation's content type options:
```php
// Enable publishing options on the Page content type, our "moderated"
// example:
// - Published = TRUE
// - Create new revision = TRUE
// - New revisions in moderation = TRUE
$options = array(
'status',
'revision',
'revision_moderation',
);
$this->drupalVariableSet('node_options_page', $options);
// Enable publishing options on the Story content type, our
// "unmoderated" example:
// - Published = TRUE
// - Create new revision = TRUE
// - New revisions in moderation = FALSE
$options = array(
'status',
'revision',
);
$this->drupalVariableSet('node_options_story', $options);
```
Again, even though Drupal already has a `variable_set()` function, using `drupalVariableSet()` is better, because it will return the variables back to their original state when the tests are completed.
### Working with Users, Roles, and Permissions
The method `drupalCreateUserRolePerm()` creates a user with a random name and adds them to a role with a given set of permissions. When the tests are completed, users created with this function are automatically removed, along with their content.
Once you've created a user with `drupalCreateUserRolePerm()`, you can then use `drupalLoginUser()` to impersonate a user with that permission set. This function will only work with users created by `drupalCreateUserRolePerm()`, because it needs access to the `raw_pass` value in order to login.
Here's the chunk of code that creates the basic and administrative users for Revision Moderation module:
```php
// Create a basic user, which is subject to moderation.
$permissions = array(
'access content',
'create page content',
'edit own page content',
'create story content',
'edit own story content',
);
$basic_user = $this->drupalCreateUserRolePerm($permissions);
// Create an admin user that can bypass revision moderation.
$permissions = array(
'access content',
'administer nodes',
);
$admin_user = $this->drupalCreateUserRolePerm($permissions);
// Assign users to their test suite-wide properties.
$this->basic_user = $basic_user;
$this->admin_user = $admin_user;
```
That last chunk assigns the user accounts to the variables that we created above, which are visible throughout the test suite, so that we can access them outside of the `setUp()` function.
### Creating content
The final thing we need for our setup stuff is to create a couple of nodes: one that's in revision moderation and one that's not.
```php
// Login as basic user to perform initial content creation.
$this->drupalLoginUser($this->basic_user);
// Create a moderated piece of content.
$edit = array();
$edit['title'] = $this->randomName(32);
$edit['body'] = $this->randomName(32);
$this->drupalPostRequest('node/add/page', $edit, t('Save'));
$moderated = node_load(array('title' => $edit['title']));
// Create an unmoderated piece of content.
$edit = array();
$edit['title'] = $this->randomName(32);
$edit['body'] = $this->randomName(32);
$this->drupalPostRequest('node/add/story', $edit, t('Save'));
$unmoderated = node_load(array('title' => $edit['title']));
// Assign nodes to their test suite-wide properties.
$this->moderated_content = $moderated;
$this->unmoderated_content = $unmoderated;
// Logout as basic user.
$url = url('logout', array('absolute' => TRUE));
$this->get($url);
```
A couple salient points from this snippet of code:
- The nodes are created by the basic user, logged in using the `drupalLoginUser()` method. That way, moderation should kick in on the page content type.
- The nodes' Title and Body are both set to some random 32-character string using the `randomName()` method. This is necessary, because the nodes need to be pulled back out later by title (since the node ID won't be known), and this way there's a pretty good chance of that title being unique.
- The `drupalPostRequest()` function is used to submit the form at node/add/page and node/add/story with the given values. You can also add other things to the $edit array if need be in order to test your module.
- Finally, in order to log the user out again, the `get()` method is used to retrieve the logout URL. If this isn't done, errors will pop up if `drupalLoginUser()` is called twice in a row.
Note that there's currently a patch in the SimpleTest queue to add a nice [drupalCreateNode()](http://drupal.org/node/203825) function instead of doing this manual process.
## All right! Let's start testing this thing, already!
Testing your module involves re-using some of the above API functions, as well as a new type of function called an *assertion*. This is basically a check to ensure that a thing you want either does or does not exist. If everything goes according to plan, the test passes. If it doesn't, the test fails.
Each assertion function takes at least two arguments:
- First, one or more arguments that are things to compare; text that ought to not be there, numbers that ought to match, etc.
- Finally, the last argument is always an optional string to display if the test fails. It should provide some clue as to what the test was looking for when it failed.
Let's take a look at the full `testModeration()` as an example.
```php
/**
* Ensure moderated revisions are not immediately published.
*/
function testModeration() {
// Login as basic user.
$this->drupalLoginUser($this->basic_user);
// Edit moderated piece of content.
$node = $this->moderated_content;
$edit = array();
$edit['title'] = $this->randomName(32);
$edit['body'] = $this->randomName(32);
$this->drupalPostRequest("node/$node->nid/edit", $edit, t('Save'));
// Ensure that changes do NOT appear.
$this->assertWantedRaw(t('Your changes have been submitted for moderation.'), t('Moderation message not found'));
$url = url("node/n/$node->nid", array('absolute' => TRUE));
$contents = $this->get($url);
$this->assertNoText($edit['body'], t('Edited content found on moderated node.'));
// Edit the unmoderated piece of content.
$node = $this->unmoderated_content;
$edit = array();
$edit['title'] = $this->randomName(32);
$edit['body'] = $this->randomName(32);
$this->drupalPostRequest("node/$node->nid/edit", $edit, t('Save'));
// Ensure that changes DO appear.
$url = url("node/$node->nid", array('absolute' => TRUE));
$contents = $this->get($url);
$this->assertText($edit['body'], t('Edited content not found on unmoderated node.'));
}
```
There are a few new functions here worth looking at:
- `assertWantedRaw()`: After a POST request, you can use this function (and its sister function `assertNoUnwantedRaw()`) to check for the existence (or non-existence) of certain text. Useful when looking for text set by `drupal_set_message()` after submission of a form.
- `assertText()`: Checks to see whether the given text appears within the current page. There's also `assertNoText()`.
These functions are used throughout the test in order to ensure that things are working properly. `assertWantedRaw()` is used to make sure that the `'Your changes have been submitted for moderation.'` message appears after the basic user submits the form. `assertText()` and `assertNoText()` are used to check for the existence/non-existence of the node body, depending on the state of moderation.
A full list of assertion functions may be found in the various chapters of the [SimpleTest documentation](http://simpletest.org/en/overview.html).
## Checking test results
Before committing anything, head to **Administer >> Site building >> SimpleTest unit testing** (admin/build/simpletest), check off "Select all tests in this group" for your module, and hit the "Begin" button. With luck, your output will look something like this:

You'll note that the number of tests executed will be *way* above the number that you did yourself. This is because each time you run one of the SimpleTest API functions, it does several assertX() functions itself.
If something goes wrong, you might see something like this:

If this happens (and it might; I've caught a couple core bugs during the creation of these tests :)), then scroll down the list until you find one outlined in red to figure out where things went awry. Sometimes it means your test needs to be updated in order to reflect changes upstream, and other times it means something legitimately broke. Congratulations, you just saved yourself the pain, embarrassment, and ridicule of a buggy check-in. ;) Fix up those bugs!
Once your tests are working properly, ideally you from now on do what is called **test-driven development**, where whenever you go to add a new feature/fix a bug in your module, you write your tests *first*, then fill in code until they pass. This both forces you to think through what it is you actually want the feature to entail, as well as ensures that you know when it's actually doing that thing. :) Once you get in the habit of doing this, it's amazing how freeing it can be -- suddenly your code feels invincible. ;)
## Conclusion
Unit testing is smart, it saves you time, and it gives you peace of mind:
- Tests document what the heck it is your module's actually supposed to do.
- Tests document all the various edge cases that break can and have broken your module in the past.
- Tests help automate the extensive manual process that you'd normally have to go through to ensure things are working.
In this article we covered the following topics:
- Adding a `hook_simpletest()` to your module so it's picked up by SimpleTest.
- Creating a .test file for your module to hold your tests.
- How the various SimpleTest API functions can be used to prepare your module for testing.
- What assertions are and how they can be used to check your code.
Happy testing!
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Automate Your Life with Phing"
url: "/articles/automate-your-life-with-phing"
type: article
date: 2012-02-15
updated: 2021-01-12
---
# Automate Your Life with Phing
# Automate Your Life with Phing
Phing is a PHP tool that allows us to automate processes -- typically, it's used for building and deploying software.
By
[ Sally Young ](/about/sally-young)
February 15, 2012
[Phing](https://www.phing.info/trac/) is a PHP tool that allows us to automate processes -- typically, it's used for building and deploying software. It's similar in functionality to the Apache Ant project and uses XML files to execute tasks that are defined as PHP classes. Best of all, it has a tonne of cool tasks already baked in! These include tasty things such as unit testing, file system operations, code sniffer integration, SQL execution, shell commands, 3rd party services and version control integration.
### Ok, that sounds good. How do I use it?
You can install Phing easily through [PEAR](https://pear.php.net/).
```
$> pear channel-discover pear.phing.info
$> pear install phing/phing
```
The default name for a Phing build file is build.xml. This file contains a number of targets, which are like different functions that you might find in a PHP file.
``
<project name="MyProject" default="hello">
<!-- ============================================ --> <!-- (DEFAULT) Target: hello --> <!-- ============================================ --> <target name="hello" description="Says Hello"> <echo msg="Hello, world!" /> </target>
<!-- ============================================ --> <!-- Target: cheese --> <!-- ============================================ -->
<target name="cheese" description="Expresses feelings about cheese"> <echo msg="I like cheese" /> </target>
</project>
Let's go ahead and run the above build, in which we've specified the default target to be "hello", by running the "phing" command from the directory where this file is saved,:
```
$ phing
Buildfile: /Users/sal/phing/build.xml
MyProject > hello:
[echo] Hello, world!
BUILD FINISHED
Total time: 0.6856 seconds
```
Now, try and run the "cheese" target
```
$ phing cheese
Buildfile: /Users/sal/phing/build.xml
MyProject > cheese:
[echo] I like cheese
```
### Yo Dawg, I heard you like targets
Running multiple targets can be done by separating them with a space
```
$ phing cheese hello
```
We can also set targets to be dependencies of other targets. Let's modify our default.
```
```
```
$ phing
Buildfile: /Users/sal/phing/build.xml
MyProject > cheese:
[echo] I like cheese
MyProject > hello:
[echo] Hello, world!
```
### Tasks and Properties
Tasks are the actions that our targets are going to step through so we can get stuff done- we've already used the EchoTask above. We can pass them string, integer and boolean parameters as well as more complex data types such as lists of files. Tasks are defined as PHP classes and it's straightforward to roll your own should you need to, however most of what you'll need is probably already there. Documentation on all the built in Tasks can be found in the [Phing User Guide](https://docs.phing.info/docs/guide/stable/). You can also check out the PHP source for all the tasks which will be located in your PHP installation's lib folder.

Properties are the equivalent of variables and let us use and manipulate stored values in our tasks.
```
```
### A simple Drupal deploy
We're going to clone the Drupal core repository and deploy it to a server. For this deploy, I'm going to grab Drupal from http://git.drupal.org/project/drupal.git (see http://drupal.org/project/drupal/git-instructions). We'll keep it simple for this example by having everything in one task, but you should split tasks into reusable chunks as you would with PHP code/functions.
```
```
This may fail for you if you don't have PEAR's VersionControl\_Git package installed, go ahead and install that if you need to
```
$ pear install VersionControl_Git-alpha
```
This is going to take several minutes since cloning a repository with Git will retrieve the entire history of the Drupal project, so it's time to go make yourself a cup of tea. If you want to understand more about what exactly git is retrieving at this point, I recommend Blake Hall's excellent [Vision for Version Control](https://drupalize.me/videos/vision-version-control) video on Drupalize.me. For the sake of saving us some time whilst we're learning, we're going to reuse this clone so we don't have to wait each time we run the Phing build file (you could try "*git archive --remote*" if your remote repository supports that). We'll do it by checking to see if the drupal/.git directory exists or not. For your own deployment scripts it's better to make no assumptions about files that are already on your system and start from a clean checkout of your code.
```
```
Now you can run "*phing getDrupal*" if you want to remake your git repository.
### Steps for Deployment
It usually helps to write the steps down in a list before building the XML.
1. Get Drupal
2. Switch to the version of Drupal I want
3. Add database credentials to settings.php
4. Put the code in the webroot (and remove unwanted files)
5. Make the code live
I then like to put these in as comments and fill them out. You can follow them in the full build file below.
```
```
```
$ phing
Buildfile: /Users/sal/phing/build.xml
[resolvepath] Resolved ./drupal to /Users/sal/phing/drupal
DrupalDeploy > deploy:
[gitcheckout] git-checkout command: /usr/bin/git checkout '7.10'
[gitcheckout] git-checkout: checkout "/Users/sal/phing/drupal" repository
[gitcheckout] git-checkout output:
[delete] Deleting: /Users/sal/phing/drupal/sites/default/settings.php
[copy] Copying 1 file to /Users/sal/phing/drupal/sites/default
[append] Appending string to /Users/sal/phing/drupal/sites/default/settings.php
[chmod] Changed file mode on '/Users/sal/phing/drupal/sites/default/settings.php' to 444
[copy] Created 120 empty directories in /Users/sal/Sites/mysite/drupal-2012_01_16__13_13_55
[copy] Copying 1035 files to /Users/sal/Sites/mysite/drupal-2012_01_16__13_13_55
[symlink] Linking: /Users/sal/Sites/mysite/drupal-2012_01_16__13_13_55/drupal to /Users/sal/Sites/mysite/live
```
Run it a few times and you should see your new code being deployed each time.

### Custom Tasks
One of the best things about Phing is that we can build our own custom tasks using PHP. See the [Extending Phing](https://docs.phing.info/docs/guide/stable/chapters/ExtendingPhing.html) docs for more information. Custom tasks can be placed in a folder called "tasks" next to your build XML.

Here's an example of a task and build file that prints a random string.
```
require_once 'phing/Task.php';
class RandomStringTask extends Task
{
private $propertyName;
/**
* Set the name of the property to set.
* @param string $v Property name
* @return void
*/
public function setPropertyName($v) {
$this->propertyName = $v;
}
public function main() {
if (!$this->propertyName) {
throw new BuildException("You must specify the propertyName attribute", $this->getLocation());
}
$project = $this->getProject();
$c = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxwz0123456789";
$length = 12;
for(;$length > 0;$length--) $s .= $c{rand(0,strlen($c))};
$random = str_shuffle($s);
$this->project->setProperty($this->propertyName, $random);
}
}
```
```
```
```
$ phing
Buildfile: /Users/sal/Downloads/phing/build.xml
Random > random:
[echo] 0uH282IFqirG
```
### Jenkins Usage
Jenkins is a very popular open-source continuous integration server that executes and monitors repeated jobs such as testing code, building software and running cron jobs. The good news is it has Phing support and you can enable it under the Plugin Manager.
Once you've enabled the plugin, you'll see an "Invoke Phing targets" build step option when you configure a job. From there you can specify which Phing targets to run and pass values of properties to your build.

You can pass a number of very useful things from the Jenkins build environment into your Phing script, such as the workspace location and build tag.

A common use for this is deploying automated builds to test environments every time code is committed to version control.
### Homework
- Use an external property file to hold credentials or if using Jenkins use secret files
- Use drush commands
- Deploy code to remote servers
- Switch the live symlink with an atomic operation by creating a temporary symlink and then using mv
- Automate as much as possible! e.g. running database updates, getting contrib and custom modules
Published in:
- [ System Administration ](/topics/system-administration)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Sending a Drupal Site Into Retirement"
url: "/articles/sending-a-drupal-site-into-retirement"
type: article
date: 2020-02-13
updated: 2020-02-26
---
# Sending a Drupal Site Into Retirement
# Sending a Drupal Site Into Retirement
Ideas for how to gracefully retire (or semi-retire) a Drupal site.
By
[ Karen Stevenson ](/about/karen-stevenson)
February 13, 2020
***Note:** This article was originally published on May 15, 2014. We are republishing this article (with updates) as the first in a series detailing why and how to retire a Drupal site. Comments from the original will appear unmodified.*
Drupal is a great tool for creating a website. It has a lot of modules and functionality that enable building interesting and complex features, but sometimes those sites lose their relevancy. However, there are several types of sites that might be repurposed from active Drupal sites to static sites:
- A site for an event that has passed where event information and session summaries should remain available but no longer actively require maintenance.
- A site that is infrequently or never updated, where the work of maintaining the site takes more time than the site is worth.
- A site that will never be migrated from an older version of Drupal because the next version of the site will start with a clean slate. If old content is still relevant and should be archived, the original content can be preserved as static pages while creating new, different pages in a new Drupal instance.
- A sub-section of a site that wonât be updated anymore, especially if the sub-section has a distinctive theme or layout that is no longer needed, and doesnât have to match the rest of the site.
## Where to Host a Static Site
When transforming a Drupal site to a static site, a decision needs to be made as to where to host it. A cheap or free option for hosting is great, especially if the purpose of this project is just to preserve the siteâs content without doing any more work on it. One nice option is to use [Github Pages](https://pages.github.com/), to host a Jekyll or static site for free.
## Preparing to Go Static
There is some preparation necessary for converting any dynamic Drupal site into a static site.
### Update Views
- Remove ajax functionality.
- Remove all exposed views filters.
- Remove clickable table column headers.
- Edit views to remove pagination where possible to avoid the need to deal with static paginated results. Display all results wherever possible.
- Edit views fields and remove links to content that wonât be available in the static site.
### Other Changes
- Switch to a non-javascript theme.
- Remove login and user blocks.
- Turn on JS and CSS aggregation.
- Disable and remove all forms.
- Remove the search form and turn search off.
- Turn off comment options on all content types, close new comments on all existing content.
- Remove links to unwanted pages in the static site, like links to authors.
- Update formatters to remove âlink to contentâ options if that linked content in the static site are not needed.
- Edit permissions and make sure anonymous user permissions reflect exactly whatâs desired in the static site. Examine permissions for things like the ability to add comments or content or view messages.
- If including an XML sitemap, the sitemap needs to be copied from the generating site, followed by a copy/replace to change the base URL of the sitemap to the URL of the static site.
One final task is to make sure no error messages will appear in my static content. The following was found in page.tpl.php on a Drupal 7 site and removed while spidering the site:
``
Finally, review the site as an anonymous user to see if there are any other elements that arenât accessible or won't work if Drupal is not actively running in the background.
See for more ideas.
### Think About Links
One of the biggest problems of transforming a dynamic site into static pages is that the URLs must change. The 'real'URL of a Drupal page is **index.php?q=news**, or **index.php?q=/about**, i.e., there is only one HTML page that dynamically re-renders itself depending on the requested path. A static site has to have one HTML page for every page of the site, so the new URL has to be something like **/news.html** or **/news/index.html**.
Since URLs must change, internal links still need to work. Those internal links are still going to look like **/news**. One way to check the links is to use Apacheâs mod\_rewrite to redirect requests for patterns like **/news** to **/news.html**. Anotheroption is relying on the default behavior of many servers to automatically redirect a request for **/news** to **/news/index.html**.
### A Tale of Two Sites
In these scenarios, a current Drupal site will become the source for a second, new, static, site. The Drupal site could be retired once the static site is created or preserved to create future iterations of the static site. If thereâs a need to preserve it, the original Drupal site could move behind a VPN or into some other protected location to simply serve as a place for editors to make later updates and for administrators to generate future static versions of the site.
### Creating a Static Site
Now that the site is ready, there are several ways to actually create a static version of the site. Watch for more articles that will explore several specific ways to accomplish this.
Published in:
- [ Security ](/topics/security)
- [ System Administration ](/topics/system-administration)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Why not ASP.Net?"
url: "/articles/why-not-aspnet"
type: article
date: 2007-08-27
updated: 2014-05-15
---
# Why not ASP.Net?
# Why not ASP.Net?
By
[ Jeff Eaton ](/about/jeff-eaton)
August 27, 2007
A few days ago, developer Sasha Sydoruk asked [why there aren't more cool startups building web sites based on ASP.Net.](http://www.sashasydoruk.com/2007/08/19/where-are-all-the-cool-startups-that-run-on-aspnet/)
> ...If you checkout the new startups on TechCrunch, it seems like every new startup is something Linux based and is not ASP.NET.
>
> And I really want to know why. If you are a new startup, you have only one shot at it, so you really want to use the best tools available. And it seems like everybody picks anything but ASP.NET, unless you are doing corporate development.
It's an interesting question, and it's a lot different than the usual "Why doesn't everyone use LISP/Eiffel/SmallTalk/My Cool Language" head-scratchers. Microsoft has poured huge amounts of energy into building the .NET framework, and it's fair to say that most Windows software written in the past half a decade or so uses it. Lots of .NET code is being written every day: it is the very opposite of a dead language. It's got a very robust and feature-rich web framework called ASP.NET, designed to compete with Java as a platform for building web applications. It's very powerful. As Sasha observes, though, you just don't hear about new sites being launched on it. Outside of the corporate world, and a handful of select projects like DotNetNuke, it's a pink unicorn: no one's ever seen it f'real. Sasha continues:
> Is it the cost of tools? Hosting cost? Restrictive licensing? Or maybe ASP.NET became "the van" of web development. Safe, bulky and definitely not sexy.
I think it's a little of each, and more. I spent about four years with two companies, first building .NET web apps for the real estate market then building desktop client/server apps for a vertical market. .NET is Java done better: a framework for large carefully deployed vertical solutions. Today I'm a Drupal/LAMP guy and -- despite the kinds of frustrations that any language or platform will give you -- I'm loving it.
ASP.Net faces a couple of key disadvantages.
1. **Cost.** A solid .NET development setup for a team of three or four, plus the licenses for all the server-side software you'll need to run things, can probably buy you half a man-year of developer time. This isn't a HUGE issue if you're launching a startup with funding, but quite a few of the groundbreaking sites out there started out as experimental skunk-works projects. You can cut costs by using free development tools (the C# compiler, after all, is a free download) but you lose a lot of the benefits that come with the platform.
2. **Fewer hackers.** This is very close to the first point, but it's a bit different. The barrier for entry for most of the 'hot' languages on the \*NIX side is low, closer to old-school ASP than the heavy-duty stuff of ASP.NET. That means a smaller pool of hobbyists-turned-coders to feed the project mill. While you probably don't mind the higher barrier for entry if you're hiring a team to develop some enterprise software, most startups don't happen that way. This isn't even a .Net specific issue -- it's more about the changing view of 'scripting languages' when compared to 'real languages' like C, Java, C#, C++, and... well. Whatever flavor of C you can think of.
3. **Not the best fit for web RAD.** .NET is an amazing platform for developing Windows applications. Truly awesome. Unfortunately, ASP.NET tends to err on the side of 'making the web work like WinForms'. When it comes time to web-enable your .NET based client/server application, you'll thank your lucky stars for ASP.NET's familiarity. When you're trying to pound out a prototype of a new social networking site, however, you'll feel like you're dragging a Volvo uphill. It just doesn't make as much sense.
4. **The people are the platform.** It's obviously not universal, but the GPL/MIT/Creative Commons influence that permeates the non-corporate \*NIX side of the development world affects a lot more than just the software itself. Rapid dissemination of best practices, novel tools, and open-sourced solutions to common problems are standard operating procedure in the \*NIX side of the fence. **Ultimately, this is far more important than the details of the specific software platform.** The Open Source world is a 'gift economy' -- you gain karma and status by giving people things of value. Whether that's a new caching API, patches for bugs in an existing framework, or hard-won knowledge about esoteric optimization issues, sharing is built into that development community's fabric. This makes life hell if you're trying to figure out how to sell boxed software, but if you're trying to implement a cool idea and launch a startup in your spare time, the difference is night and day.
When an OSS project or a large-scale LAMP site goes down in flames, knowledge of how to avoid the problem in the future tend to spread fast, benefiting every other site built with the same tools. This accumulated knowledge exists in the .Net world, certainly, but on a much different scale. Microsoft publishes incredibly high-quality materials, and they've engaged the development community in great ways over the past several years. Their podcasts, blogs, publications, and so on are great for .NET developers They still can't compete on the knowledge-sharing front, though, because of the fundamental difference between *Microsoft Publishing Stuff*, and *Lots Of Developers Talking About Stuff.*
These are obviously pretty sweeping generalizations. I don't mean to imply that .Net is bad, or that the languages/platform are inherently flawed. I work primarily with PHP, and man do I miss a lot of the powerful language constructs that I took for granted in C#. In addition, a lot of the above problems become less important if you're part of an organization that already has a major investment in .Net, or an existing talent pool of developers to tap. If I were trying to bootstrap a startup without quite a bit of seed funding, though, I'd be hard-pressed to justify .Net.
Am I talking out of my hat? Do I have blinders on? Have I missed fundamental improvements in the framework made over the last year or two? Quite possibly. But the question was asked, and that's how I see it.
***Update:** Over on TechToolBlog, a poster named Tim has published [some interesting stats on advertised job openings for various platforms.](http://www.techtoolblog.com/archives/ruby-php-aspnet-job-comparison) In his area, there are at least twice as many ASP.NET openings as there are PHP/Rails jobs. A quick peek around the Chicago area confirmed the same numbers, though it might just be a midwest thing. Perhaps that means that there are quite a few large-scale corporate gigs, but fewer high-visibility "Cool Startups" using the platform?*
Published in:
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Git Best Practices: Workflow Guidelines"
url: "/articles/git-best-practices-workflow-guidelines"
type: article
date: 2012-06-14
updated: 2014-05-15
---
# Git Best Practices: Workflow Guidelines
# Git Best Practices: Workflow Guidelines
By
[ Andrew Berry ](/about/andrew-berry)
June 14, 2012
[Git](https://git-scm.com/) is a flexible and powerful version control system. While Git offers significant functionality over legacy centralized tools like CVS and Subversion, it also presents so many options for workflow that it can be difficult to determine what is the best method to commit code to a project. The following are the guidelines I like to use for most software projects contained within a Git repository. They aren't applicable to every Git project (especially those hosted on drupal.org or GitHub), but I've found that they help ensure that our own projects end up with a reasonable repository history.
## Small, logical commits
CVS and Subversion encouraged large, single commits due to limitations in their branching model. This is especially apparent with the Drupal project, where a single-commit patch-based workflow is still in use. There are a few problems with large monolithic commits:
- `git blame` becomes much less useful, as the commit message on given lines of code will usually be something like "Ticket #123: Add progress bars to video series." instead of "Ticket #123: Add updated jQuery UI library for progress bars."
- History of the development of code is lost. With large commits, the process of code development is obscured and not discoverable within git.
- `git bisect` becomes near useless. Even when debugging manually by checking out different commits, having granular commits makes it much simpler to find the lines of code that are actually the source of the bug.
For a Drupal project, a few guidelines I use for commit size include:
- Always add or update modules in their own commits. Never bundle multiple modules in the same commit unless there is tight coupling between the modules.
- When writing new modules, write stub functions and phpdoc comments first. Then, come back to each function and fill them out, committing along the way.
- Always write and commit API-level functions before writing and committing consumers of those functions (such as forms, menu callbacks, and theme code).
- If a commit is more than 100 lines of code, re-evaluate it to see if it's actually a few different logical changes.
- Always commit unrelated bug fixes to your branch as separate commits, or as a separate commit on a new branch.
## Always review code before committing it
It's common for introductory git tutorials to suggest always committing code with `git commit -a` without accurately explaining what the command does. By treating the commit command like how other legacy VCS' do, one of the the most useful features of Git is ignored.
Git introduces a new tool to the version control workflow interchangeably called the "staging area" or the "index". It sounds complicated, but it's actually a very simple tool. In essence, the index is a place to indicate what exactly to include in the next commit. This can be as broad as an entire directory or file, or as granular as specific changes in a file (excluding other unrelated changes). `git commit -a` is just shorthand for telling git to add all uncommitted changes (except for new files) to the index, and then immediately start the commit process.
A much better method for the commit process is to explicitly review what is to be committed. Git includes an awesome tool for this in the form of `git add --patch`. This command will show changes in your code, and ask if they should be committed or not. Sometimes, Git will show a large diff that is actually a few small changes. In this case, "s" will split the diff into smaller chunks that can be individually acted on. If needed, the change can be manually edited to indicate exactly what should be committed. The commit process ends up looking something like this:
1. `git add --patch` to add changes to the index.
2. `git diff --cached` to do a final review of what is to be committed.
3. `git commit` to commit what is in the index.
4. Repeat at step 1 until there is nothing left to commit, or there are uncommitted changes that need more work.
One small caveat is that `git add --patch` will not add brand-new files to the index, but only files that have previously been added to the repository. In that case (such as adding a new module) use `git add` directly to start tracking the new files.
## Never rebase shared commits
Rebasing is a powerful feature of Git that is both awesome and dangerous. Rebasing allows you to rewrite the history of a branch into something new. Most commonly `git rebase` is used to move where a branch appears to start from and to rewrite it to be on top of a new commit. `git rebase --interactive` is also an excellent tool for rewriting commits to amend in typo fixes, rewrite commit messages, or change the order of commits to accurately describe dependencies in code. With GitHub projects in particular, rebasing is commonly used to keep history straight and free of merge commits.
The issue with rebasing shared (or pushed) commits is that doing so requires a "forced push" and automatically invalidates any work others might be doing on that branch of code. It obscures the actual development history of a branch in favour of arbitrary cleanliness of the Git history graph. Rather than forbid rebasing entirely, I have a rule that I never rebase commits that I've pushed to a remote repository. This ensures I don't break anyone else's code that they've committed to my branch, and keeps a log of any bugs or mistakes I've fixed.
## Never delete unmerged remote branches
One serious difference between Git and Subversion is that branch addition and removal are not commits themselves. A Git branch is just a pointer to a commit. While in Subversion a deleted branch can be restored just by checking out an old revision, in Git a commit not pointed to by any branch will eventually be removed by the garbage collection process. So, how do we handle obsolete branches so they can be referenced if needed, without cluttering up the `git branch` listing?
`git merge -s ours obsolete-branch `
This will merge obsolete-branch into the current branch, but completely discarding the changes in the obsolete branch. I usually make it clear in the commit message for the merge that the branch is being discarded instead of a true merge.
`git merge -s ours --edit obsolete-branch `
If the old changes are ever needed for reference or to be resurrected, it's as easy as checking out the last commit on the merged branch and creating a new branch pointing to it.
## Make your Git toolbox your own
Git is easily extensible and configurable. It's possible to add custom git commands to ~/.gitconfig or to write entirely new top-level commands in whatever language you prefer. Git is best thought of as being more like another Unix shell than a monolithic program. I'm partial to [](https://github.com/rtomayko/git-sh>git-sh,%20a%20tool%20that%20exposes%20git%20commands%20directly%20()
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ Deployment ](/topics/deployment)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Customizable Header Images for Your Drupal Theme"
url: "/articles/customizable-header-images-for-your-drupal-theme"
type: article
date: 2010-03-09
updated: 2014-05-15
---
# Customizable Header Images for Your Drupal Theme
# Customizable Header Images for Your Drupal Theme
By
[ Jeff Eaton ](/about/jeff-eaton)
March 9, 2010
### Meet your new friend: theme-settings.php
Drupal themes get a number of configurable settings options for free. For example, most provide toggle switches for the search box, site slogans, user pictures, and so on. Similarly, most provide file uploading widgets to add a custom logo or favicon. These settings are easy: Drupal will add them to the theme's configuration page by default, so it takes no extra work. We want to create our own *custom* setting, however -- one that adds another field to the Theme configuration form. To do that, we'll need to add a new file to the theme: **theme-settings.php**.
This file's only job is to implement a single Drupal hook: hook\_settings(). Its job is to take an array of settings for the theme, and return a custom FormAPI form that allows users to edit them. Drupal will automatically save the values that users enter into it for later retrieval: managing the form itself is this code's only task.
```php
/**
* Implementation of hook_settings() for themes.
*/
function MYTHEMENAME_settings($settings) {
// This ensures that a 'files' directory exists if it hasn't
// already been been created.
file_check_directory(file_directory_path(),
FILE_CREATE_DIRECTORY, 'file_directory_path');
// Check for a freshly uploaded header image, save it to the
// filesystem, and grab its full path for later use.
if ($file = file_save_upload('header_image',
array('file_validate_is_image' => array()))) {
$parts = pathinfo($file->filename);
$filename = 'MYTHEMENAME_header_image.'. $parts['extension'];
if (file_copy($file, $filename, FILE_EXISTS_REPLACE)) {
$settings['header_image_path'] = $file->filepath;
}
}
// Define the settings-related FormAPI elements.
$form = array();
$form['header_image'] = array(
'#type' => 'file',
'#title' => t('Header image'),
'#maxlength' => 40,
);
$form['header_image_path'] = array(
'#type' => 'value',
'#value' => !empty($settings['header_image_path']) ?
$settings['header_image_path'] : '',
);
if (!empty($settings['header_image_path'])) {
$form['header_image_preview'] = array(
'#type' => 'markup',
'#value' => !empty($settings['header_image_path']) ?
theme('image', $settings['header_image_path']) : '',
);
}
return $form;
}
```
In the code above, loosely adapted from Development Seed's Singular theme, we've defined three specific form elements. The first, header\_image, is a file upload widget that lets users post a file: it's pretty straightforward.
The second form element is header\_image\_path. It's a 'value' field that stores the actual location of the uploaded file on the server. It will never be displayed directly to the user, but because it is present in the form, it will be saved in the theme's settings and can be used later.
The third form element is header\_image\_preview. If an image path exists, it simply spits out an image tag containing a preview of the uploaded header image.
The only other code is the snippet at the beginning: it checks to see whether the form has just been submitted, along with a file upload. It ensures that the file itself gets saved correctly, and the path is extracted and handled properly.
### Teach your theme new tricks: template.php
Now that the settings form has been created, administrators can upload new header images at will. The theme doesn't actually *do* anything with those fresh images, however: that's something we'll need to add ourselves. We'll do that in the **template.php** file, where your theme can store custom "Preprocess" functions that prepare variables for use in your HTML templates.
```php
/**
* Implementation of hook_preprocess_page().
*/
function MYTHEMENAME_preprocess_page(&$variables) {
$settings = theme_get_settings('MYTHEMENAME');
if (!empty($settings['header_image_path'])) {
$vars['header_image_path'] = $settings['header_image_path'];
}
else {
$variables['header_image_path'] = path_to_theme().'/head.jpg';
}
}
```
This function is pretty straightforward. When the theme's page.tpl.php file is about to be called, rendering the entire page into HTML, this function will fire -- it gets a chance to add or alter the $variables collection that will be passed on to the template.
To make the magic happen, we're calling theme\_settings(), a Drupal API function that conveniently retrieves all of the settings that our form allowed site administrators to change. Then we look for the header\_image\_path setting, and if it exists, we add the custom path to the $variables array that will be handed off to the template. If the setting doesn't exist -- in other words, if the administrator hasn't uploaded a custom image -- we use the default header image that ships with the theme.

### Pulling it together
We've added the settings page to our theme, we've added a new $header\_image\_path variable to the page template's list of pre-built data, and we're ready to rock. All that's left now is printing it out at the proper location in the theme's page.tpl.php file instead of a hard-coded reference to a static header image.
These same techniques can be used to expose more advanced settings. The [NineSixty Robots](http://drupal.org/project/ninesixtyrobots) theme we build in our [Advanced Theming](http://store.lullabot.com/products/advanced-theming-for-drupal) DVDs, for example, exposes configuration options for a Twitter-driven slogan rotator. Because we're exposing the header image as a page template variable, other Drupal modules can intercept and modify it. For example, a site's custom module could use the hook\_preprocess\_page() function to change the header image depending on the section of the site that's being viewed, or the time of day.
Of course, themes don't have to be that complex! Even simple uses of theme settings -- like this swappable header -- can put a lot of personalization power in the hands of site builders.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Installing Memcached on RedHat or CentOS"
url: "/articles/installing-memcached-on-redhat-or-centos"
type: article
date: 2009-08-20
updated: 2014-05-15
---
# Installing Memcached on RedHat or CentOS
# Installing Memcached on RedHat or CentOS
By
[ Nate Lampton ](/about/nate-lampton)
August 20, 2009
[Memcached](http://www.danga.com/memcached/) is a service that allows entire database tables to be stored in memory, drastically speeding up queries to those tables and alleviating database load. In Drupal, the [Memcached module](http://drupal.org/project/memcache) allows you to store all cache tables in memory.
We've covered how to install Memcached before [on Debian](https://www.lullabot.com/articles/how-to-install-memcache-on-debian-etch) and [on Mac OS X](https://www.lullabot.com/articles/setup-a-memcachedenabled-mamp-sandbox-environment). But server software can vary significantly between sites, and this guide can be used to set up Memcached on Red Hat Enterprise Linux (RHEL) or CentOS, which are architecturally the same.
## Install memcached through RPM
The easiest way to install Memcached is through a package manager such as *yum* or *apt*. However, Memcached is not available from the default collection of packages, so the first thing we need to do is add a new RPM (Red Hat Package Manager) server so that we can install Memcached through yum.
One of the best 3rd-party RPM servers is provided by [Dag Wieers](http://dag.wiee.rs/), which will provide us with up-to-date packages that are not provided by Red Hat directly. The one tricky part of setting up an RPM server is making sure you get the repository that matches your server version and architecture (32-bit or 64-bit). So we need to collect that information first.
From a shell prompt, get the CentOS/RedHat version number:
```
$ cat /etc/redhat-release
CentOS release 5.3 (Final)
```
Then get the server architecture information. This is a typical response for a 32-bit machine:
```
$ uname -a
Linux server1.example.com 2.6.18-92.1.13.el5 #1 SMP Wed Sep 24 19:33:52 EDT 2008 i686 i686 i386 GNU/Linux
```
Or if you have a 64-bit machine you will probably get something like this:
```
$ uname -a
Linux server.example.com 2.6.18-53.1.21.el5 #1 SMP Tue May 20 09:35:07 EDT 2008 x86_64 x86_64 x86_64 GNU/Linux
```
Now install the RPM server that matches your architecture and CentOS version from http://dag.wieers.com/rpm/FAQ.php#B2.
The server I was using when I wrote this was a 32-bit machine running CentOS version 5.x. So my particular server was:
`http://apt.sw.be/redhat/el5/en/i386/rpmforge/RPMS/rpmforge-release-0.3.6-1.el5.rf.i386.rpm `
To install a new RPM server, we can just use the `rpm` command. Note that you **must** find the RPM server string that matches your architecture and software. Do not use the URL unless you have a 32-bit machine running CentOS 5.x, instead get the server that's appropriate from http://dag.wieers.com/rpm/FAQ.php#B2.
```
$ rpm -Uhv http://apt.sw.be/redhat/el5/en/i386/rpmforge/RPMS/rpmforge-release-0.3.6-1.el5.rf.i386.rpm
```
Now we can simply use yum (or apt) to install Memcached:
```
$ yum install memcached
```
Afterwards you can confirm memcached is up and running by calling it.
```
$ memcached -h
memcached 1.2.6
```
## Install the Memcache PECL Extension
Even though memcached is happily running on the server, it's not accessible from PHP without the PECL extension. Fortunately this is a very easy process, just use the `pecl` command.
```
$ pecl install memcache
```
Then add the memcache extension to your php.ini file, usually at `/etc/php.ini`.
```
extension=memcache.so
```
And finally restart Apache so that it will pick up the new extension:
```
$ /etc/init.d/apache2 restart
```
Running phpinfo() on your webserver should now confirm that memcache is installed:

## Set up Memcached as a service
Just having memcache installed will not do anything by itself, we need to actually start up some instances of it for our web server to connect to, and we need memcached to automatically start up when the server restarts.
For this we need to install a new script at `/etc/init.d/memcached`. For this I usually use a custom script that's a bit crude, since it assumes that memcached is being used exclusively for our web server. However, most of the time this is true and it works just fine.
[Download the memcached script](https://www.lullabot.com/files/memcached.txt) (rename to just "memcached").
So simply load this script into `/etc/init.d`. Then set the permissions on it to make it executable:
```
$ chmod 755 memcached
```
Then register the script to start up with the server:
```
$ chkconfig --add memcached
```
Now you can start up memcached as a service.
```
$ service memcached start
```
And you can confirm that memcached has fired up several instances by checking `ps`.
```
$ ps -e | grep memcached
22805 ? 00:00:59 memcached
22807 ? 00:00:58 memcached
22809 ? 00:01:16 memcached
22811 ? 00:00:55 memcached
22813 ? 00:00:01 memcached
22815 ? 00:01:02 memcached
22817 ? 00:00:27 memcached
22819 ? 00:00:35 memcached
22821 ? 00:00:01 memcached
22823 ? 00:00:01 memcached
22825 ? 00:00:01 memcached
```
And that's it! You may need to change the /etc/init.d/memcached file to match your needs depending on what you're using Memcached for. If you're using Memcached with [Drupal](http://drupal.org), you can follow the instructions for changing your settings.php file by following the instructions provided with [the Memcache module](http://drupal.org/project/memcache). Also make sure you [configure your Firewall](https://www.howtoforge.com/linux_iptables_sarge) to prevent access to Memcache from external URLs.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "The Art of Estimation"
url: "/articles/the-art-of-estimation"
type: article
date: 2011-05-10
updated: 2021-01-12
---
# The Art of Estimation
# The Art of Estimation
Before you can estimate, you have to break a project down into estimable pieces. In theory, this is simple enough.
By
[ Seth Brown ](/about/seth-brown)
May 10, 2011
### Two Truths About Estimation
The first truth about large software projects is that it's nearly impossible to provide an accurate estimate at the outset. A modern web site is a collection of small systems interacting to form a much larger systemâone that is about as predictable as the weather, or the stock market. In scientific terms, an enterprise-scale Drupal website is "fundamentally complex," and fundamentally complex systems tend to defy prediction.
The second truth about large software projects is that clients almost invariably require estimates, whether it's a fixed bid or a "back of the envelope" guesstimate they can take to their superiors to get an Agile project the green light. Because we love our clients, and very few of them live in a world of limitless budgets and flexible requirements, estimates are a necessary evil. So what's the aspiring estimator to do? In this article, I'll talk about how Lullabot uses a variation on the "Wideband Delphi method," a consensus-based estimation technique developed at RAND Corp. in the 1940s. Don't worry! Our approach is not as fancy as it sounds.
### Decomposing the Project
Before you can estimate, you have to break a project down into estimable pieces. In theory, this is simple enough. To quote my college Computer Science text book, "Algorithms for solving a problem can be developed by stating the problem and then subdividing the problem into major subtasks. Each subtask can then be subdivided into smaller tasks. This process is repeated until each remaining task is one that is easily solved." Great, you might think. That sounds easy! I have time to catch up on my correspondence moves at chessatwork.com. In reality, it's always difficult to come up with a Work Breakdown Structure (W.B.S.), the fancy term for a long and comprehensive list of the small, estimable tasks required to complete a mongongous project. There are many different approaches to decomposing a project; exploring them all in detail is out of scope for this article, but I'll briefly gloss over a few of the most popular. If the requirements are unknown they must be elicited, which is a fancy way of saying that it's time to sit down in a room with your stakeholders for three days and ask a bazillion questions. I like to pretend Iâm a detective doing a murder investigation, leaving no stone un-turned. Usually Lullabot will do a one-week onsite, and then go back to the basement to write a "Vision and Scope" or "Software Requirements Specification" document, which breaks the project into 10-20 major features with detailed descriptions around the requirements and assumptions about each one. These features can then be broken down into smaller and smaller problem sets. We'll then describe how Drupal typically solves those problems. When we're working with a client accustomed to Agile/Scrum, we may not need to do an estimate, but we'll still develop a project backlog expressed first as epics and then broken down into user stories, and the user stories become the estimable elements, as well as the basis for recommending Drupal solutions.
### Our Approach to Project Decomposition
Oftentimes, however, there isn't a discovery budget, you can't sell Agile, or you're working off a RFP. In these scenarios, I would advise breaking the project down into existing Drupal solutions. What are the content types, the views, the taxonomy requirements, the menus, the blocks, etc.? Will you require Panels or Context modules for help with blocks or layout? Knowing the tool ahead of time is a huge advantage when it comes to making estimates. When you've already used solutions like Panels, or Services, or SOLR, or Migrate, or Features to solve problems in the past, there are fewer unknowns, and you can estimate off of past experience. Lullabot has a template we like to use that helps us think about large enterprise-level websites in terms of their constituent Drupal building blocks. Don't forget about non-functional requirements, and stuff that's typically not visible to users, but is still important such as deployment processes, hosting requirements, etc. Whichever method you use to decompose the project, at the end of the day you'll likely end up with a spreadsheet, which is still the best tool I've found for estimation. I've seen everything from OmniOutliner to a combination of playing cards, tequila shots, and a whiteboard used to capture estimates, but I think spreadsheets are a sober choice. This spreadsheet represents your best guess at what work will be involved in completing the project.
Here's a [link](https://docs.google.com/spreadsheets/d/1y-3AliSRiHxnAkNhHa9JvQjX3EHpkiulxjlec4B7isA) to our base Google Spreadsheet. To learn more about how to use it, read this [fantastic follow-up to this piece](https://www.lullabot.com/articles/handling-uncertainty-when-estimating-software-projects).
### A Typical Work Breakdown Structure
Here's a spreadsheet showing how we typically organize our Work Breakdown Structure

Notice how we have columns for R&D, development, theming, and quality assurance for each item. We treat project management hours separately as overhead across the whole project and, depending on the type of project, the client's internal project management resources and the culture of the client, historically weâve found 20 and 35 percent of the total budget to be the common range for project management. The higher investment of time is reserved for clients who have not been previously involved in an enterprise CMS deployment before.
It's also sometimes useful to organize the W.B.S. into sprintsâchunks of work no more than a month long. The benefit of this exercise is that it helps you start to get a handle on timeline, and forces you to think about the priority and sequence of the tasks. For every four sprints, consider building in a slush sprint with no explicit goals, other than to allow for the inevitable delays of a project, the squashing of pernicious bugs from past sprints, and for change orders. Most change control processes in contracts allow for the theoretical possibility of a change order occurring, but almost never set aside time in the timeline to actually deal with such a change.
### How Much is this Bad Boy Gonna Cost?
Now it's time to get down to the nifty business of estimating. The first step is to decide on a unit of estimation. At Lullabot, we use ideal programmer hours, meaning how many hours would this take if the programmer was left alone in distraction-free, RedâBull-fueled bliss to crank out code, write Selenium tests, or theme the item. We are flexible with increments, meaning we make our best guess. The truth is, though, that the larger the estimate the higher the probability of inaccuracy. For this reason, it's often useful to confine the increments of your estimates to something like: 15 minutes, 1, 2, 3, 4, 8, 12, 16, 24, 32, or 40 hours. It's silly to argue about whether a task will take 30 or 32 hours, because, wellâ¦hard saying, not knowing. My belief is that tasks likely to take more than one developer week to complete should be broken down further, and the sub-tasks revisited. If you've done your job on the W.B.S., most of the tasks should come in on the lower end of the spectrum, where you have the luxury of greater precision.
Next, select two or more engineers from your team, including all who were involved in decomposing the project into tasks. Distribute an estimate-free copy of the W.B.S. spreadsheet to each estimator, and ask them to go through and provide estimates for each item, while simultaneously documenting their assumptions in the "Comments" column of the spreadsheet. Documenting assumptions is critical for consensus, and a must for good contracts. If these assumptions are documented, and subsequently encompassed in the contract, then it is easy, and contractually possible, to make a case to the client that the original estimate should be tossed out, and the bid revised.
Now the inestimable joy of estimation really begins. First, it's helpful to have a disinterested project manager play moderator. Ideally this person hasn't estimated the tasks, and therefore has no preconceived notions. The moderator gathers everyone on Skype, or a conference line or evenâgaspâan actual, physical room, and shares an estimate-free copy of the spreadsheet as a Google Doc. Now it's time for everyone to reveal their estimates, and any assumptions that went into that estimate. For many tasks, the estimates will be really close, and the need to make assumptions negligible. In these instances, it is best for the project manager or moderator to simply post an average to the master spreadsheet. If, however, the estimates diverge wildly, it's time to discuss assumptions. One developer might have assumed a few drush commands would be sufficient to administer a custom module, where another developer might have assumed a full-featured UX. Once everyone agrees on the assumptions, and the project manager has them documented, it's time for the engineers to re-estimate. Hopefully the results are closer. Sometimes, the estimators after exchanging assumptions will actually swap positions as high and low. The delta to each estimators original bid puts the âDelphiâ in Wideband Delphi estimation.
The key here is to make sure the conversational tone is loose and friendly, and to continue the cycle of estimate, discuss, estimate, discuss. The goal is consensus on a best-guess estimate, and ensuring that the group's assumptions are documented. If you haven't reached consensus after three iterations of estimation, it's probably because, at heart, the estimators don't agree on the assumptions. This is invariably due to either too many unknowns or programmer hubris (âThat wonât take 12 hours, itâs just an alter hook!â). At this point, it is best to move on, highlight the contentious column in Yellow, and assign someone to start to eliminate the unknowns. If, due to timeline, you only have one shot at producing a final estimate, consider presenting the client with a range, along with the assumptions that would affect that range one way or the other. There may be items you simply won't bid. Consider adding a time and materials clause to the contract to cover risky, inestimable pieces of work that defy prediction. (See migration, data.) At the end of this process, you will hopefully have a comprehensive estimate that can be attached to the Statement of Work, providing a detailed, and, hopefully, accurate prediction of the number of hours required to complete the project.
### Extra Credit
To introduce even more fun into the process, and to make sure the estimates are blind, consider using [Planning Poker](https://www.planningpoker.com/), a great virtual estimation game that has the added benefit of including a two-minute timer for each round of discussion.
### The Blameless Autopsy
Find a way to log time (we happily use http://www.letsfreckle.com) on projects, and consider logging time at least to sprint-level granularity so that you can go back and perform a blameless autopsy on your estimates. Now ask what, if anything, went wrong, why did 'Sprint 6: Data Migration' take 47x as long as predicted? Note the type of Sprints that seem to come up again, and again as culprits in overruns, and adjust your future estimates accordingly. If you're really meticulous, consider entering your W.B.S. directly into a project management system as tasks. We use Jira for this. After the project is completed, go back and review the actuals against the estimates and see what happened. You'll learn a lot, and this historical data should help guide the way on future estimates.
### In Conclusion
Human beings, Nostradamus aside, are bad at predicting the future. Using a consensus-based approach that depends on the blind estimates of multiple engineers, and combining that with iterative refinement, can go a long way in mitigating the uncertainty.
Published in:
- [ Business ](/topics/business)
- [ Technical Project Management ](/topics/project-management)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Photo galleries with Views Attach"
url: "/articles/photo-galleries-with-views-attach"
type: article
date: 2009-06-01
updated: 2014-05-15
---
# Photo galleries with Views Attach
# Photo galleries with Views Attach
By
[ Jeff Eaton ](/about/jeff-eaton)
June 1, 2009
A quick screencast demonstrating a new technique for building photo galleries in Drupal with Views and CCK.
**Update:** An encapsulated version of these settings has been [exported](https://www.lullabot.com/files/views_gallery.zip) for use with the [Features](http://drupal.org/project/features) module -- it should automatically handle the dependency tracking.
**Update 2:** An additional 'raw' export of the [views, css, and content types](https://www.lullabot.com/files/views-gallery-exports.zip) is now available for download as well. For those not using the Features module, it should get you started. The ImageCache presets will still need to be set up, but the rest is there.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Configuring Varnish for High-Availability with Multiple Web Servers"
url: "/articles/configuring-varnish-for-highavailability-with-multiple-web-servers"
type: article
date: 2011-04-05
updated: 2019-02-02
---
# Configuring Varnish for High-Availability with Multiple Web Servers
# Configuring Varnish for High-Availability with Multiple Web Servers
Varnish is a very popular software package that can dramatically accelerate the work of serving HTTP pages.
By
[ Nate Lampton ](/about/nate-lampton)
April 5, 2011
Varnish is a very popular software package that can dramatically accelerate the work of serving HTTP pages. Varnish caches fully-rendered responses to HTTP requests and serves them without the delay of building content from scratch. Because it's so much more efficient than building a page with Apache and Drupal, Lullabot regularly deploys Varnish when a site needs to handle high levels of anonymous traffic. In many situations, installing Varnish on the same machine as MySQL and Apache can help squeeze more performance out of a single box. However in most of our deployments, we're working with "high availability" setups, where dedicated servers handle different functions and redundant backup servers are on hand for every piece of the infrastructure. That means two database servers, two web servers, two Varnish servers, and so on.
 A typical Drupal high-availability setup with Varnish.
This article is all about configuring Varnish optimally for these high-availability setups, in which multiple dedicated back-end servers are protected from heavy traffic by Varnish serving cached content to the outside world. We also have some neat tricks for server maintenance, optimizing your cached content, and configuring Varnish to act as a fail-safe even if *all* of your back-end servers go down.
## Basic Varnish Configuration
Varnish usually has three locations of configuration. The boot script, the system-wide configuration, and the VCL file that does most of the work. The first script that starts up Varnish is usually located with the rest of your system startup scripts at `/etc/init.d/varnish`. This file rarely needs adjustments, but it can be interesting to read or help you locate further configuration (since the startup script is responsible for calling the next file). The second file is usually located at `/etc/sysconfig/varnish` (on CentOS and RedHat machines) or `/etc/default/varnish` (on Ubuntu). This file defines global configuration for Varnish such as which port it should run on and where it should store its cache. Typically it contains 5 different ways of writing the same thing. It doesn't matter which option you use: just be sure to change your storage backend. The default is usually "file", which stores cached information on disk. Be absolutely sure to change this to "malloc", which stores information in memory! If you don't have enough memory in your box for a decent sized cache (say a few gigabytes), consider a memory upgrade. Here's a configuration that we have running on a very popular site with a large number of images and pages being cached (this is using the "Option 2" in the /etc/sysconfig/varnish file):
```
DAEMON_OPTS="-a :80,:443 \
-T localhost:6082 \
-f /etc/varnish/default.vcl \
-u varnish -g varnish \
-S /etc/varnish/secret \
-p thread_pool_add_delay=2 \
-p thread_pools= \
-p thread_pool_min=<800 / Number of CPU cores> \
-p thread_pool_max=4000 \
-p session_linger=50 \
-p sess_workspace=262144 \
-s malloc,3G"
```
The last line is the most important to set up. In this case we're allocating 3GB of memory for Varnish's dedicated use. Also note the paths used in this file that reference which VCL file you will use. It's probably best to stick with whatever file path your distribution uses. The lines above for ``, be sure to replace with actual server information. You can get information about the number of processors in your machine by running `grep processor /proc/cpuinfo`, which will return a line for each processor core you have available.
## VCL Configuration
The VCL file is the main location for configuring Varnish and it's where we'll be doing the majority of our changes. It's important to note that Varnish includes a large set of defaults that are always *automatically appended* to the rules that you have specified. Unless you force a particular command like "pipe", "pass", or "lookup", the defaults will be run. Varnish includes an entirely commented-out default.vcl file that is for reference. We'll be going through each of the sections individually down below, but for ease of reading here's a complete copy of the VCL file that we're currently using, and a copy of the defaults that Varnish will automatically append to our own rules. *Updated 4/9/2012: I've added an updated VCL and examples here for Varnish 3, [which differs slightly](https://vinyl-cache.org/docs/3.0/installation/upgrade.html) from the Varnish 2 configuration.*
- [Lullabot's default.vcl for multiple web servers (for Varnish 2.1.x)](https://www.lullabot.com/sites/lullabot.com/files/default.vcl_.txt)
- [Lullabot's default.vcl for multiple web servers (for Varnish 3.x)](https://www.lullabot.com/sites/lullabot.com/files/default_varnish3.vcl_.txt)
- [View the set of defaults (as of Varnish 2.1.3)](https://www.lullabot.com/sites/lullabot.com/files/default-standard.vcl_.txt)
Now let's get started walking through the most interesting stuff, the VCL!
## Health Checks and Directors
A more recent feature of Varnish (2.x and higher) has been the addition of "directors" to send traffic to any number of web servers. These directors can do regular health checks on each web server to see if the server is still running smoothly. If Varnish receives a request for an asset that it hasn't yet cached, it will only pass on the request to a "healthy" server. Our VCL file is typically set up to handle both HTTP and HTTPS traffic, so we need to define a list of web servers by their IP address twice; once for port 80 which provides normal pages and again for port 443 for secure connections. If you've set up Apache on a different port, reference that port here.
```
# Define the list of backends (web servers).
# Port 80 Backend Servers
backend web1 { .host = "192.10.0.1"; .probe = { .url = "/status.php"; .interval = 5s; .timeout = 1s; .window = 5;.threshold = 3; }}
backend web2 { .host = "192.10.0.2"; .probe = { .url = "/status.php"; .interval = 5s; .timeout = 1s; .window = 5;.threshold = 3; }}
# Port 443 Backend Servers for SSL
backend web1_ssl { .host = "192.10.0.1"; .port = "443"; .probe = { .url = "/status.php"; .interval = 5s; .timeout = 1 s; .window = 5;.threshold = 3; }}
backend web2_ssl { .host = "192.10.0.2"; .port = "443"; .probe = { .url = "/status.php"; .interval = 5s; .timeout = 1 s; .window = 5;.threshold = 3; }}
# Define the director that determines how to distribute incoming requests.
director default_director round-robin {
{ .backend = web1; }
{ .backend = web2; }
}
director ssl_director round-robin {
{ .backend = web1_ssl; }
{ .backend = web2_ssl; }
}
# Respond to incoming requests.
sub vcl_recv {
# Set the director to cycle between web servers.
if (server.port == 443) {
set req.backend = ssl_director;
}
else {
set req.backend = default_director;
}
}
```
The last part of the configuration above is part of our `vcl_recv` sub-routine. It's what defines which set of servers will be used based on the port by which Varnish received the traffic. It's a good idea then to set up a reasonable health check on each web server to make sure that server is ready to deliver traffic. We use a file located directly in the root of the web server called "status.php" to check if the web server is healthy. This file does a few checks including:
- Bootstrapping Drupal
- Connecting to the Master database
- Connecting to the Slave database (if any)
- Connecting to Memcache (if in use)
- Checking that the files directory is accessible
If any of these checks fail, the file throws a 500 server error and Varnish will take the web server out of rotation. The status.php file is also extremely useful for intentionally taking a web server out of rotation. Simply move the status.php file to a new location (like status-temp.php) and Varnish will automatically remove the server from rotation while the server itself stays up so that it may be serviced independently of the other web servers. This approach is common when performing upgrades or installations of new software on the web servers.
- [View/Download our status.php script here](https://www.lullabot.com/sites/lullabot.com/files/status.php_.txt)
While our status.php script is fairly universal, it may require some tweaking for your own purposes. If you have additional services that are required for a server to function properly, adding additional checks to the script will ensure Varnish doesn't cache bad data from a broken server.
## Caching Even if Apache Goes Down
Even in an environment where everything has a redundant backup, it's possible for the entire site to go "down" due to any number of causes. A programming error, a database connection failure, or just plain excessive amounts of traffic. In such scenarios, the most likely outcome is that Apache will be overloaded and begin rejecting requests. In those situations, Varnish can save your bacon with the *Grace period*. Apache gives Varnish an expiration date for each piece of content it serves. Varnish automatically discards outdated content and retrieves a fresh copy when it hits the expiration time. However, if the web server is down it's impossible to retrieve the fresh copy. "Grace" is a setting that allows Varnish to serve up cached copies of the page even after the expiration period if Apache is down. Varnish will continue to serve up the outdated cached copies it has until Apache becomes available again. To enable Grace, you just need to specify the setting in `vcl_recv` and in `vcl_fetch`:
```
# Respond to incoming requests.
sub vcl_recv {
# Allow the backend to serve up stale content if it is responding slowly.
set req.grace = 6h;
}
# Code determining what to do when serving items from the Apache servers.
sub vcl_fetch {
# Allow items to be stale if needed.
set beresp.grace = 6h;
}
```
Both of these settings can be the same, but the setting in vcl\_fetch must be longer than the setting in vcl\_recv. Think of the vcl\_fetch grace setting as "the maximum time Varnish should keep an object". The setting in vcl\_recv on the other hand defines when Varnish should use a stale object if it has one. Just remember: while the powers of grace are awesome, Varnish can only serve up a page that it has already received a request for and cached. This can be a problem when you're dealing with authenticated users, who are usually served customized versions of pages that are difficult to cache. If you're serving uncached pages to authenticated users and all of your web servers die, the last thing you want is to present them with error messages. Instead, wouldn't it be great if Varnish could "fall back" to the anonymous pages that it does have cached until the web servers came back? Fortunately, it can -- and doing this is remarkably easy! Just add this extra bit of code into the `vcl_recv` sub-routine:
```
# Respond to incoming requests.
sub vcl_recv {
# ...code from above.
# Use anonymous, cached pages if all backends are down.
if (!req.backend.healthy) {
unset req.http.Cookie;
}
}
```
Varnish sets a property `req.backend.health` if any web server is available. If all web servers go down, this flag becomes FALSE. Varnish will strip the cookie that indicates a logged-in user from incoming request, and attempt to retrieve an anonymous version of the page. As soon as one server becomes healthy again, Varnish will quit stripping the cookie from incoming requests and pass them along to Apache as normal.
## Making Varnish Pass to Apache for Uncached Content
Often when configuring Varnish to work with an application like Drupal, you'll have some pages that should absolutely never be cached. In those scenarios, you can easily tell Varnish to not cache those URLs by returning a "pass" statement.
```
# Do not cache these paths.
if (req.url ~ "^/status\.php$" ||
req.url ~ "^/update\.php$" ||
req.url ~ "^/ooyala/ping$" ||
req.url ~ "^/admin/build/features" ||
req.url ~ "^/info/.*$" ||
req.url ~ "^/flag/.*$" ||
req.url ~ "^.*/ajax/.*$" ||
req.url ~ "^.*/ahah/.*$") {
return (pass);
}
```
Varnish will still act as an intermediary between requests from the outside world and your web server, but the "pass" command ensures that it will always retrieve a fresh copy of the page. In some situations, though, you *do* need Varnish to give the outside world a direct connection to Apache. Why is it necessary? By default, Varnish will always respond to page requests with an explicitly specified "content-length". This information allows web browsers to display progress indicators to users, but some types of files don't have predictable lengths. Streaming audio and video, and any files that are being generated on the server and downloaded in real-time, are of unknown size, and Varnish can't provide the content-length information. This is often encountered on Drupal sites when using the Backup and Migrate module, which creates a SQL dump of the database and sends it directly to the web browser of the user who requested the backup. To keep Varnish working in these situations, it must be instructed to "pipe" those special request types directly to Apache.
```
# Pipe these paths directly to Apache for streaming.
if (req.url ~ "^/admin/content/backup_migrate/export") {
return (pipe);
}
```
Finally, while we're discussing paths that need exceptions, Varnish is also a good place to restrict access to specific URLs. Because the VCL file is so flexible, it can be a good place to lock down paths that should never be seen by the outside world. Up at the very top of our VCL file, we have a line that defines an access control list of IP addresses. These addresses are considered to be "internal" to our environment. A common example is restricting public access to Drupal's cron.php file so that only local web servers can trigger expensive tasks like search indexing. Since the local web servers all have IP addresses that begins with "192.10.", they are granted access while all others receive an access denied message At the top of the default.vcl file:
```
# Define the internal network subnet.
# These are used below to allow internal access to certain files while not
# allowing access from the public internet.
acl internal {
"192.10.0.0"/24;
}
```
An then inside of `vcl_recv`:
```
# Respond to incoming requests.
sub vcl_recv {
# ...code from above.
# Do not allow outside access to cron.php or install.php.
if (req.url ~ "^/(cron|install)\.php$" && !client.ip ~ internal) {
# Have Varnish throw the error directly.
error 404 "Page not found.";
# Use a custom error page that you've defined in Drupal at the path "404".
# set req.url = "/404";
}
}
```
## Optimizing Varnish's Cache
First, let's dissect a very popular but misguided configuration that's made the rounds on the internet. When describing how to serve cached content to users with cookies, a number of sources recommended this solution:
```
# Routine used to determine the cache key if storing/retrieving a cached page.
sub vcl_hash {
# Do NOT use this unless you want to store per-user caches.
if (req.http.Cookie) {
set req.hash += req.http.Cookie;
}
}
```
Generally, this is **not** a useful approach unless you're serving up the same page to a single user repeatedly. It will recognize the unique cookie that Drupal gives to every logged in user, and use it to keep cached content for one user from being displayed to another. However, this approach is a waste: Drupal explicitly returns a Cache-Control header for all authenticated users that prevents Varnish from caching their content:
```
Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0
```
In other words, don't use this approach unless you have an explicit reason to cache authenticated pages. In most situations, this approach will add overhead without caching anything. In the worst case scenario, it will fill up your cache with entries for each authenticated user and push out more valuable anonymous pages that can be reused for thousands of visitors. In most cases, the best approach is to maintain a single cache that is used for all users. Any content that cannot be served to all users can be passed through to Apache. Because Drupal uses a cookie to indicate the account of a logged in user, the easiest way to spot requests that need fresh, un-cached content is ignore requests with cookies. However, that cookie will *also* be added to requests for images, JavaScript files, CSS files, and other supporting media assets. Although the HTML page itself is likely to change for logged in users, there's no reason that Varnish can't serve up cached versions of the support assets. Taking this into consideration, it's a good idea to discard any cookies that are sent by the browser when requesting such files:
```
# Respond to incoming requests.
sub vcl_recv {
# ...code from above.
# Always cache the following file types for all users.
if (req.url ~ "(?i)\.(png|gif|jpeg|jpg|ico|swf|css|js|html|htm)(\?[a-z0-9]+)?$") {
unset req.http.Cookie;
}
}
```
So far, so good. Stripping cookies from requests for static files allows them to be cached for both anonymous and authenticated users. While it works fine for out-of-the-box Drupal sites, however, there are unfortunately quite a few other ways that cookies can be set on your site. The most common culprits are statistics tracking scripts (like Google Analytics) and advertising servers. Ad scripts in particular have a terrible habit of setting cookies through JavaScript. For the most part, Varnish and Drupal are not concerned at all with these cookies, but since *any* cookie passed by the browser will cause Varnish to pass the request to Apache, we need to take care of them. There are multiple approaches to handling this problem, and most administrators start by trying to build a "blacklist" of cookies to strip out from the request, leaving only the ones in which they have interest. This usually results in a configuration file that look something like this:
```
// Remove has_js and Google Analytics __* cookies.
set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(__[a-z]+|has_js)=[^;]*", "");
```
This approach will usually work for a short period of time, but as soon as an ad script or some new piece of JavaScript adds a cookie (like Comment module, Flag module, or any of many other modules), Varnish will cease to cache the page. You'll have to track down the new cookie and add it to the blacklist manually. Instead, we use an "inclusion" list, where all cookies but a few will be automatically stripped from the request. This logic is a lot more verbose, but it is definitely a more sustainable solution:
```
# Respond to incoming requests.
sub vcl_recv {
# ...code from above.
# Remove all cookies that Drupal doesn't need to know about. ANY remaining
# cookie will cause the request to pass-through to Apache. For the most part
# we always set the NO_CACHE cookie after any POST request, disabling the
# Varnish cache temporarily. The session cookie allows all authenticated users
# to pass through as long as they're logged in.
if (req.http.Cookie) {
set req.http.Cookie = ";" + req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|NO_CACHE)=", "; \1=");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.Cookie == "") {
# If there are no remaining cookies, remove the cookie header. If there
# aren't any cookie headers, Varnish's default behavior will be to cache
# the page.
unset req.http.Cookie;
}
else {
# If there are any cookies left (a session or NO_CACHE cookie), do not
# cache the page. Pass it on to Apache directly.
return (pass);
}
}
}
```
Once you've tamed cookies, there's one other "enemy" of caching you need to plan for: the "Accept-Encoding" header sent by different browsers. Each browser sends information to the server about what kind of caching mechanisms it supports. All modern browsers now support "gzip" compression, but they all inform the server that they support it in different ways. For example, the header of modern browsers will report Accept-Encoding the following ways: Firefox, IE: `gzip, deflate ` Chrome: `gzip,deflate,sdch ` Opera: `deflate, gzip, x-gzip, identity, *;q=0 ` In addition to the headers sent by the browser, Varnish must also pay attention to the headers sent by Apache, which usually include lines like this:
```
Vary: Accept-Encoding
```
This means that Varnish will store a different cache for every version of the "Accept-Encoding" header it receives from different browsers! That means you'll be maintaining separate cached copies of your web site for *each different browser,* and in some cases for *different versions* of the same browser. This is a huge wast of space, since every browser actually supports "gzip", but just reports it differently. To prevent this confusion, we include this segment in `vcl_recv`:
```
# Handle compression correctly. Different browsers send different
# "Accept-Encoding" headers, even though they mostly all support the same
# compression mechanisms. By consolidating these compression headers into
# a consistent format, we can reduce the size of the cache and get more hits.
# @see: http:// varnish.projects.linpro.no/wiki/FAQ/Compression
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "gzip") {
# If the browser supports it, we'll use gzip.
set req.http.Accept-Encoding = "gzip";
}
else if (req.http.Accept-Encoding ~ "deflate") {
# Next, try deflate if it is supported.
set req.http.Accept-Encoding = "deflate";
}
else {
# Unknown algorithm. Remove it and send unencoded.
unset req.http.Accept-Encoding;
}
}
```
## Conclusions
Varnish is an amazing and incredibly efficient tool for serving up common resources from your site to end-users. Besides simply making your site faster, it also can add additional redundancy to your setup by acting as a full backup if the web servers fail. In order to make Varnish both serve as an effective backup and efficient caching layer, it needs to clean up incoming headers from the browser, strip down cookies, and consolidate the "Accept-Encoding" header. After such extensive explanations it's easy to get overwhelmed, but the good news is that the VCL file provided here can quickly be deployed to almost any Drupal site and start working immediately. For most sites no further customization is needed, and sites that need to tweak it will have a good head start towards huge reductions in server load. On our sites, Varnish is usually able to handle about 85% of the traffic without ever touching the web servers. Even during peak times with hundreds of thousands of requests coming in per hour, Varnish can hum along at less than 5% CPU usage of an average 4-core server. Instead of scaling out your web servers horizontally, adding a few Varnish machines in front of them can save a huge amount of processing and speed up your site at the same time. If you haven't already, grab the actual default.vcl file that we use on our sites and read it through start to finish. Now with all of the individual pieces explained in-depth above, we hope you can use it as a starting point for your own VCL configuration. Happy caching!
Published in:
- [ System Administration ](/topics/system-administration)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Simple Off-Site Backups with rsync, ssh, and sudo"
url: "/articles/simple-offsite-backups-with-rsync-ssh-and-sudo"
type: article
date: 2012-05-02
updated: 2017-10-03
---
# Simple Off-Site Backups with rsync, ssh, and sudo
# Simple Off-Site Backups with rsync, ssh, and sudo
Using a combination of rsync, ssh, sudo, and a touch of bash, it's possible to back up your servers quickly and easily.
By
[ Andrew Berry ](/about/andrew-berry)
May 2, 2012
Setting up a proper backup system is often ignored until it's too late. Manage a computer or a server for long enough, and you'll inevitably run into missing data, or worse yet, corrupted data. For small servers running on a VPS, a complete off-site backup solution might be cost prohibitive or even unavailable. Many backup systems use complicated or proprietary storage mechanisms, making recovery difficult when restoring from "bare metal". Using a combination of rsync, ssh, sudo, and a touch of bash, it's possible to back up your servers quickly and easily.
## Tools Needed
- [rsync](https://rsync.samba.org/)
- [ssh](https://www.openssh.org/)
- [sudo](https://gratisoft.us/sudo/sudo.html)
- cron
- A backup server to store backups with.
## Plan of Attack
Using a few standard \*nix tools, we're going to set up incremental off-site backups. rsync will be the core of our backup strategy. rsync is an efficient and flexible program for copying data between different systems. In essence, rsync mirrors the contents of a source directory to a destination directory. What makes rsync *awesome* is that it can operate very quickly even over slow network links. By default, rsync will only transfer changed files between systems, ignoring files that already exist in the destination. This means that once the initial copy is complete, subsequent rsync commands will be much faster.
An important component of a backup strategy is not just to have the current state of a file system, but also to have some ability to pull files from previous backups. Without previous backup storage, it's possible for data corruption to cause data loss. rsync enables incremental backups with the `--link-dest` parameter. This flag tells rsync to use hard links to link to previous unchanged copies of a file. Without this option, each backup would contain a complete copy of a file, significantly impacting disk space needed for the backup.
rsync is flexible in that it enables the use of many different protocols to transfer files between servers. One of those protocols is SSH, which is installed on just about every \*nix server by default. Using ssh allows us to use standard access controls for user accounts as well as use key authentication for security. Over slow network links, SSH is one of the best options for incremental backups. When using ssh, rsync spawns a copy of the rsync process on the server, allowing for changes to determined locally on the server instead of transferring the files to the backup system. This can be significantly faster than using NFS or Samba to access to source files from the backup server.
sudo is used to help protect our destination server. In order to back up a machine, the backup server must have permission to access most files on the backup client. A naïve approach would be to connect with ssh to a root account. However, that means that if the backup server is compromised it could be used to compromise the backup client. With sudo, we can add the ability for a restricted account to run a very specific command and ensure that the backup server can not change any files on the backup client.
Finally, we use cron to schedule our backups. Why? Because backups are [SERIOUS BUSINESS](https://www.nataliedee.com:443/index.php?date=071907).
*All of the following steps are based on using Ubuntu 10.04. Modify the commands as needed for your distro or operating system. "Backup Server" means the server where backups are stored. "Backup Client" means the server that is being backed up.*
## Step 1: Add an rsync user account on the backup client
`$ sudo useradd rsync `
Note that by default, this account will not be able to log in with a password. This helps improve security on the server by requiring SSH keys to log in remotely.
## Step 2: Enable passwordless sudo for the rsync command
`$ sudo visudo `
Add the following line to the end of the file:
`rsync ALL=(ALL) NOPASSWD: /usr/bin/rsync --server --sender -logDtprze.iLsf --numeric-ids . / `
## Step 3: Generate an SSH key on the backup server to authenticate with the backup client
`$ sudo -i # mkdir .ssh # chmod 0700 .ssh # cd .ssh # ssh-keygen -C rsync-backup `
When creating the SSH key, don't enter a passphrase. Otherwise, the backup script will not be able to connect automatically. After the keypair is generated, you will need to copy `id_rsa.pub` to `~/.ssh/authorized_keys` of the rsync user on the backup client. It's usually easiest to just copy and paste the public key from your terminal.
## Step 4: Set up the backup script on the backup server
I've [uploaded backup-servername.sh to github](https://gist.github.com/0f1066650e3ea5c5ffc1) as a starting point. It can be placed in `/etc/cron.daily` to be run once every 24 hours. A default "excludes" file is provided as well to prevent backing up /dev, /proc, and other system directories. Make sure to make it executable and to replace all instances of "servername" with the name of the server you are backing up. As well, I usually keep the backup destination on a separate LVM volume that I only mount when needed. Simpler configurations can remove the calls to mount and unmount.
## Step 4a: MySQL
To ensure consistent backups, I back up MySQL directly using mysqldump. Database backups are not stored incrementally, but for most servers the disk space used will be minimal. To back up all tables, either create a MySQL user with SELECT granted for all databases, or use the root MySQL account. Make sure that the permissions on the backup script are 0600 so that only root can read the saved password.
## Step 5: Testing!
Since the backup program is a simple bash script, it can be executed directly to manually run a backup.
`$ sudo /etc/cron.daily/backup-servername.sh `
If rsync isn't behaving as expected, or you are customizing the parameters, temporarily change the sudoers file on the backup client with visudo to allow all rsync options:
`rsync ALL=(ALL) NOPASSWD: /usr/bin/rsync `
Then, use `ps auxww | grep rsync` to pull out the exact command line used.
After running the script over a few days, there will be dated directories containing each backup. You can verify that hard linking is working properly with `du` and `stat`. `du` will show us that the subsequent backups are taking up minimal disk space, while `stat` will confirm the number of Links to unchanging files.
`# du -sh 20120101 20120102 15G 20120101 302M 20120102 # stat 20120101/bin/bash File: `20120101/bin/bash' Size: 818232 Blocks: 1600 IO Block: 4096 regular file Device: fc03h/64515d Inode: 979 Links: 47 Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2011-11-12 02:12:21.000000000 -0500 Modify: 2010-04-18 21:51:35.000000000 -0400 Change: 2012-04-14 10:10:04.715677821 -0400 `
Restoring from a backup is simple. Boot your destination server off of a rescue image and enable SSH. Use rsync to copy everything back over to the server. Create any directories that were excluded. Though I like back up complete servers, most of the time when restoring I simply reinstall all packages with apt-get, and then rsync /home, /root, /usr/local, and /etc.
## Advantages of rsync / hard link backups
- Possible to chroot into a backup if your server is the same OS and processor architecture as the backup client.
- Low disk usage compared to complete copies.
- Simple to understand.
- rsync runs on just about any OS available.
- Filesystem independent.
- No complexity of block-level incremental backups.
- Can delete any incremental backup in any order without affecting other backups.
## Disadvantages of rsync / hard link backups
- User and group IDs may not match on the destination server.
- Editing files in the backup is possible.
- Poor performance and disk use for large files that change slightly, such as virtual machine disk images.
## Next Steps
As is, this backup script doesn't remove any old backups. I like to do it manually every few months as it forces me to check to make sure that backups are still running successfully. For deployments beyond a single server, a combination of `date` and `rm -rf` should make it possible to easily remove old backups beyond a given age.
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ System Administration ](/topics/system-administration)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Building a Development Matrix"
url: "/articles/building-a-development-matrix"
type: article
date: 2014-04-30
updated: 2015-07-06
---
# Building a Development Matrix
# Building a Development Matrix
A streamlined tool for tracking website construction
By
[ Jerad Bitner ](/about/jerad-bitner)
April 30, 2014
Breaking down a digital project into bite-sized pieces is often a challenge, because there are so many ways to do it. User stories, functional silos, and more can all be useful. Recently, we've also used what we call a "Development Matrix" â an inventory of a website's visible, visitor-facing components. What landing pages, common template pages, and index pages must be built? What components (blocks, images, videos, text) show up on those pages?
Having this inventory of pages and components in a spreadsheet gives us a clear picture of every element that needs to be accounted for in the finished site, and allows us to track the completion of those pieces more accurately. We can calculate the project's *visible* progress, and it can even help us prioritize architectural work. If a particular component appears on more pages than another, then it's going to have a bigger impact on our bottom line.
## Landing Pages
In our matrix, we use this term to cover any visitor-accessible location on the web site. Typically you'll have mockups of each of these pages, because it's how designers typically think about the site. The home page, an individual article, or a listing of articles in a particular category are common examples. Start by identifying all of these and listing them in your spreadsheet in the second column. The first column will be used for some calculations that we'll get into later.
As you work through these landing pages, some templates might be the same, or have the exact same components. For instance, if you have a listing page of articles (listing A) and a listing page of people (listing B) and they use the same mix of components (such as the same sidebar blocks and then the main listing itself), they'll probably be implemented using the same underlying templates. When a block is placed on one, it will show up on the other. This constitutes the same body of work and should be tracked as such. In these situations, you might want to consolidate listing A and listing B into the same line item. A good general rule is to think of the work being done, and if it's accomplished with one ticket, it's probably okay to consolidate it in the matrix.

## Web Components
These are the various elements on the landing pages. Is there a title; a sidebar block for listing related articles; a newsletter signup widget at the bottom? List these in the subsequent columns on the second row. The first row will be used for calculating the amount of times any one component appears across the site.
At this point it's a good idea to come up with standard names for your components, perhaps based on the visible title of the component if it has one. Insert a note or comment in it's cell that links to either the actual ticket for building out the component, or to a screenshot of the component. This helps immensely in situations where terminology or descriptions are ambiguous, such as "Article listing A" and "Article listing B".
As you list a new component, cross reference which landing page/s it appears on and place an "x" in the corresponding cell at which the landing page and component intersect. You'll soon have a full inventory of x's in various cells â and then, you can begin to use the data.

## Calculations
The first calculation is a pretty straight-forward one. The top row is used for a simple [`COUNTA()`](https://support.google.com/drive/answer/3093991) which returns the number of x's in a given column. This just gives you a quick way to you see what components are more important, or at the very least, how many line items can be crossed off by finishing a single component. It can also help with prioritization. You might be able to hold off working on a component that shows up in just one place on one landing page, while front-loading work on a component that shows up on every single page.

The second calculation is a bit trickier. Google Spreadsheets has a neat little function called [`COUNTIF()`](https://support.google.com/drive/answer/3093480). This returns a conditional count across a range, which means it can return the number of times a certain character appears. For our purposes, I set this to `COUNTIF([range], "â")` in order to count the number of check marks. I then divide that by the `COUNTA([range])` (the number of cells that have something in them) and format the cell as a percentage. The complete calculation is something like `=COUNTIF(C5:AV5, "â")/COUNTA(C5:AV5)`: the completion percentage of each page, based on the underlying components it's built with.

## How it helps
This simple tool has helped our team *and* our clients visualize what needs to be done and how far we've progressed. It doesn't account for the level of effort, or any of the backend work that needs to be done to make the site work, but it does represent how complete we are from the perspective of most clients and stakeholders.
If you'd like to use this technique on your projects, we've made a [template](https://docs.google.com/a/lullabot.com/spreadsheets/d/1x0njXaQcypwVDs-ennsbbAPlG1Wa2TOaFNbi3_p8O64/edit#gid=0) on Google Docs. If you use it, or have ideas to change it, I'd love to hear them in the comments!
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
- [ Technical Project Management ](/topics/project-management)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Dodging Cassandra"
url: "/articles/dodging-cassandra"
type: article
date: 2014-04-22
updated: 2021-01-12
---
# Dodging Cassandra
# Dodging Cassandra
When my wife introduced me to opera, I didn't think Iâd learn much about tech projects. But as we watched Les Troyens, I saw a familiar story unfoldâ¦
By
[ Jeff Eaton ](/about/jeff-eaton)
April 22, 2014
âYouâre all doomed.â
When my wife introduced me to the world of opera a few years ago, I assumed itâd be a peek into high culture, not a lesson in keeping technology projects on track. But as we sat through *Les Troyens* â The Trojans â I watched a familiar story unfold.
Cassandra is a familiar presence in Greek mythology. She can see the future, but spurning Apolloâs amorous advances earns her a curse: no matter how accurate her prophecies are, no one ever listens to her. *Les Troyens* finds her in the city of Troy in the final days of its brutal war with Greece. When the Greek soldiers surrounding the city mysteriously disappear, leaving a gigantic wooden horse behind, all of Troy celebrates.
Obviously, the end of a decade-long siege means that itâs time to break out the champagne! Cassandra warns them that itâs a deadly trap, announcing that theyâll all be killed... but of course, no one listens. Theyâre too busy feasting and admiring their new monument. Their Trojan Horse.
Spoiler warning, folks: *the horse is full of Greek soldiers*.
### Stop me if youâve heard this before
If youâve ever been the skeptical person in the room during a new projectâs first, joyous planning session, you probably know how Cassandra felt. Youâre seeing bad omens, and your spidey-senses are tingling â but everyone else is smiling and saying, âLetâs crush this!â
The horse is full of crazy deadlines, and no one will listen.
Getting stuck in the role of the naysayer is never fun, especially in the very early stages of a project. Often, the warning signs youâre picking up are vague, and easy to dismiss. At those moments, itâs easy to lean back and turn the concerns into Cover-Your-Ass disclaimers. âPerhaps,â I sometimes think, âtacking an âassumptionsâ section onto the project plan will shield me from the consequences of a disaster I fear is inevitable...â
As tempting as that can be, I try to remember Cassandraâs fate. She was right when she warned Troy that it was doomed, but *she lived there, too*. When disaster struck, she perished along with the rest of the city. If we really care about the projects we work on and the people we work with, thereâs no joy in saying âI told you so.â The entire team suffers, and weâre right there with them.
### Dodging Cassandraâs curse
In a mature team under ideal circumstances, gut checks can be enough to get a decision-makerâs attention, but there are always times when something more concrete is necessary. How can we overcome Cassandraâs curse, and turn our vague portents of doom into clear, unambiguous advice? Thereâs no magic bullet, but a handful of basic techniques can improve our chances.
1. Catalog the uncertainty. Is there a hard deadline, but a fuzzy and ill-defined list of required features? Is unfamiliar or immature technology required to make it happen? Does the team lack an unambiguous set of success criteria? Make a list, and map out those scary shadows. Sometimes, there are answers and theyâll assuage your fears. When there arenât, though, it can help decision-makers realize they need to head back to the drawing board.
2. Compare the work to similar tasks and projects. If the early estimates for a large project feel too optimistic, it can be difficult to explain *why*. Whenever possible, find examples of similar projects or tasks from the past. Show how long *they* took, and if the estimates for those projects shared the same early optimism, point it out. As unpleasant as it is to keep time sheets and logs, they can be critical ammunition in the fight for sanity.
3. Identify deep dependencies, in technology and teams. Are you building a mobile app that relies on a third-party library... which relies on a fourth-party service... which relies on a fifth-party startup? Does one department control the infrastructure your project will need to launch, while a second is responsible for content and a third handles the development? The more external dependencies a project has, and the deeper those chains go, the more risk there is. Thereâs no way to avoid reliance on outside teams or tech, but mapping them out makes the risks clear.
4. Identify fuzzy authority roles. Few things are as depressing as ironing out a projectâs requirements, building it to spec, and preparing for launch only to discover that your client wasnât really in charge. The last-minute emergence of a VP with different aesthetic tastes, or ongoing conflict between two or three equal stakeholders, can sabotage an otherwise well-run project. If you hear talk of ârunning the plan past a few other peopleâ before it can be approved, or itâs unclear whoâs in charge of key decisions, donât be shy. Get a list of people with veto power and ensure thereâs a single buck-stops-here person for key decisions, or wave a red flag.
5. Time-box and prototype. Especially when new or unfamiliar technology is involved, accurately judging risks and sketching out timelines can be impossible. Carving out a small chunk of time for a prototype is critical. If the exercise reveals unanticipated challenges or problems, you have concrete evidence to offer rather than vague concerns.
### Towards a happy ending
The goal of these techniques is twofold. First, the work that goes into them can reveal *solutions* to the problems and clear answers to the troubling questions. Obviously, thatâs the best outcome: successfully routing around danger rather than grumbling about it. If that isnât possible, though, carefully articulating the concerns can make the pitfalls clear and unambiguous.
Itâs an approach that goes beyond *avoiding blame* and puts important information in the hands of people who need it. It doesnât always work, and we canât always avoid the dangers, but itâs far better than the cynical alternative.
Now, if youâll excuse me, I have to look into the next trip to the opera. This time? I think weâll try a comedy...
Published in:
- [ Business ](/topics/business)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal, duplicate content, and you"
url: "/articles/drupal-duplicate-content-and-you"
type: article
date: 2008-09-22
updated: 2014-05-15
---
# Drupal, duplicate content, and you
# Drupal, duplicate content, and you
By
[ Jeff Eaton ](/about/jeff-eaton)
September 22, 2008
### Does Google's "duplicate content penalty" harm Drupal sites? No! Here's why.
For years, Drupal has enjoyed a solid reputation as a search engine friendly CMS. It generates relatively clean, standards-compliant HTML out of the box; syncs up the important TITLE tag with semantically useful H1 and H2 tags in the body of each page; and provides short, human-readable URLs with plentiful options for customization. (Anecdotal evidence: several years back, I wrote a post on my Drupal-powered blog that mentioned the name of the company I worked for. Within two weeks, my blog post ranked higher than the company's own web site on Google.)
Recently, I've witnessed a number of discussions where people expressed concern about the way Drupal generates the human-readable URLs that help make it Google-friendly. In particular, they were worried about Google's dreaded Duplicate Content Penalty, a system designed to keep spammers from flooding Google with the same content at dozens (or hundreds!) of URLs. There's a lot of confusion floating around, so for the geeks in the crowd (and the not-so-geeky interested in learning how things work behind the scenes), I thought it would be useful to give a guided tour of how Drupal manages and generates URLs.
### Ground Zero: index.php
Every page generated by Drupal has a unique "path" that's used to identify it internally. Individual pieces of content live at paths like `node/1`, `node/2`, and so on. Unique administration pages get paths like `admin/settings/files` or `admin/content/comments`. Other modules like Views, Poll, and so on can add other paths.
It's a good starting point, but at this point, Drupal's URL structure is still as ugly as any other PHP web-app. Why? All requests for pages are routed through the index.php script at Drupal's heart, and the "path" of the desired page is passed along as an additional bit at the end of the url: `http://www.example.com/index.php?q=node/1` is one such example. In the screenshot below, I'm accessing an article on the Drupal.org web page using this basic URL structure.

### Clean URLs to the rescue!
Fortunately, few Drupal sites use those ugly URLs. The vast majority of web servers (Apache, IIS, and many of the smaller players) can finesse incoming URLs, routing simple URLs like `http://www.example.com/node/1` through the index.php script automatically. Drupal is configured to take advantage of it automatically, and Drupal 6.0 and later will double-check to ensure your web server supports the feature during installation.
In the screenshot below, I'm accessing the same page on Drupal.org using the "clean" version of the URL: it's just the site's domain, followed by the path, without any of the ugly index.php business cluttering things up.

### But wait, there's more!
Eliminating the ugly cruft only gets us half-way there. We still have content at relatively meaningly paths like `node/1` and `node/2`. That's where Drupal's *path* module comes in. It allows site administrators to define aliases for any path on the web site, turning URLs like `http://www.example.com/node/1` into `http://www.example.com/about-us`. In the final screenshot, below, I'm accessing the same article on Drupal.org using its path alias.

Path aliasing is particularly useful when combined with the [PathAuto module](http://drupal.org/project/pathauto). It allows site administrators to set up rules that generate path aliases for content automatically. When I first moved my blog from Movable Type to Drupal, it allowed me to mirror my existing URL structure without any manual tweaking.
When path aliases for nodes are set up to include the node title, search engines are pleased, too. Most search algorithms pay extra attention to text that appears in a page's URL, in the page's title, and inside of important tags like H1 and H2.
### Flies in the ointment
If you were paying attention during the explanation above, you noticed that content on a Drupal site can be given friendly URLs, but it *stays available at the original, unfriendly URL as well.* As far as most search engines are concerned, that means that you have multiple copies of the same content on your web site, and *that* raises all sorts of alarms. It's common knowledge that many search engines -- Google in particular -- penalize sites for putting duplicate pages at different URLs. Without that protection, unethical site owners could easily put thousands of copies of an article on their site and quickly become the "ultimate source for information" on a topic, even though they only have a tiny amount of unique content.
Does that mean that Drupal sites using path aliases are hurting themselves in the long run? Thankfully, the answer is no. First, Google's [documentation for webmasters](https://support.google.com/webmasters/answer/66359) explains that the only "penalty" is that only one copy of the content will be listed in search results. In fact, [a recent web post on the Google blog](https://webmasters.googleblog.com/2008/09/demystifying-duplicate-content-penalty.html) bent over backwards to clarify:
> Let's put this to bed once and for all, folks: There's no such thing as a "duplicate content penalty." At least, not in the way most people mean when they say that.
In a related post, [Deftly dealing with duplicate content](https://webmasters.googleblog.com/2006/12/deftly-dealing-with-duplicate-content.html), they explain that the only real concern is making sure that the *right* path for your page gets displayed when people search on Google.
One of the most important tips mentioned in that post is being consistent when you link to your site's pages. Because Google indexes your site by automatically following all of its links, you should always be sure that URLs on your pages point to the "proper" path rather than the default `node/1` style.
Internally, Drupal does this automatically: *all* URLs are passed through the [l()](http://api.drupal.org/api/function/l) function before they're displayed. Internally, modules always deal with the standard path (`node/1`, `user/1`, and so on) for a page on the site. Before outputting any links to a browser, they hand the l() function the standard path, and it spits out the "best" possible version of a given path: a friendly alias if one is available, the default path if the web server supports clean URLs, and the "ugly" index.php style if no other options are available. All Drupal modules are expected to use this function rather than hard-coding URLs: in fact, code that doesn't use the l() function [is considered buggy](http://drupal.org/node/2318).
### Covering all the bases
Thanks to the l() function, links generated by Drupal will always point to the "clean" version of the URL and Google will never see the duplicate versions. The unfriendly URLs, though, are still sitting there: what happens if other people link to them, and Google follows those links?
Google recommends using HTTP 301 redirects to solve this problem: they tell web browsers that the requested content *actually* lives at another URL. Web browsers will automatically jump to the correct URL, and search engine web-crawlers respect these redirects as well.
In Drupal, the [Global Redirect](http://drupal.org/project/globalredirect) module generates 301 redirects whenever a user visits a standard URL when a friendly path alias has been defined. It also generates a 301 redirect if someone visits an old-style "ugly" url like `http://www.example.com/index.php?q=node/1`. The end result? No more duplicate content, period. Google will always see your content at the best possible URL, regardless of how users link to it.
### Recap
For everyone who's read this far (or those who skipped to the end for the "good parts"), a summary is in order.
1. Drupal gives content friendly URLs with the Path module, and automates the process with the [PathAuto](http://drupal.org/project/pathauto) module. However, content remains available at the old URLs as well.
2. Thanks to the l() function, Drupal outputs the best possible version of the URL when generating links to internal content.
3. If Google finds links to the "ugly" URLs, it will index them, but only one version of the page will be displayed in search results.
4. To ensure the best version of every URL appears in Google search results, use the [Global Redirect](http://drupal.org/project/globalredirect) module.
Published in:
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Managing Projects with GitHub"
url: "/articles/managing-projects-with-github"
type: article
date: 2012-06-20
updated: 2019-02-05
---
# Managing Projects with GitHub
# Managing Projects with GitHub
Some in depth tips on managing your project with GitHub.
By
[ Jerad Bitner ](/about/jerad-bitner)
June 20, 2012
We've tried a lot of project management systems over the years. In some way, they have always seemed lacking, confusing or just a pain in the rear end. If they had good tools for project managers, they were confusing to developers. If they were useful for developers, designers complained about the eye-sores. No one system ever seemed to satisfy the team.
We recently started using GitHub for project management after the developers started raving about how much they loved it for managing code. To our surprise, GitHub has proven a solid option for project management. Our designers have even started using it for some of their projects, which I think says something about GitHub's aesthetics. With a little bit of something for each role, GitHub is starting to come out on top as the tool of choice for hosting code, managing projects, and facilitating project communication.
## Project Introductions
GitHub is pretty developer-centric. As such, the first thing a developer sees when they open a project, is a view of the code repository. Below that, GitHub automatically renders the README file found in the root of the code base. It's a very typical practice for software projects, especially open source software projects, to have this file in place. The README can be in various formats, but a favorite of mine is [Markdown](https://daringfireball.net/projects/markdown/). Simply giving the README an extension of .md tells GitHub to render your README.md using the Markdown syntax. Even better, GitHub has it's own [flavor of markdown](https://github.github.com/github-flavored-markdown/). Since the developers of your project see the README first, this is a great place for information that will get them up and running with the project as quickly as possible. Be concise. If you need to write more than a few sentences, chances are, you should be linking off to more in-depth documentation in your project's wiki. Here's a quick guideline of some of the things that you might want to include in your README.
1. A quick project overview.
Provide a short description of the project's goals and a bit of background. Any links that you frequently access are also good to include up at the top as well, for easy access. Everyone loves easy access.
2. Information about the directory structure.
Typically we have more than just Drupal in our repository root, so it's helpful to have a brief description of what is in there. We typically have a [drush folder](https://github.com/Lullabot/drupal-boilerplate/tree/master/drush) for aliases and commands, as well as a patches directory with its own README.
3. How to get started developing.
Tell the developers what the best way to jump into the project might be. Things like, "clone this repository, create a feature branch, run the installer, download a copy of the database, etc.. Whomever reviews the pull request should also do things like remove the remote branch from the repository once it is merged."
4. Code promotion workflow.
It's a good idea to outline your development process, as it may change from project to project. Do you want people to fork your repository and send pull requests; create feature branches and send pull requests; or just go ahead and commit to master? Let the developers know up-front, so there's no confusion.
5. Environments.
Outline information for your dev, staging and live environments, if you have them. Also, outline the process for getting things to the various places. How do I make sure my code is on staging? What is the best way to grab a database dump? We like to setup drush aliases for each environment ahead of time as a means of outlining this information and giving developers a good starting point. This document contains some example commands for doing some typical operations. [Here's an example](https://github.com/Lullabot/drupal-boilerplate/blob/master/drush/aliases/example.aliases.drushrc.php).
6. Links to where to find more information.
Typically this is our wiki, where we keep more detailed documentation and notes on things; project details like the original proposal's SOW, credentials to environments, Scrum Notes, Pre-launch checklists, etc.
We've attempted to create a [drupal-boilerplate](https://github.com/Lullabot/drupal-boilerplate), of sorts, for our Drupal projects which we're continuously re-using for new projects and modifying when we find things that work better. Take a look, and if you find it useful, please feel free to use it! If you find anything missing, or have ideas on improving it, please fork it and send us a pull request!
## Working with GitHub Issues
GitHub has a pretty simple issue management system for bug tracking, but it is flexible enough to be a pretty powerful tool for managing entire projects, large and small. It has issues which can reference each-other; labels for attaching meta data to your issues; methods for attaching code to your issues; and even milestones for grouping and focusing your issues within time blocks.
### Referencing and Association
Issues can be associated with each other by simply throwing an #issue-number (ex: #3) within the body of another issue. This is useful in many ways. Firstly, it keeps the relationship simple. We don't have to worry about what kind of relationship it is (parent/sibling/child), just that it's related. Nevertheless, there are a couple of tricks that make this a little more useful if you understand how it works. Let me give you an example.
Let's say you typically create an issue for a content type, and one of the fields on that content type is a taxonomy vocabulary. You probably want to break that vocabulary creation out into it's own issue. So you create the issue for the news content type and then you create an issue for the taxonomy vocabulary and, within your description, link to the news issue. Just by putting in the #ticket-number (in this case #4) GitHub creates a link to the news issue AND it places a back-link reference within the news issue to your tags issue!
As a part of this reference you will notice that it also gives you the status of the referenced issue. Very handy for whomever is assigned this news issue. They can easily see the status of it's 'dependency'. I use that term loosely because it is a dependency in this instance, but not always.
### Issue Labels
Tags are a simple and effective way to add metadata to your issues. A lot of systems tend to create fields and categories with various values in an effort to allow you finite control of the metadata for an issue. I've found the simple tagging system that GitHub employs to be very efficient and more flexible.
GitHub comes with a few labels by default: bug, duplicate, enhancement, invalid, question, and won't fix. These give you a good idea of how to start using labels. For example, "bug" is a type of issue, while "won't fix" is more of a status. Tags can be anything, and if chosen wisely, can give any developer an immediate clue as to what sort of ticket it is, what section it might apply to, or what status it is in at a quick glance.
While they're useful for developers, they're also good for the organizer of the project in that they serve as a great filtering mechanism as well. For instance, just by selecting various labels, I can see all of the issues that are "migration" issues for "taxonomy", or "content types."
### Attach Code to an Existing Issue
Pull requests are an amazing tool for code collaboration. If you're new to the concept, check out this [pull request demo](https://vimeo.com/41045197). It's a quick and easy way for developers to basically create a copy of the code base (by either forking or branching) and suggest modifications to the existing code, or contribute new code. It allows the other members of the project to then review that code, make their own suggestions with in-line commenting, and then make a decision as to whether to merge it into the main code base or not. We've found the in-line commenting with pull requests to be immensely useful since they keep everyone in the project in the loop with changes that are happening.
Pull requests in general are a great means of peer review and have helped to keep the quality of our code up to everyone's standards. There's a bit of overhead in that it may take a little longer for some new piece of code to be merged in, so plan accordingly. But this also means we find bugs sooner, typically before they're actually introduced into the master branch of the code.
I had one gripe with pull requests: when you create one through GitHub's web interface, it basically creates a new issue. Though you can certainly reference a particular issue within your pull request, it's still a separate issue. However, through a nice command-line tool called [Hub](https://github.com/mislav/hub), we've found there's a way to [turn issues into pull requests](https://www.youtube.com/watch?v=suS3lDn20HY)! Very handy for keeping your discussions and code all in one place and not having to deal with multiple issues about the same thing.
### Milestones
GitHub has a mechanism for milestones that is actually quite typical of many project systems these days. When you create a new milestone, it simply has a title, description, and a date choosing mechanism. You can have a nice overview during the time-boxed iteration that gives you the percentage complete. We tend to only plan one sprint ahead, but there is a milestone created for each iteration up until the end of the project. We grab these tickets from the Backlog, which is essentially just any ticket that is **not** in a Sprint.
## Huboard
GitHub's issue tracking system lacks a mechanism for prioritizing your issues. You could probably come up with labels for High, Medium and Low priorities, but I tend to prefer an ordered list with the highest priority things on top.
Enter [Huboard](https://github.com/huboard/huboard), which gives you a nice Kanban-style interface (similar to [Trello](https://trello.com/)) right on top of the GitHub api. You're looking at your GitHub issues, but with a different interface. The instructions for setting this up are quite sufficient, so I'll not re-iterate those, but I've found that it's quite easy and quick to setup on [Heroku](https://www.heroku.com/) with very little maintenance overhead. With Huboard, we now have a means of seeing what the priority tasks are for the week and it gives developers an easy way to see what they should work on next.
## Logins
Some project management systems require a new login for every instance of the software. For instance, if you have two different clients using the same project management software you may have to remember two different username and password combinations and your authentication will not transfer from one to the other. Github allows users to access all the projects and repositories you have permission to without multiple authentication.
Github is lean and spare, and you may find there are features missing that you're accustomed to. Luckily, the team over at GitHub is continually making improvements to their product and they [blog](https://github.blog/) about it often.
In summary, GitHub is great for the technically-minded person, but less tech-savvy people may not find it as attractive. I'm still working on ways to report on progress to project stakeholders in a more visual way and when I find one I like, I plan to update you all.
**Update**: Checkout the [Development Matrix](https://www.lullabot.com/articles/building-a-development-matrix) for a way to report on progress to project stakeholders.
If you have any suggestions on things we might also do to improve our process, or would like to share with us some of the exciting things you're doing with your own processes, please hit us up in the comments section! We'd love to hear from you! And remember, Lullabot loves you!
Read this [article on GitHub](https://github.com/Lullabot/github-pm).
Published in:
- [ Drupal Development ](/topics/drupal-development)
- [ System Administration ](/topics/system-administration)
- [ Technical Project Management ](/topics/project-management)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal Usability: Comment Configuration"
url: "/articles/drupal-usability-comment-configuration"
type: article
date: 2006-07-12
updated: 2014-05-15
---
# Drupal Usability: Comment Configuration
# Drupal Usability: Comment Configuration
By
[ Jeff Robbins ](/about/jeff-robbins)
July 12, 2006
Drupal offers many different settings for displaying and collecting comments. Listings can be displayed in forward or reverse chronological order, and comments can be displayed in a tree-like hierarchy so that it is possible for people follow the thread as users comment on comments.
However from a usability standpoint, most of these options are NOT GOOD.
In this article, I'm going to run through the various Drupal comment options and the pros and cons of each setting.
## Viewing Options
### Display Mode

#### Collapsed vs. Expanded
From the "I Don't Know Why Drupal Even Has This Option" department comes the "collapsed" option which breaks every comment out onto its own page. This makes it virtually impossible to follow series of comments because reading each requires clicking on its title in the list, reading it, hitting the back button, and scrolling down to try to find your place in the comment listing. Does anyone use this? **Use expanded.**
#### Threaded Lists
While this option looks useful on its surface, in use it means that comments will no longer be listed in chronological order. By placing listings in a threaded list, comments are listed by thread before they are listed by date. So the latest posts will not necessarily be at the bottom of the page. In addition, many users don't understand the concept of the threaded comments and will click on any "reply" link splitting off a thread when they really mean to respond to the original post. Keep in mind that you want the users to post ON THE TOPIC OF YOUR POST and NOT to wander off onto tangental subjects. You want comments on any given node to be ABOUT THAT NODE. If the subject of a discussion changes, it should move elsewhere. People will not come to an entry entitled "Drupal Comment Usability" to find discussion about configuring clean urls - so your user interface should discourage this type of tangental commenting.

### Display Order
While it is the convention of blogs to show the latest blog entries at the top of the page, do not get confused and believe that comments should be handled the same way. Most visitors to a web page will expect that they will be able to follow the history of a page by reading from top to bottom. This means that the latest comments should be listed at the bottom therefore comments should be listed **oldest first**.

### Comment Controls
By enabling comment controls, it is possible to allow the users to configure any of the above settings themselves - essentially destroying any comment configuration that the administrator has done. I'm from the school of: "Give the options to the administrators, NOT to the users". It will be the novice users who arrange their settings so that they can no longer follow the comments. They will call you on the phone asking what has happened to your site. I recommend setting comment controls to "**do not display**".

### Comments Per Page
While shorter pages are generally more usable, setting the number of comments per page to a low number comes in conflict with my recommendation of setting the display order to oldest first. With a small number, it is very possible that the latest comments will not be listed on the initial page and will require several clicks and scrolling to get to them. This is bad. A better solution is to set the comments per page **as high as possible**. Users will intuitively scroll down to find the latest stuff.
## Posting Settings

### Anonymous Commenting
Anonymous commenting is good. Most users will not want to register for a site simply to post a comment. However allowing random comments opens up a site to comment spam. These comments are placed on sites across the net in order to link people to a commercial (usually gambling or porn) site and also to increase the site's Google ranking.
A solution to this problem is to visit admin/access and set permissions so that anonymous users can post comments, but their comments will require approval. Then either visit admin/comment/list/approval on a regular basis to see if there are new comments or use a solution like [Comment Mail module](http://drupal.org/node/50733) or [Actions](http://drupal.org/project/actions) and [Workflow](http://drupal.org/project/workflow) (untested) to have email sent to the site administrator when new comments (requiring approval) are posted to the site.
Contact information for anonymous users can be optional, required, or not collected at all. I prefer the optional option so that users can post to the site anonymously or give themselves credit if they choose.
Anonymous commenting is enabled on the "administer >> access" page in Drupal.
### Other Comment Options

**Subject Field:** Technically speaking the subject of most comments will be the post itself. So the comment subject line often ends up being something like, "Agreed" or "Another thought", which doesn't really mean much. However the comment subject line is used in the comment block and several other places in Drupal where comments are listed. If it is not enabled, Drupal will use the first few words of the post as the subject line. It's a close call on this one, but I'm going to recommend leaving it **enabled**.

**Preview Comment:** Requiring comments to be "previewed" before posting provides another line of defense against comment spam. And since anonymous users will not be able to edit their comments once they are posted to the site, it is also a last chance to review their post before it is committed. However, from a usability standpoint, the idea of adding an extra screen to the posting process is confusing. Many users will get to the preview screen and assume that since they are seeing their comment presented on the screen, the comment has been posted to the site. They could navigate away and never have their comments actually posted.
My recommendation: If you're site has **primarily registered users, do not require preview**. If you're site has **primarily anonymous (non-logged-in) users, do require preview**.

**Location of Comment Form:** This one really depends on the design esthetic of your site. You can choose to place the comment form at the bottom of the comments on the post page, or on a separate page. From a usability standpoint it is clearer to a user that they CAN post a comment if there is a form presented right there at the bottom of the comments. It really comes down to how hard you want to push for comments to your posts. So basically, if it doesn't turn your designer's stomach, **place the comment form at the bottom of the post page**.
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Drupal Performance Tip: Block Visibility"
url: "/articles/drupal-performance-tip-block-visibility"
type: article
date: 2009-10-08
updated: 2016-04-07
---
# Drupal Performance Tip: Block Visibility
# Drupal Performance Tip: Block Visibility
Drupal back-end performance
By
[ Lullabot ](/about/lullabot)
October 8, 2009
This is something we hit a lot when doing performance analysis on very slow websites, so I figured I'd issue a public service announcement. :)
It's not uncommon in more complex themes to have many different block regions, and even dynamic regions that will only appear on certain pages or when viewing nodes of certain types. One very common use-case is to have both a page.tpl.php, and a page-front.tpl.php, each of which print out different regions, particularly for ads or promotions:

Defining block regions is super easy; simply add a couple lines in your theme's .info file:
```
regions[ad_top] = Ad Top
regions[ad_bottom] = Ad Bottom
regions[front_sidebar] = Front Sidebar
regions[sidebar_ad] = Sidebar Ad
regions[content] = Content
regions[feature_a] = Feature A
regions[feature_b] = Feature B
regions[feature_c] = Feature C
regions[feature_d] = Feature D
```
And then in your \*.tpl.php file, wherever you want the region to appear, simply print out its machine-readable name:
<?php print $feature\_a; ?>
Don't want the blocks in the "Feature A" region to show up in page.tpl.php? No problem! Just don't print the region out there! Done! Right? For many people, their concern about block visibility ends there; they're not showing up, so they move on with their day. However, this can have a profoundly negative performance impact on your site.
The [block\_list()](http://api.drupal.org/api/function/block_list/6) function has no knowledge of which block regions are printed out on *this* page. This means that **Drupal's default behaviour is to render *every* single block on your site that's assigned to *any* region on *every* single page view**, regardless of whether the region's content is actually being visibly printed out or not.
If you're doing a bunch of complex queries in those feature blocks, and you're not hiding the block by one of the following:
- Setting the block's visibility settings (on the block's configuration form) to not show up on pages that do not have the region it's assigned to printed. For example, setting any blocks assigned to the Feature A region to only show up on the `` page.
- Hiding the visibility of the block by some other means; for example, by limiting it to a role using the checkboxes on the block configuration form (or content types in Drupal 7), or installing a contributed module that implements hook\_db\_rewrite\_sql() on the block list.
...then your server is probably melting. :P
Incidentally, setting `` visibility by hand in each block that should only appear on the front page can be fairly tedious. You might also have a look at the [Block Page Visibility](http://drupal.org/project/bpv) module, which allows you to hijack the block visibility settings. It'll prevent them from being set anymore in the UI, but will allow you to establish whatever complex visibility logic you need in code instead.
So remember: don't rely on regions and template files to hide blocks; they do so visually only. Using block visibility settings will ensure that extra processing is not performed on blocks that aren't being printed out in the first place, and keep your server nice and speedy!
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Keeping Your Content Types Tip-Top"
url: "/articles/keeping-your-content-types-tiptop"
type: article
date: 2013-12-04
updated: 2021-01-12
---
# Keeping Your Content Types Tip-Top
# Keeping Your Content Types Tip-Top
How we kept content strategists and developers in sync when building MSNBC.com
By
[ Sally Young ](/about/sally-young)
December 4, 2013
In the world of content strategy, spreadsheets are a critical tool for planning and communication. In particular, content types are often defined and refined in spreadsheets before they're committed to code or CMS configuration.
The challenge comes once everyone "agrees" and the content types are implemented. If the model is updated in any way, it's easy for them to fall out of sync. The CMS is tweaked, but the spreadsheet is never updated to match, or decisions are made by the content team and entered in the spreadsheet but they never make it to the CMS configuration. In addition, if developers misunderstand the spreadsheet or make mistakes when implementing the content types, the mismatch can be easily overlooked.
While working on the recently launched redesign of [MSNBC.com](https://www.ms.now/), we found ourselves in just that situation. The back-and-forth fixes between the content modeling people and the developers turned into an ongoing time-sink and everyone was frustrated.
## The Solution
I hate doing work that computers can do better. Faced with this spreadsheet/CMS synchronisation challenge, I built a tool that handles it automatically: the CheckSheet module for Drupal. It takes a spreadsheet describing a site's content types and fields, compares it to a Drupal site's content type settings, and flags any discrepancies for review. It provides an admin screen on the Drupal site for site builders and a Drush command for those who prefer the command line.

In the screenshot above, the Article and Page content types are both out of sync with the spreadsheet. For example, the Page's `body` field should be required, and it should have a `publish_date` field -- the CheckSheet module spotted that mismatch and alerted us.
## How it works
During the MSNBC development process, we used Google Docs to store the "master" spreadsheet: it was treated as the canonical source for information on our content types. If the CMS didn't match the spreadsheet, we assumed the CMS was wrong. We exported this spreadsheet to .ods format, and checked it into the project's source control tree to preserve a historical record of the type definitions.
Whenever someone wanted to verify that the site was "in sync," they ran the Drush command or checked the admin page to spot mismatches. Because it's available as a drush command, it's relatively easy to make it part of an automated testing and continuous integration process. It's still up to the developers to fix the mismatches, but the process of spotting them is unambiguous and easy to document.
## Use it, improve it, and share your techniques
The CheckSheet module is a quick-and-dirty tool to simplify our work, not a polished product: it assumes a very specific format for the spreadsheet. It can verify the name, help text, data type, required flag, and "single/multiple value" setting for any given field. Additional columns can be added to the spreadsheet to store more information for documentation purposes, but the module will ignore them.
It also assumes that the spreadsheet is in .ods format, and located in the actual module directory: if you're using Excel, Pages, or Google Docs you'll need to export to .ods before the module can parse it. In the future, we'd like to integrate it with Google Docs directly⦠for our work on MSNBC.com, though, it served its purpose well.
The CheckSheet module is currently living in [Lullabot's GitHub repository](https://github.com/Lullabot/drupal_checksheet), and includes an example .ods format spreadsheet that demonstrates how a few content types can be defined. Give it a spin, add features, and post ways that *your team* has helped keep strategists, architects, and developers working together smoothly.
Published in:
- [ Digital & Content Strategy ](/topics/content-strategy)
- [ Drupal Development ](/topics/drupal-development)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Entityreference Multiselectors"
url: "/articles/entityreference-multiselectors"
type: article
date: 2013-03-13
updated: 2014-05-15
---
# Entityreference Multiselectors
# Entityreference Multiselectors
By
[ Karen Stevenson ](/about/karen-stevenson)
March 13, 2013
I'm working on another Drupal site and as usual, it is going to make heavy use of the entityreference field to link entities together. Many of these fields are multiple value fields, and we need an easy way to allow editors to select multiple values from some long lists of potential values.
We have a couple options out of the box. We can display the options as checkboxes, an autocomplete field, or a drop-down select list. These are long lists, too long for checkboxes. And a long list in a multiple value select list is ugly and hard to use. That leaves the autocomplete, which is fine if you know what values to expect, but it's not very good for discovering or sorting through the available options. I'm looking for something that handles long multiple value lists better. It should make it easy to see what's been selected and what's available to be selected and be easy to use.
I finally pulled down a collection of possible Drupal 7 contributed modules to review to see what the options are. Here's a line up of some of the options with screen shots to show what each of them looks like and a little information about how to get them working and what they do.
For a point of reference, here's what the unvarnished Drupal multiple value selector looks like.

## Multiple Selects
One option is [Multiple Selects](http://drupal.org/project/multiple_selects). This is a fairly simple rework that turns a single multiple value select list into a series of single value select lists, with an 'Add more' button. It's very easy to set up, just enable the module and choose the 'Multiple Selects List' widget as the widget.

## Chosen
Another interesting option is [Chosen](http://drupal.org/project/chosen). Chosen is based on a jQuery library that pulls together some of the benefits of both an autocomplete and a drop-down selector. The selected values are listed at the top of the selector. The bottom of the selector has a drop-down list of the available options. In between is an autocomplete box where you can type values that will be used to narrow the list.

To install this module, you have to enable the Libraries module and grab the jQuery Chosen library and drop it in sites/all/libraries/chosen. Then go to admin/config/user-interface/chosen and indicate when the Chosen selector should be applied. It can be set up to only apply to long lists of options and leave short lists alone and there is a jQuery selector that can be used to identify which elements it should be applied to. You need to set this up carefully because this will affect every select list on the site, not just those for a specific field.
## Dynamic Multiselect
An additional possibility is the [Dynamic Multiselect Widget](http://drupal.org/project/entityreference_dynamicselect_widget). To use it you need to enable Dynamic Select and Entity Reference Dynamic Select Widget. Then you need to create a view using the 'Dynamic Select' display type. The view should be a list of the values you want to see in the select list. Finally, change the Entityreference field to use the Dynamic Select widget, and set up the widget to use the view that you just created.
The result looks like the following, where every selected item shows up with an individual selector. At the bottom is an 'Add more' button that you can use to select another option.

It took me a while to figure out what the 'Filter' options in the widget were for but I finally understood. Basically each drop down select list is a view, and the 'Filter' option to its right is a custom exposed filter for that view that allows you to filter the list to its left. I added a new filter to the view for the node title using the 'contains' operator, edited the field widget settings to indicate that I wanted to use the 'Title' filter in the widget, and after that I could type a title or partial title into the filter textfield and it would limit the list to its left to just the values that matched the value I typed in.
## Entityreference Views Widget
Another option is [Entityreference Views Widget](http://drupal.org/project/entityreference_view_widget). This widget uses a view to display the available options, making it possible to display lots more information about each option, like title, images, description, etc.
To install it, create a view of the items that should be displayed in the selector using a display of the type 'Entityreference Views Widget'. change the widget to the 'View' widget and select the view you just created. Then check the 'Display fields' tab for the content type being displayed in the widget and adjust the 'Entity Reference View Widget' view mode to identify the fields that you want to see in the selector.

This option makes it possible to see lots of information besides the title of the related items, so you could display images or descriptions to help indicate which is which. The downside of this widget is that it takes up a lot of space on the node form. It would be nice if it opened up in a modal window and just displayed just the selected items in the form to take up less space.
## Improved Multi Select
The [Improved Multi Select](http://drupal.org/project/improved_multi_select) module is another option. To install it, enable the module, then go to admin/config/user-interface/improved\_multi\_select and choose the options. You can apply this to all multiple value selectors on the site, or indicate which ones to use, and you can indicate which paths to apply the effect on. At a minimum you will want to add 'select\[multiple\]' as the replacement. The result looks like the following:

## jQuery UI Multiselect
Finally there is [jQuery UI Multiselect](http://drupal.org/project/jquery_ui_multiselect). This is another jQuery effect. It requires the jQuery Update module. Once enabled multiple select form elements are transformed to look as follows:

The settings are controlled from admin/config/user-interface/jquery\_ui\_multiselect\_widget. It will work out of the box with the default settings, but they can be adjusted.
## Which is Best?
So that's my list. There are probably other alternatives but these are the ones I knew about or could find that have Drupal 7 releases. I don't want to even attempt to evaluate which of them is the 'best', because that depends on how you want to use it.
But I think it's helpful to see some of the available alternatives all in one place to help figure out which of them is the 'best' solution for your specific site.
Published in:
- [ UX & Design ](/topics/design-and-ux)
- [ Drupal Site Building ](/topics/drupal-site-building)
You must have JavaScript enabled to use this form.
## Get in touch with us
### Tell us about your project. We'd love to hear from you!
First Name
Last Name
Your email address
Your organization
How you heard about us
Tell us more
Leave this field blank
---
---
title: "Imagecache Example: User Profile Pictures"
url: "/articles/imagecache-example-user-profile-pictures"
type: article
date: 2007-01-24
updated: 2014-05-15
---
# Imagecache Example: User Profile Pictures
# Imagecache Example: User Profile Pictures
By
[ Nate Lampton ](/about/nate-lampton)
January 24, 2007
User profile pictures (or avatars) have been around for about as long as online bulletin board systems themselves. Drupal has included this ability for several versions, but we've found it lacking in both user friendliness and consistency.
If you're using the default Drupal user pictures. Your site's users must upload an image exactly to the dimension specifications. The default is 85x85 pixels, and often that will mean a special trip to the GiMP or Photoshop just to make such an image. Additionally, some users might upload a photo which isn't even that big, like a 16x16 icon. Your designers probably aren't going to like creating a design that needs to support an image from 1x1 pixel up to 85x85 pixels.
We've found that we implementing the following system in the community sites we develop solves these problems. Using imagecache and hook\_form\_alter(), we can make beautifully consistent and easy user profile pictures.
Getting Started
The first thing you need to do is enable user pictures. Head on over to admin/user/settings (in Drupal 5) and you should have the following options at the bottom of the screen.

Configure your screen so that it is similar to the following.

Now if you go to your user account configuration (such as user/1/edit), you should have a photo section such as this. If you don't see anything, make sure that you changed the admin/user/settings Picture support to 'Enabled'.

Now we get down to the fun stuff. You'll need to [download the imagecache.module](http://drupal.org/project/imagecache) and install it. Imagecache module has some steep requirements, so be sure all these things are also setup:
- You have GD2 installed with JPEG, GIF, and PNG support
- Clean URLs are Enabled (admin/settings/clean-urls)
- Open the .htaccess file in you files directory and make sure it is exactly like the following:
```
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
Options None
Options +FollowSymLinks
```
If you have mod\_rewrite\_engine disabled in the files directory (a default in some versions of Drupal 4.7) imagecache will not work.
Enable the imagecache module, then open up the imagecache settings page (admin/settings/imagecache). Create a new imagecache preset called 'thumb'. Then setup two actions so your screen looks like this.

Make sure you 'Scale to fit' the Outside dimensions. Now the resulting image is at least the entered dimensions, so when you crop you'll always remove the larger side. You can play with the settings as you like to get the right display for your website.
Cool, now we've got an imagecache preset configured. Try it out by uploading a photo for your user on the site (at least 100x100 pixels!), then accessing the URL to the cached image directly. Let's say you're user #1 and you've already uploaded a picture using the default location of /files/pictures/picture-1.jpg. Then try accessing the cached image at: /files/imagecache/thumb/files/pictures/picture-1.jpg.
If you don't see an image exactly 100x100 pixels, then review all the steps above.
Okay, now we can really start some coding! The first thing we want to do is actually make Drupal use our new thumbnails for profile pictures.
Open up your template.php file in your theme's directory. If you don't have a template.php file, just create one and open up a php tag (<?php) at the beginning of the file.
We're going to override the default theme\_user\_picture function. You can just paste in the code below if you like:
```php
/**
*
* Insert into your theme's template.php file:
*
* Theme override for user.module
* Utilized imagecache module to scale down large uploaded profile pictures
* @param $size
* Image size to scale to. Options: thumb (default) and large
*/
function phptemplate_user_picture($account, $size = 'thumb') {
if (variable_get('user_pictures', 0)) {
// Display the user's photo if available
if ($account->picture && file_exists($account->picture)) {
$picture = theme('imagecache', $size, $account->picture);
}
return '