# 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 --- --- title: "Matt Westgate" url: "/about/matt-westgate" type: bio date: 2015-06-23 updated: 2023-06-27 --- # Matt Westgate # Matt Westgate Co-Founder & Former Chairperson Providence, RI - He - Him Matt’s career began when he graduated from Iowa State University in *Computer-Mediated Technologies*, a degree program he literally built from the ground up that focuses on the emerging field of web applications and information architecture. After graduation, Matt teamed up with [John VanDyk](https://www.sysarchitects.com/) to develop MFramework, a metadata-driven Content Management System which later became an important cornerstone to the foundation of Drupal's CCK platform. Matt and John also authored the [Pro Drupal Development](https://www.amazon.com/Pro-Drupal-Development-John-VanDyk/dp/1590597559) book, a must-have resource for any Drupal core developer. Matt has been an active member and supporter of the Drupal community from the beginning and is credited with over 600 commits. He contributed the path aliasing module and table sort library to Drupal core and wrote the original e-commerce package as a proof of concept that Drupal was much more than a CMS. In 2006, [Matt launched Lullabot with co-founder Jeff Robbins](https://www.lullabot.com/about#block-block-contentad515c1f-b0b5-4cd4-a759-a53033dee43c). Matt built Lullabot’s [unique culture](https://www.lullabot.com/values) on trust and open collaboration, a foundation embraced by the whole team that is the driving force behind Lullabot’s continued success. While CEO of Lullabot, Matt was also recognized as one of the [50 Best CEOs](https://www.usatoday.com/story/money/business/2018/12/11/microsoft-ceo-satya-nadella-ranked-best-leader-us-comparably/2206490002/) among small and mid-sized businesses. ## Featured resources ... [ More ... ](/resources) [### The Next Generation of Lullabot Read ](/articles/next-generation-lullabot) [### Supporting Mental Health at Lullabot Read ](/articles/supporting-mental-health-lullabot) [### Lullabot Education Becomes Osio Labs Read ](/articles/lullabot-education-becomes-osio-labs) --- --- title: "Jeff Robbins" url: "/about/jeff-robbins" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Jeff Robbins # Jeff Robbins Co-Founder & Former CEO Seekonk, MA Jeff Robbins is the co-founder and former CEO of Lullabot. He worked at [O'Reilly](https://www.oreilly.com/) as an illustrator and systems administrator as the World Wide Web came into being. He was involved in the early stages of the first commercial website, O'Reilly's [Global Network Navigator](https://en.wikipedia.org/wiki/Global_Network_Navigator), but left to start one of the first web development companies, Liquid Media, in 1993. In 1994, Jeff's band [Orbit](http://www.orbitband.com) signed to A&M Records. Over the next 6 years, the band recorded 3 albums for A&M, scored a modern rock top 10 hit, appeared on MTV and MuchMusic, and toured extensively throughout the U.S. and Canada playing many radio festivals and the Lollapalooza tour. Before co-founding Lullabot, Jeff developed websites for [Ringo Starr](https://www.ringostarr.com/), [Participant Productions](https://participant.com/), [Adaptive Path](http://adaptivepath.com/), the [Fearless Living Institute](https://fearlessliving.org/), and many others. He also ran a soundtrack music company called SomeMusic. Jeff has [contributed](http://drupal.org/user/17190) over 20 Drupal modules and themes, including popular favorites such as LoginToboggan, and the Zen theme. Jeff believes that free open source software doesn't need to be ugly and difficult to use. Along with Matt Westgate, he began Lullabot as a way to improve these tools and make them more accessible to companies and individuals who are looking for a non-proprietary solution with social relevance. Open source development makes the world a better place. Jeff lives in Seekonk, MA with his wife [Jennifer](https://twitter.com/jenville), a graphic designer and author of O'Reilly's [Web Design In A Nutshell](https://www.oreilly.com/library/view/~/0596009879/), and their son Arlo. [Follow Jeff on Twitter](http://twitter.com/jjeff). ## Did you know? Jeff's band played the Lollapalooza tour in 1997. ## Featured resources ... [ More ... ](/resources) [### The Genesis Of Lullabot Read ](/articles/the-genesis-of-lullabot) [### Lullabot Celebrates 10 Years Today Read ](/articles/lullabot-celebrates-10-years-today) [### Lullabot's 7th Annual DrupalCon Party Read ](/articles/lullabots-7th-annual-drupalcon-party) --- --- title: "Nate Lampton" url: "/about/nate-lampton" type: bio date: 2015-06-23 updated: 2024-02-01 --- # Nate Lampton # Nate Lampton Senior Technical Architect Oakland, CA - He - Him Nate Lampton is a leader in Open Source, with over a decade of contributions to the Drupal project and a founder of [Backdrop CMS](https://backdropcms.org), a low-cost alternative to Drupal. Nate has a wide skill set, ranging from front-end development to server-side performance optimization. Nate has brought his skills to many of the largest companies and websites in the world, including Sony Music, NBC, Turner Media, Harvard University, the Grammys, Tesla Motors, and many others. Nate provides services of architecture, development, performance analysis, and consulting. He has taught over 60 Lullabot Training courses as an instructor in Lullabot Learning Series. He is also the O'Reilly author of the first edition of "Using Drupal". Nate is a frequent speaker at conferences, where you can meet him in person, or follow him[ on Twitter as @quicksketch](http://twitter.com/quicksketch). He also loves board games, home improvement projects, and animals. ## More about Nate ### Drupal Contributions: - [Download & Extend](https://www.drupal.org/project/drupal), credited on 3 issues - [Views (for Drupal 7)](https://www.drupal.org/project/views), credited on 1 issue - [FileField Sources](https://www.drupal.org/project/filefield_sources), credited on 1 issue - [Layout Paragraphs](https://www.drupal.org/project/layout_paragraphs), credited on 3 issues - [Page Templates](https://www.drupal.org/project/pate), credited on 3 issues - [Metatag](https://www.drupal.org/project/metatag), credited on 1 issue - [... and more.](https://www.drupal.org/u/quicksketch) ### Projects Worked on at Lullabot: - State of Iowa - The Evergreen State College - Excel Jet - [New Relic](https://www.lullabot.com/our-work/new-relic) - [Carnegie Mellon University](https://www.lullabot.com/our-work/carnegie-mellon-university) - The Recording Academy (Grammys) - Harvard University - Tesla Motors - NBC - NewsBank - [Pantheon](https://www.lullabot.com/our-work/pantheon) - [Edutopia](https://www.lullabot.com/our-work/building-a-new-edutopia-on-decoupled-drupal-8) - Turner Broadcasting - Sony Pictures Television International - MTV UK ### Education & Certifications: - Bachelor of Science in Computer Science, Truman State University - Bachelor of Fine Arts in Visual Communication, Truman State University ## Featured resources ... [ More ... ](/resources) [### Social Share Links Are (Probably) Spying On You Read ](/articles/social-share-links-are-probably-spying-you) [### Fixing Docker and VPN IP Address Conflicts Read ](/articles/fixing-docker-and-vpn-ip-address-conflicts) [### Setting up SSL Offloading (Termination) on an F5 Big-IP Load Balancer Read ](/articles/setting-up-ssl-offloading-termination-on-an-f5-bigip-load-balancer) ## Featured work ... [ See all our work ](/our-work) - [ ### Pantheon An Amazing, Fast New Site for an Amazingly Fast Hosting Platform Pantheon ](/our-work/pantheon) --- --- title: "Jeff Eaton" url: "/about/jeff-eaton" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Jeff Eaton # Jeff Eaton Former Senior Digital Strategist Geneva, IL - He - Him Jeff brings more than two decades of diverse experience in publishing, enterprise infrastructure, and web development to his role at Lullabot. He's built ecommerce sites for florists, enterprise web systems for multinational corporations, and supply-chain automation tools for billion-dollar industries using technologies from Perl to ASP.Net. At Lullabot, he's designed and implemented large-scale web platforms for clients including Sony/BMG Music, Fast Company and Inc. Magazine, World Wrestling Entertainment, Verizon Wireless, Harvard University, and more. When he's not writing or speaking about multichannel publishing, structured content, and the business value of streamlined editorial workflow, Jeff hosts the [Insert Content Here](https://insertcontenthere.com/) content strategy podcast. In the Drupal world, he's best known as a co-author of O'Reilly Media’s *Using Drupal*; author of the popular Voting API, EVA, and Token projects as well as dozens of other plugin modules; the primary developer of Drupal’s core TokenAPI and a co-developer of FormAPI; and member of the content advisory committee for Drupal.org. In a previous life, Jeff worked as a freelance writer and a copy editor, jobs that he recalls fondly while designing editorial tools for today's content teams. He lives in Illinois with his wife Catherine, and their cats [Maddie](https://www.flickr.com/photos/70315202@N00/5358743708/) and [Joe](https://www.flickr.com/photos/jeffeaton/2934437533/). ## Did you know? Jeff Eaton owns more than 80 whiteboard markers, and calls them each by name. ## Featured resources ... [ More ... ](/resources) [### Custom Layout Options in Drupal 8 Read ](/articles/custom-layout-options-drupal-8) [### A Content Personalization Primer Read ](/articles/content-personalization-primer) [### Markdown Won’t Solve Your Content Problems Read ](/articles/markdown-wont-solve-your-content-problems) ## Featured work ... [ See all our work ](/our-work) - [ ### Pantheon An Amazing, Fast New Site for an Amazingly Fast Hosting Platform Pantheon ](/our-work/pantheon) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) - [ ### EYP Architecture & Engineering A Re-Architected Website for the World’s Preeminent Architecture Firm ](/our-work/eyp-architecture-engineering) --- --- title: "Addison Berry" url: "/about/addison-berry" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Addison Berry # Addison Berry Former Director of Education Copenhagen, Denmark Addi ([add1sun](http://drupal.org/user/65088)) has been involved with Drupal since 2006. She is Lullabot's former Director of Education and the Product Manager for [Drupalize.Me](https://drupalize.me/). She also travels around the world, speaking at events from local high schools to major conferences like [OSCON](http://en.oreilly.com/oscon2009/), working to improve Drupal and Open Source software. Addi has been working with technical documentation and training since 2000, and was the Drupal Documentation Lead from 2008 to 2010. She is one of the co-authors for the O'Reilly book Using Drupal, published in 2008. In March 2009 she was awarded a [Knight Foundation grant](https://knightfoundation.org/news/press_room/knight_press_releases/detail.dot?id=344961) to improve Drupal documentation. In addition to her focus on coordinating documentation efforts she's provided core patches, maintains several contributed modules, and has been involved with the [Drupal Dojo](http://groups.drupal.org/drupal-dojo) and the Google Highly Open Participation (GHOP) mentoring programs. In 2010 Addison was recognized as one of the [Most Influential Women in Tech](https://www.fastcompany.com/3017254/women-in-tech-2010/the-most-influential-women-in-technology-2010-the-evangelists) by Fast Company Magazine. ## Did you know? In 2010 Addison was recognized as one of the Most Influential Women in Tech by Fast Company Magazine ## Featured resources ... [ More ... ](/resources) [### Pausing the Drupalize.Me Podcast Listen ](/podcasts/drupalizeme-podcast/pausing-the-drupalizeme-podcast) [### DrupalCon Session Selection Listen ](/podcasts/drupalizeme-podcast/drupalcon-session-selection) [### Web Services Listen ](/podcasts/drupalizeme-podcast/web-services) --- --- title: "Karen Stevenson" url: "/about/karen-stevenson" type: bio date: 2015-06-23 updated: 2025-05-07 --- # Karen Stevenson # Karen Stevenson Board Chair Normal, IL Karen has been a Drupal contributor since 2006. She was a co-maintainer of the Content Construction Kit (CCK), the author of the Date and Calendar modules, as well as the author or maintainer of 40+ other Drupal modules like Schema.org Metatag. The Content Construction Kit, a fundamental Drupal system that allows users to create any number of content types and populate them with custom fields, was added to Drupal Core in Drupal 6 and Drupal 7. The Date module was added to Drupal core in Drupal 8. Karen has been actively involved in web development since the early days of the web. She created [ElderWeb](https://web.archive.org/web/20170206195822/https://www.elderweb.com/) in 1994 as one of the very first healthcare sites on the web and has created numerous personal sites, including a [personal blog site](https://www.karen-stevenson.com) that is currently focused on finance and the economy. At Lullabot, Karen serves as Chairperson of our Board of Directors. ## Featured resources ... [ More ... ](/resources) [### Employee Owners, This is Us Read ](/articles/employee-owners-us) [### Alternatives to Upgrading from Drupal 7 to Drupal 9 Read ](/articles/alternatives-upgrading-drupal-7-drupal-9) [### How and When to Upgrade from Drupal 7 to Drupal 9 Read ](/articles/how-and-when-upgrade-drupal-7-drupal-9) ## Featured work ... [ See all our work ](/our-work) - [ ### Martha Stewart Living Creating the Recipe for a Central Content Platform ](/our-work/martha-stewart-living) - [ ### Google AMP-ing up Drupal Google ](/our-work/accelerated-mobile-pages) --- --- title: "Seth Brown" url: "/about/seth-brown" type: bio date: 2015-06-23 updated: 2024-02-05 --- # Seth Brown # Seth Brown CEO Carbondale, CO - He - Him Seth joined Lullabot in 2010 to help build the web services practice. Gradually, Lullabot evolved away from its roots in Drupal education. Drupalize.Me spun off as Osio Labs, a new sister company, in 2016. Having overseen the growth of the client services side of the business, Seth took the helm of Lullabot in 2020. In 2021, Lullabot completed the transition to 100% employee ownership. Seth started out in the publishing industry as a writer and editor at his alma mater, Colorado College. Luckily it was 1995, so, being of Generation X, he was also tasked with creating the college’s first website. He's been building websites ever since. After 8 years in Higher Education, he joined Blue Tent Marketing, a digital agency based near Aspen, Colorado. As a leader and partner at Blue Tent, Seth built the web department from the ground up before leaving to join Lullabot. Seth lives in beautiful Carbondale, Colo., where he tries to keep up with three daughters across a range of activities that include Nordic skiing, volleyball, and rock climbing. Seth is head of the Western Colorado Drupal User's Group and services on Lullabot's Board of Directors. ## Did you know? Seth went on an 81-day Outward Bound Leadership Semester course and subsequently led wilderness courses for at-risk youth in Capitol Reef National Monument. ## Featured resources ... [ More ... ](/resources) [### Twenty Years of Lullabot Read ](/articles/twenty-years-lullabot) [### Does Working From Home Benefit the Environment? Read ](/articles/does-working-from-home-benefit-the-environment) [### Mistakes Agencies Make: A Story in Three Acts Read ](/articles/mistakes-agencies-make-a-story-in-three-acts) ## Featured work ... [ See all our work ](/our-work) - [ ### Martha Stewart Living Creating the Recipe for a Central Content Platform ](/our-work/martha-stewart-living) --- --- title: "Haley Scarpino" url: "/about/haley-scarpino" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Haley Scarpino # Haley Scarpino Former Senior Event Planner Des Moines, IA Haley is Lullabot's Senior Event Planner. If you pick up the phone to call Lullabot, chances are you'll talk to Haley. She coordinates our public and private workshops, handles travel and logistics as the Lullabot team journeys around the world, and heads up customer service help for the Lullabot Store. She keeps workshops running smoothly, helps organize the annual Do It With Drupal, and generally greases the wheels and hinges to keep the big Lullabot robot rolling along. Haley lives in Des Moines, IA with her dog Nina. She's got a penchant for thoroughness and a great sense of humor. ## Did you know? Haley's birthday is February 14th. ## Featured resources ... [ More Resources ](/resources) [### Lullabot Sponsoring DrupalCamp Ohio Read ](/articles/lullabot-sponsoring-drupalcamp-ohio) [### Lullabot Sponsors BADCamp 2012 Read ](/articles/lullabot-sponsors-badcamp-2012) [### Do It With Drupal! Read ](/articles/do-it-with-drupal) --- --- title: "Jerad Bitner" url: "/about/jerad-bitner" type: bio date: 2015-06-23 updated: 2026-08-04 --- # Jerad Bitner # Jerad Bitner Lead Technical Project Manager Gig Harbor, Washington Jerad's journey with Drupal began in the early days of 2005, navigating the challenging transition from versions 4.6 to 4.7. His initial foray into the tech world was as a Computer Operator at a hospital system, and then a technical illustrator at C/S Group, where he honed his skills in Photoshop, Illustrator, AutoCAD, Macromedia products, and PHP. It was during his quest to develop a unified platform for the company's various locations that Jerad discovered Drupal, a discovery that profoundly shaped his career path. His proficiency and passion for Drupal led him to a transformative Lullabot workshop in 2006. Shortly thereafter, [Sony Music](https://www.sonymusic.com/) recognized his talent, bringing him on board to develop their artist platform. At Sony, Jerad played a pivotal role in creating a centralized system for launching artist websites and contributed significantly to the development of Sony's video portal, MyPlay. After a fruitful tenure at Sony, Jerad expanded his horizons at [LifeTime Digital](https://www.mylifetime.com/), and eventually channeled his expertise into founding Rapid Waters Development with [David Burns](https://www.lullabot.com/about/david-burns), a venture that marked a significant milestone in his career. 2010 marked a full-circle moment in Jerad's career when Lullabot acquired Rapid Waters Development. Joining Lullabot as a Senior Developer, he rapidly ascended to the roles of Senior Technical Project Manager and Development Manager. In these capacities, Jerad has been instrumental in recruiting and nurturing developers, leading pivotal client projects, and steering Lullabot towards the cutting edge of technology, particularly in the realms of Node.js, modern JavaScript frameworks, Artificial Intelligence, and Machine Learning. ## Did you know? Jerad is a prolific XR artist, nature photographer, and father of four bright and creative children. ## More about Jerad ### Projects - Turner Broadcasting - [NYU School of Medicine](https://www.lullabot.com/our-work/nyu-school-of-medicine) - [IBM](https://www.lullabot.com/our-work/ibm-cloud) - NBC Universal - [National Association of Music Merchants Inc (NAMM)](https://www.lullabot.com/our-work/namm-foundation) - The Recording Academy - [Google](https://www.lullabot.com/our-work/accelerated-mobile-pages) - [Syfy](https://www.lullabot.com/our-work/syfy) - Bravo - MSNBC - [Pantheon](https://www.lullabot.com/our-work/pantheon) - NewsBank - PAC-12 ## Featured resources ... [ More ... ](/resources) [### Building an AI-Powered Project Management System Read ](/articles/building-ai-powered-project-management-system) [### How to Supercharge AI Coding with Cursor Rules and Memory Banks Read ](/articles/supercharge-your-ai-coding-cursor-rules-and-memory-banks) [### Transforming eBooks: From PDFs to Accessible Web Experiences Read ](/articles/transforming-ebooks-pdfs-accessible-web-experiences) ## Featured work ... [ See all our work ](/our-work) - [ ### Pantheon An Amazing, Fast New Site for an Amazingly Fast Hosting Platform Pantheon ](/our-work/pantheon) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) - [ ### Google AMP-ing up Drupal Google ](/our-work/accelerated-mobile-pages) --- --- title: "David Burns" url: "/about/david-burns" type: bio date: 2015-06-23 updated: 2026-06-29 --- # David Burns # David Burns VP of Extended Support Springfield, PA - He - Him David's passion for business and technology has guided his professional marketing, development, and project management career. His eagerness to learn new technologies and solve complex problems in the simplest way possible has helped him succeed as a Drupal developer and senior technical project manager. David began working with Drupal in 2005 while employed at an e-commerce company in Malvern, PA. Not long after, he was hired by [Sony Music](https://www.sonymusic.com/) and moved to New York City. While there, he had the opportunity to work with Lullabot to build the [MyPlay](http://myplay.com/) music portal and a Drupal multi-site installation that powered hundreds of Sony artists' websites. From there, Drupal took him to San Francisco, where he worked with [Lifetime Television](https://www.mylifetime.com/), but his heart was still on the East Coast. He moved back to Pennsylvania and started his own development company, Rapid Waters Development, which was acquired by Lullabot in 2010. David was a part of the initial group of developers hired to build Lullabot's development services, and he established the support and maintenance department. When David is not writing and organizing Jira tickets or hacking away at code, you will find him spending time with his wife, Della, and their dog, Jackie. He is also completing a bachelor's degree online at the University of Maryland University College with a major in Digital Media and Web Technology and a minor in Computer Science. David enjoys watching movies, listening to live music, hanging out by the fire pit, and cooking on the grill. ## Did you know? David has amassed over 2200 video games on Steam which earned him the title of "Director of Acquisitions" within the Steam community. ## More about David ### Drupal Contributions: - [Ad Space](https://www.drupal.org/project/ad_space) - [Brütal Simplicity](https://www.drupal.org/sandbox/deviantintegral/1766038) - [Form Beautifier](https://www.drupal.org/project/form_beautifier) - [Hackpad](https://www.drupal.org/project/hackpad) - [Media Entity Image EXIF](https://www.drupal.org/project/media_entity_image_exif) - [Panels Accordion](https://www.drupal.org/project/panels_accordion) - [Publishing](https://www.drupal.org/project/publishing) - [slide\_menu](https://www.drupal.org/project/slide_menus) - [... and many more.](https://www.drupal.org/u/davidburns) ### Projects worked on at Lullabot: - [MSNBC](https://www.lullabot.com/our-work/msnbc) - [NAMM Foundation](https://www.lullabot.com/our-work/namm-foundation) - Carnegie Mellon University - Bravo - Grammys - NBC's Last Comic Standing - [Edutopia](https://www.lullabot.com/our-work/building-a-new-edutopia-on-decoupled-drupal-8) - [Georgia Public Broadcasting](https://www.lullabot.com/our-work/georgia-public-broadcasting) - [The State of Georgia](https://www.lullabot.com/our-work/govhub-building-georgias-digital-future) - [Martha Stewart Living](https://www.lullabot.com/our-work/martha-stewart-living) - [DocuSign](https://www.lullabot.com/our-work/docusign) - [IBM Investor Relations](https://www.lullabot.com/our-work/ibm-investor-relations) - [Lehigh University](https://www.lullabot.com/our-work/lehigh-university) - Cisco - Evergreen State College - PAC-12 - Qualcomm - WWE - Safari Books Online - Union College ### Education and Certifications: - - Certified ScrumMaster - Google IT Support - IT Security - Operating Systems - Computer Networking - Technical Support Fundamentals - System Administration and IT Infrastructure Services - ## Featured resources ... [ More ... ](/resources) [### How Automated Code Review Tools Reduce Pull Request Bottlenecks Read ](/articles/how-automated-code-review-tools-reduce-pull-request-bottlenecks) [### How Automation Transformed Government Site Maintenance Read ](/articles/how-automation-transformed-government-site-maintenance) [### Automate Maintenance, Humanize Support Read ](/articles/automate-maintenance-humanize-support) ## Featured work ... [ See all our work ](/our-work) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) --- --- title: "Jared Ponchot" url: "/about/jared-ponchot" type: bio date: 2015-06-23 updated: 2026-03-13 --- # Jared Ponchot # Jared Ponchot CCO Atlanta, GA Jared is Lullabot’s Chief Creative Officer, leading the agency’s design practice and helping shape how strategy, design, and development come together to deliver effective digital platforms. He works closely with clients and project teams to translate complex organizational needs into clear, scalable user experiences. Jared has led design and UX efforts for organizations including the State of Georgia, the State of Maryland, the State of Massachusetts, NBCUniversal, Manhattan University, South Dakota State University, and IBM. He was also an early advocate for responsive design and helped create the first fully responsive version of GRAMMY.com for the 54th Annual GRAMMY Awards. Jared has spoken at design and development conferences, including SXSW Interactive, An Event Apart, HOW Design Interactive, Breaking Development, and multiple DrupalCons. He lives in Roswell, Georgia, with his wife and children. Outside of work, Jared enjoys gardening, building things, cooking, exploring craft beer, and spending time with family and friends. ## More about Jared ### Drupal Contributions: - [Olivero](https://www.drupal.org/project/olivero) - [Florida DrupalCamp](https://www.drupal.org/project/fldc), credited on 1 issue - [Drupal Core](https://www.drupal.org/project/drupal), credited on 2 issues - [DrupalCon Global 2020](https://www.drupal.org/project/drupalcon_global), credited on 1 issue - [Florida Drupal Camp 2020](https://www.drupal.org/project/fldc2020), credited on 1 issue - [Drupal Camp Asheville](https://www.drupal.org/project/dcavl), credited on 1 issue ### Projects Worked on at Lullabot: - [AIChE](https://www.lullabot.com/our-work/aiche) - [Ames Laboratory](https://www.lullabot.com/our-work/ames-laboratory) - Angie's List - Bravo TV - [Carnegie Mellon University](https://www.lullabot.com/our-work/carnegie-mellon-university) - Dartmouth Health - [DocuSign](https://www.lullabot.com/our-work/docusign) - Drupalize.me - Ellucian - [Georgia.gov](https://www.lullabot.com/our-work/govhub-building-georgias-digital-future) - GoCO2Free - [Georgia Public Broadcasting](https://www.lullabot.com/our-work/georgia-public-broadcasting) - GRAMMY - Harvard Library - Harvard Extension School - [IBM Investor Relations](https://www.lullabot.com/our-work/ibm-investor-relations) - Intel - Longboard - MIT - [MSNBC](https://www.lullabot.com/our-work/msnbc) - [The NAMM Foundation](https://www.lullabot.com/our-work/namm-foundation) - NBC Media Village - NBC Plus - NewsBank - [Pantheon](https://www.lullabot.com/our-work/pantheon) - Qualcomm - Sony Pictures - [Spredfast](https://www.lullabot.com/our-work/spredfast) - This Old House - Time Warner - Tugboat - Union College ### Education & Certifications: - Bachelor of Fine Arts in Graphic & Interactive Communications, Ringling College of Art & Design ## Featured resources ... [ More ... ](/resources) [### What Is a Design System? (And How to Know When You Need One) Read ](/articles/what-design-system-and-how-know-when-you-need-one) [### Presentation Modeling: A Prioritized, User-centered Approach to Design Requirements Gathering Read ](/articles/presentation-modeling-prioritized-user-centered-approach-design-requirements-gathering) [### Designing for Chaos: Finding Order for Olivero Read ](/articles/designing-chaos-finding-order-olivero) ## Featured work ... [ See all our work ](/our-work) - [ ### AIChE A Sustainable Design System for a Global Professional Association AIChE The Global Home of Chemical Engineers, logo ](/our-work/aiche) - [ ### Pantheon An Amazing, Fast New Site for an Amazingly Fast Hosting Platform Pantheon ](/our-work/pantheon) - [ ### Spredfast Showcasing ‘Smart Social’ for Technology's Biggest Event ](/our-work/spredfast) --- --- title: "James Sansbury" url: "/about/james-sansbury" type: bio date: 2015-06-23 updated: 2025-06-05 --- # James Sansbury # James Sansbury Director of Product, Tugboat Atlanta, Georgia - He - Him Like many in the web development world, James came to programming through the side door. After graduating with a degree in music performance, he decided in 2004 to start a record label and began searching for tools to help him build websites for the bands he was working with. A recommendation from a fellow nerd landed him on the [Drupal homepage](https://www.drupal.org). Drupal 4.5 was the current release at the time, and while he got a lot for free from the community, he soon found that he had quite a bit to learn in order to be able to build the simple yet unique websites his musicians wanted. James took to web development quickly. As his skills increased and interest grew, he began learning other technologies, and built one of the first [decoupled Drupal](https://www.lullabot.com/podcasts/drupalizeme-podcast/decoupling-drupal) websites using a Drupal 5 backend that fed XML to a Flash frontend website. It wasn't long before James realized that he was receiving more compliments on the websites he was building than for the music, and so he hung up his record label hat and pursued a full time career in web development. After working as the Drupal developer for Sprocket, a web design agency based in metro Atlanta, he was hired as a part-time Drupal trainer for Lullabot, then as a developer, senior developer, and then architect—all for some of the world's biggest brands. James has worked on large data migrations for [The Recording Academy](https://www.grammy.org), engaging editorial experiences for [MSNBC](https://www.ms.now/), and helped create a fast and stable development and deployment process for [Cisco's Support Forum](https://community.cisco.com/). He architected a decoupled user experience for [Martha Stewart](https://www.marthastewart.com/), and a continuous integration and deployment process for [Intel](https://www.intel.com/content/www/us/en/homepage.html), which later became [Tugboat](https://www.tugboatqa.com/). In his role as Director of Product for [Tugboat](https://www.tugboatqa.com/), James leverages his years of experience as a technical architect for Lullabot to support the biggest brands on the web with their digital platforms. ## Featured resources ... [ More ... ](/resources) [### Drupal 8 Composer Best Practices Read ](/articles/drupal-8-composer-best-practices) [### Three Things Every Drupal Site Needs to Kick Ass Read ](/articles/three-things-every-drupal-site-needs-to-kick-ass) [### Keeping Drupal's Files Safe Read ](/articles/keeping-drupals-files-safe) ## Featured work ... [ See all our work ](/our-work) - [ ### Syfy Creating an Out of This World Experience for Science Fiction Fans SYFY ](/our-work/syfy) - [ ### Martha Stewart Living Creating the Recipe for a Central Content Platform ](/our-work/martha-stewart-living) --- --- title: "Esther Lee" url: "/about/esther-lee" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Esther Lee # Esther Lee Former Senior HR Generalist Las Vegas, NV Esther started working with Lullabot in 2008 as a contractor and has always loved the Lullabot “way”. She also loves helping people-- this love, plus a swift drop in the deep-end of HR, eventually led her to the position of Senior HR Generalist at Lullabot. Esther was born and raised in Iowa, amidst cyclones and cornstalks. She traded in the cow pies for mountains and moved to Utah to attend Utah Valley State College in 2001. It was there that she quickly worked her way up the ladder at Cirque Lodge Drug Rehab to become Director of Human Resources & Event Coordination. She oversaw the HR department, kept up with over 65 employees, helped to plan the annual reunion and had many other duties. Since joining Lullabot as an employee in 2010, Esther continues to enjoy the challenges of managing HR at a wholly distributed company with employees spread across North America and Europe. Esther loves to read, write and ride horses. She holds an Associate of Arts degree from the College of Southern Nevada and is currently working towards her Bachelor's in HR Management. She is a Certified Professional with the Society of Human Resource Management ([SHRM-CP](https://bcert.me/bc/html/show-badge.html?b=tekfvas)). She enjoys the challenge of balancing home, school and work. Esther also volunteers as the 1st Vice President on the local elementary school PTA. She lives in a rural community outside of Las Vegas where she enjoys the desert heat with her husband and three children. ## Did you know? Esther recently returned to school to further her education in HR Management, and is both enjoying and lamenting having homework. ## Featured resources ... [ More ... ](/resources) [### How Lullabot Fosters Human Connection for New Hires in a Distributed Company Read ](/articles/how-lullabot-fosters-human-connection-for-new-hires-in-a-distributed-company) [### Fear Not: Emotional Fear in the Workplace Read ](/articles/fear-not-emotional-fear-in-the-workplace) [### Hiring Secrets Of A Distributed Company Read ](/articles/hiring-secrets-of-a-distributed-company) --- --- title: "Blake Hall" url: "/about/blake-hall" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Blake Hall # Blake Hall Senior Developer & Trainer Green Bay, WI Blake Hall began working with Drupal in 2006 before joining Lullabot in 2010. In his first weeks learning Drupal, Lullabot's initial podcast episodes proved to be invaluable resources. Lullabot made an impression early on, and he knew he wanted to eventually be part of their team. Over the years Blake has built Drupal sites for event registration, sports league management, political watchdog organizations, helped run one of the larger MediaWiki installations on the web, the World Kickball Association site, and a social media startup. Blake also led the organization of the first several Drupal Camps in Wisconsin [starting in 2008](http://barcamp.org/w/page/402640/DrupalCampWI). More recently Blake has been working with node.js to help build an [agile QA tool for Lullabot](http://tugboat.qa). His experiences at [JS.conf](http://2014.jsconf.us/) and [NodeConf](http://nodeconf.com/) have him planning Wisconsin's first [NodeSchool](https://nodeschool.io/) event. From Blake's [Bunyan-esque](https://en.wikipedia.org/wiki/Paul_Bunyan) beard, his years as a cheese factory worker, his Packers season tickets or his log cabin in the woods Blake comfortably embraces several Wisconsin stereotypes. ## Featured resources ... [ More Resources ](/resources) [### CSS Regression Testing with Resemble.js Read ](/articles/css-regression-testing-with-resemblejs) [### Introducing Videola Read ](/articles/introducing-videola) --- --- title: "Andrew Berry" url: "/about/andrew-berry" type: bio date: 2015-06-23 updated: 2025-04-28 --- # Andrew Berry # Andrew Berry VP of Technology Guelph, Ontario, Canada - He - Him Andrew is a technical architect and developer who works at the intersection of business and technology. His journey with Drupal and PHP started with a student website that needed to change a sentence in the site footer. Two months and a migration later, Andrew was thoroughly convinced of the value of Drupal and its community. He has made many contributions as [deviantintegral](http://drupal.org/user/71291) on drupal.org to many modules and Drupal core, as well as helping to organize the Waterloo Region Drupal Users Group. Since joining Lullabot, he's helped architect and build products for clients, including the [State of Massachusetts](https://www.lullabot.com/our-work/massgov-das), [DocuSign](https://www.lullabot.com/our-work/docusign), and the American Booksellers Association. Andrew enjoys going beyond Drupal and PHP, supporting Lullabot’s team in DevOps, security, and project management. Most recently, he’s been exploring home automation, giving him opportunities to deepen his expertise in Python and TypeScript. He also led the discovery and architecture for the American Bookseller's Association Drupal 9 e-commerce platform. Andrew has a Master's degree in Computer Science from the University of Guelph. While researching there, he specialized in Human-Computer Interaction and online security. His research interests include perceptions of privacy online and visualization of security and privacy threats. Andrew lives in Guelph with his wife Julia, two daughters, and their cats. ## Did you know? Andrew can be nerdsniped with any problem that needs a debugger to solve. ## More about Andrew ### Drupal Contributions - [Amazon Simple Notification Service](https://www.drupal.org/project/amazon_sns) - [App Link](https://www.drupal.org/project/app_link) - [Brightcove Player](https://www.drupal.org/project/brightcove_player) - [Client Error Trace](https://www.drupal.org/project/client_error_trace) - [Dropbox Integration](https://www.drupal.org/project/dropbox) - [Excommunicate](https://www.drupal.org/project/excommunicate) - [File Formatter Extras](https://www.drupal.org/sandbox/deviantintegral/1966076) - [jPlayer](https://www.drupal.org/project/jplayer) - [... and many more. ](https://www.drupal.org/u/deviantintegral) ### Projects - NBC Digital - American Booksellers Association - Turner Broadcasting - [State of Massachusetts](https://www.lullabot.com/our-work/massgov-das) - [DocuSign](https://www.lullabot.com/our-work/docusign) - Grammys - State Farm - The Tonight Show - Saturday Night Live - TheaterMania - [Lehigh University](https://www.lullabot.com/our-work/lehigh-university) - Bravo - [Universal Kids](https://www.lullabot.com/our-work/universal-kids) - NewsBank ### Education & Certifications - Master of Science in Human Computer Interaction, University of Guelph - Bachelor of Computing, University of Guelph ## Featured resources ... [ More ... ](/resources) [### You Can’t Govern What You Can’t See: Visibility Challenges with State Government Websites Read ](/articles/you-cant-govern-what-you-cant-see-visibility-challenges-state-government-websites) [### Beyond Free: Choosing the Right Search Solution for Your Website Read ](/articles/beyond-free-choosing-right-search-solution-your-website) [### How to Calculate Git Repository Growth Over Time Read ](/articles/how-calculate-git-repository-growth-over-time) ## Featured work ... [ See all our work ](/our-work) - [ ### American Booksellers Association Creating the Next Platform as a Service for Hundreds of Independent Bookstores American Booksellers Association ](/our-work/american-bookseller-association) - [ ### Universal Kids Simplifying the Architecture for a Popular Children’s Network Universal Kids ](/our-work/universal-kids) - [ ### Google AMP-ing up Drupal Google ](/our-work/accelerated-mobile-pages) --- --- title: "Matt Kleve" url: "/about/matt-kleve" type: bio date: 2015-06-23 updated: 2025-10-28 --- # Matt Kleve # Matt Kleve Lead Engineer Holyoke, CO Matt is a lead engineer at Lullabot who enjoys working with Drupal performance and scalability. Before Lullabot, Matt worked for the Alumni Association of the Air Force Academy, managing the web/IT team. It was there that he started using Drupal in 2007. He quickly became the resident Drupal expert, leveraging Drupal to integrate various parts of their data/technology. Since then, he has helped the states of Iowa and Georgia roll out modernized Drupal platforms. His work for the American Booksellers Association powers e-commerce websites and event management for independent bookstores around the country. He even helped NBC win an Emmy award (Outstanding Interactive Program - 2014 The Tonight Show Starring Jimmy Fallon Digital Experience) after he developed critical functionality for a decoupled Drupal application. Matt lives in Holyoke, CO, where he helps run the family farm with his wife, Charlee, and their two boys. His other marketable skills include driving a tractor, fixing a fence, raising pigs, and welding! ## Featured resources ... [ More ... ](/resources) [### The Shadow DOM {Frontend.Darkside} Listen ](/podcasts/lullabot-podcast/shadow-dom) [### Directing How Single Directory Components Directly Simplify Theming Listen ](/podcasts/lullabot-podcast/sdc) [### Just Say Drupal‽ Listen ](/podcasts/lullabot-podcast/just-say-drupal) ## Featured work ... [ See all our work ](/our-work) - [ ### American Booksellers Association Creating the Next Platform as a Service for Hundreds of Independent Bookstores American Booksellers Association ](/our-work/american-bookseller-association) --- --- title: "Sally Young" url: "/about/sally-young" type: bio date: 2015-06-23 updated: 2025-03-10 --- # Sally Young # Sally Young Senior DevOps Engineer London, United Kingdom - She - Her As well as being an experienced back-end developer, Sally also spends quite a lot of time building APIs, writing JavaScript and working on front-end tools. She's a core JavaScript maintainer for Drupal, as well as leading the [Admin UI & JavaScript Modernization Initiative](https://www.drupal.org/about/strategic-initiatives/admin-ui-js). She also currently serves as front-end track chair and decoupled summit lead for [DrupalCon](https://events.drupal.org/nashville2018/team), and helped bring [Contenta CMS](https://www.contentacms.org/) into the world. A long-time proponent of open source, her first experiences were hacking around with bulletin boards in Perl and PHP, including contributing the original smiley pack for phpBB 1.0. Sally earned her Masters degree in Computer Science from the University of London, where she applied genetic algorithms to the lexical categorisation of words for her dissertation, and thus learned many valuable lessons in processing high volumes of data and begging universities for CPU time. During her time there she got to work with the BAFTA winning [SodaRace](https://royalsociety.org/Sodarace-humans-vs-machine-intelligence/) project and also worked on a number of courses, helping students with their programming in Java, Assembly and Prolog. Between her Bachelors and Masters year she won a grant sponsored by the Nuffield Foundation to develop, test and document verification systems in the areas of category theory and the verification of properties in signal flow graphs. Sally went on to work in the publishing, film and music industries, as well as Her Majesty’s Government, building sites in Zend Framework and Drupal, as well as running the local [Drupal learning meetups in London](https://www.meetup.com/Learning-Drupal-Meetup), before joining Lullabot in 2011. ## Did you know? Sally is also a long-time lindy hopper and won the 2010 and 2011 London Jitterbug Championship! ## Featured resources ... [ More ... ](/resources) [### Nightwatch in Drupal Core Read ](/articles/nightwatch-in-drupal-core) [### Drupal JavaScript Initiative: The Road to a Modern Administration UI Read ](/articles/drupal-javascript-initiative-the-road-to-a-modern-administration-ui) [### Dynamically Inlining Critical CSS with Server-side JavaScript Read ](/articles/dynamically-inlining-critical-css-with-serverside-javascript) ## Featured work ... [ See all our work ](/our-work) - [ ### American Booksellers Association Creating the Next Platform as a Service for Hundreds of Independent Bookstores American Booksellers Association ](/our-work/american-bookseller-association) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) --- --- title: "Kyle Hofmeyer" url: "/about/kyle-hofmeyer" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Kyle Hofmeyer # Kyle Hofmeyer Former Video Engineer & Trainer Raleigh, NC Kyle Hofmeyer is an accredited film maker and video producer with Drupalize.Me. With over a decade of experience, he has worked on educational videos, animated shorts, and documentary films. Kyle's work extends beyond film making and he has been the on-screen talent for Drupalize.Me videos relating to Drupal site building, and Web development, and has assisted in teaching Drupal workshops on two continents. Kyle is valued by his colleagues for his attention to detail in video production. From the setup of recording, all the way through to the final encoding of video files, Kyle is meticulous and persistent in his demand for quality work. He is constantly striving to find more efficient ways to produce premium videos for his audience. Kyle's work has helped individuals from NASA, NPR, Sony, and NBC Universal learn Web development. In his spare time, Kyle loves to play with the latest tech gadgets. He is an early adopter of technology, but also an empathetic teacher who genuinely cares about the experience others have with the tools they use. A die-hard fan of the early Apple days, Kyle will enthusiastically engage in a this vs. that debate on the latest tools you are using. Kyle lives in Pennsylvania with his wife, two children, and a dog named Leia. He regrets the winter of 2014 and misses SCUBA diving off the coast of Florida. If the snow ever melts, you'll find him outside on the weekend tinkering on his CJ8 Scrambler, or heading down a hill on his mountain bike. ## Did you know? The Lullabot team celebrates "Kyle Hofmeyer Day" once a year. ## Featured resources ... [ More ... ](/resources) [### The New Lullabot.com Listen ](/podcasts/drupalizeme-podcast/the-all-new-lullabotcom) [### Demystifying the Sprint Listen ](/podcasts/drupalizeme-podcast/demystifying-the-sprint) [### Getting Excited for Drupal 8 - The Donkey, Human, Ant, Chameleon CMS Listen ](/podcasts/drupalizeme-podcast/getting-excited-for-drupal-8-the-donkey-human-ant-chameleon-cms) --- --- title: "Ben Chavet" url: "/about/ben-chavet" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Ben Chavet # Ben Chavet Former Senior Systems Architect Grand Prairie, TX *Ben sadly passed away from Covid pneumonia. He was a beloved colleague and friend and his work greatly contributed to the success that Lullabot and [Tugboat](#) enjoy today. He will greatly be missed and was appreciated more than words can express.* Ben was Lullabot and Tugboat's secret weapon – an expert systems administrator, server and hosting infrastructure architect, web and database optimizations guru, and all-around friendly and knowledgeable guy. He worked on Lullabot products and client projects with everything from simple server setup and configuration to architecting high-performance high-availability server clusters and content delivery networks for some of the most popular websites on the Internet. Previous to joining Lullabot, Ben worked as a senior system administrator and resident Drupal-hosting expert at NeoSpire. It was there that he caught the attention of the Lullabot team as he helped the Bots launch several high-profile client sites. Early in 2012, Ben decided to make a career change and Lullabot snatched him up. Ben ensured that all of Lullabot's websites hummed along happily day and night. He could even get them to sing in harmony if you listened closely enough! Ben earned a Bachelor of Science degree in Computer Science from The University of Nebraska – Lincoln and lived in Grand Prairie, Texas with his wife and two children. When he wasn't managing servers and infrastructure devices, Ben enjoyed DIY home improvement projects, playing drums, and video games. He also maintained a small tech blog at . To learn more about Ben's work on Tugboat, read [Removing Technical Barriers to Collaboration](https://www.linode.com/resources/ben-chavet/). ## Featured resources ... [ More ... ](/resources) [### Convincing Docker and Iptables to Play Nicely Read ](/articles/convincing-docker-and-iptables-play-nicely) [### Installing Solr for use with Drupal Read ](/articles/installing-solr-for-use-with-drupal) [### MySQL Backups Using LVM Snapshots Read ](/articles/mysql-backups-using-lvm-snapshots) --- --- title: "Tim McDorman" url: "/about/tim-mcdorman" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Tim McDorman # Tim McDorman Administrative Manager Ames, IA Tim started with Lullabot in 2007 as a part-time contractor, doing Drupal development and various technical odds and ends. Over the years he has played many roles at the company, but these days he does a lot of number tracking for Lullabot. As bookkeeper, Tim keeps track of Lullabot's ins and outs, and he makes sure the batteries stay charged for our giant robot. As resource manager, Tim keeps track of who's working on what and makes sure that we've the right people available at the right time to keep our clients content and happy. As part of the human resources team, Tim also interfaces with our benefits companies to make sure that our health insurance, retirement plans, and other benefits are there when we need them. When Tim isn't working you'll probably find him looking for a new hiking trail, enjoying a laugh with his wife or lost in contemplation. ## Did you know? Tim studied Mandarin in Taipei for a year. --- --- title: "Joe Shindelar" url: "/about/joe-shindelar" type: bio date: 2015-06-23 updated: 2026-06-29 --- # Joe Shindelar # Joe Shindelar Senior Developer Minneapolis, MN - He - Him Joe has been building Drupal sites and contributing to the Drupal project since 2006. His work sits at the intersection of development, education, documentation, and open source community leadership. He first joined Lullabot in 2010, helping deliver on-site Drupal training for clients. Those workshops became part of the foundation for Drupalize.Me, Lullabot’s online Drupal training platform. When Drupalize.Me spun off into its own company in 2016, Joe continued leading the effort to create and maintain practical learning materials for the Drupal community. Now back at Lullabot, he brings that same educator’s mindset to client work, documentation, and community contribution. As a DrupalCon keynote speaker and frequent presenter at conferences and community events, Joe has spoken on topics ranging from development and documentation to community involvement. He has contributed to Drupal core, serves as a member of the Drupal Documentation Working Group, maintains the Drupal User Guide, and helped author the Drupal CMS Guide. Drawn to Drupal by the community’s willingness to teach, document, and improve things together, Joe believes deeply in the power of shared knowledge. He enjoys making complex ideas easier to understand and helping others reach the moment when something finally clicks. Outside of Drupal, Joe teaches snowboarding as a professionally certified instructor and enjoys spending as much time as possible outdoors with his family. ## Featured resources ... [ More Resources ](/resources) [### Web Services Listen ](/podcasts/drupalizeme-podcast/web-services) [### Your Javascript should expose APIs, too! Read ](/articles/your-javascript-should-expose-apis-too) --- --- title: "Chelsea Barwald" url: "/about/chelsea-barwald" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Chelsea Barwald # Chelsea Barwald Former Executive Assistant Rhode Island Chelsea Barwald was the Office Manager and Executive Assistant at the Lullabot Activity Center in Providence, RI. If you attended any of our Providence events or workshops, you probably met Chelsea. She was born and raised in Mesa, AZ, but moved to Providence where she was on the dean's list and graduated from Johnson & Wales University with a degree in Business Management. Prior to joining Lullabot, she was a coffee master and assistant manager at Starbucks. Chelsea lives in Warren, RI and enjoys cooking for friends, running, and playing frisbee with her high-jumping dog, Peso. When you meet her, be sure to ask about her hot sauce collection. --- --- title: "Sidharth Kshatriya" url: "/about/sidharth-kshatriya" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Sidharth Kshatriya # Sidharth Kshatriya Former Senior Developer New Delhi, India Sidharth Kshatriya started learning Drupal for a personal project in 2008. He quickly found Drupal insanely addictive, productive and fun. The combination of Views, CCK and Panels was so powerful that he couldn't understand why anybody would choose to make websites any other way. Over the years, Sidharth has been involved in almost all facets of website development. Sidharth has been a project manager / architect for websites like [Open Magazine](http://drupal.org/node/629664), an independent Drupal trainer and has most recently worked as a senior developer at Drupalize.me Sidharth is deeply interested in open source software, science and economics. He has tried to learn something formally in all his areas of interest: He holds a Bachelors in Computer Science from Cornell University and a Masters in Physics from Indian Institute of Technology, Madras. He also holds an MBA degree. Sidharth lives with his wife in New Delhi, India. When not pondering Drupal's API calls or teaching himself cool open source technologies, Sidharth likes to engage in slightly more down to earth interests like reality television and exploring world cuisine. --- --- title: "Brian Skowron" url: "/about/brian-skowron" type: bio date: 2015-06-23 updated: 2026-03-27 --- # Brian Skowron # Brian Skowron President Dallas, TX Brian Skowron found his way to the web through the route of traditional media. He’s always had a passion for media theory and communications, and that passion manifested itself in roles with Fox and Univision Radio. But Brian truly earned his account chops in the technology world, selling managed hosting services for complex web applications at NeoSpire and Logicworks. Even within this nuts and bolts industry, Brian consistently found himself drawn to high-profile web projects and the unique challenges they provided. It was on these projects where Brian was introduced to Drupal and witnessed its capabilities on a large scale. At Lullabot, Brian prefers to listen and help solve problems instead of tooting Lullabot’s horn. (Well...he might toot Lullabot’s horn a little bit.) Brian lives in Dallas with his wife Adrienne, with whom he shares a love of travel, karaoke, and good food. He's a huge fan of baseball and hopes to share the game with his son, Dash. ## Did you know? Brian's hidden talent is creating Bob Ross-style landscapes on Etch-a-Sketch. ## Featured resources ... [ More Resources ](/resources) [### Adopting a Unified Web Platform Requires a New Platform Culture Read ](/articles/unified-web-platform-requires-new-platform-culture) [### Lullabot’s Hierarchy of Qualification Read ](/articles/lullabots-hierarchy-of-qualification) --- --- title: "Sean Lange" url: "/about/sean-lange" type: bio date: 2015-06-23 updated: 2024-02-20 --- # Sean Lange # Sean Lange Director of Talent Development Escondido, CA Initially hired in 2010 as a Front-end Developer, Sean has had the opportunity to work on some exciting projects at Lullabot. The Grammys (The Recording Academy), WWE, and MSNBC to name a few; Sean has fond memories of putting to use a combination of Drupal Site Building, HTML, CSS and some Javascript skills. After a time, Lullabot grew and project managers were needed. Sean’s past experience as a Recreation Supervisor (with over 20 years experience in the field of Parks and Recreation!) running special events, after-school programs, and overseeing community grant-funded programs made him well equipped for the role of planning and leadership. Sean transitioned to Project Manager and has worked with clients including the American Booksellers Association, Oxygen, Cisco, NBC, NewsBank, and the National Associations of Music Merchants (NAMM). These projects included organizing and planning all kinds of projects ranging from re-designs to migrations, and completely new creations. Currently, Sean serves as Director of Talent Development. While the days of spending hour upon hour with code editors and debugging are behind him, Sean finds it personally and professionally rewarding to support individual project teams, lead Lullabot’s Front-end Developers (FrontBots), and assist in furthering the culture, impact, and awesomeness of Lullabot. Sean counts the opportunity to support his teammates' growth and learning as one of the most rewarding parts of his job. It’s no wonder he earned a BS Degree in Recreation and Community Services-- Sean loves to serve others. Sean lives in San Marcos, CA where he enjoys running, getting frozen yogurt with his wife and teenage daughters, and often plays host to traveling Lullabots. ## Did you know? Sean’s weekends are never boring. You can find him either playing guitar for weekend services or getting little-to-no sleep developing video games with Unity3D for random Game Jams. ## More about Sean ### Drupal Contributions: - Contributed a Drupal theme, was active in porting Views to Drupal 8 ### Projects Worked on at Lullabot: - [MSNBC](https://www.lullabot.com/our-work/msnbc) - [The NAMM Foundation](https://www.lullabot.com/our-work/namm-foundation) - [Edutopia](https://www.lullabot.com/our-work/building-a-new-edutopia-on-decoupled-drupal-8) - Turner Broadcasting - [Ames Laboratory](https://www.lullabot.com/our-work/ames-laboratory) - NewsBank - Oxygen - Cisco - WWE - American Booksellers Association - Penguin Random House ### Education and Certifications: - Certified ScrumMaster ## Featured resources ... [ More ... ](/resources) [### The Accidental Project Manager: Starting a New Project Read ](/articles/being-a-digital-project-manager-starting-a-new-project) [### Making The Most Of Mobilegeddon Read ](/articles/making-the-most-of-mobilegeddon) [### Using Remote Image Files When You Develop Locally Read ](/articles/using-remote-image-files-when-you-develop-locally) ## Featured work ... [ See all our work ](/our-work) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) --- --- title: "Angus Mak" url: "/about/angus-mak" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Angus Mak # Angus Mak Lead Engineer Ottawa, Ontario, Canada Angus Mak is a man of diverse talents. Case in point: He earned his Honours Bachelor of Science degree from the University of Toronto in 2008, with a Specialist in Software Engineer and a Major in Economics. Angus started his career in web development at a Software firm in Toronto, which was focused on front-end technologies and Content Management Systems including Drupal and WordPress. Angus refers to himself as “technologically agnostic”, staying open minded about trying different technologies. Since joining Lullabot in 2012, Angus has put on multiple hats working on numerous projects, oftentimes as both a front-end developer and back-end Drupal developer as needed. Angus describes the "skill of learning" as his greatest asset--often saying, "once you have acquired the skill to learn, you will never be limited by your skill set". Eventually Angus ventured outside of the web development world into the world of connected devices. As a Lullabot, his efforts working on Roku Development contributed to the success of NBC's Roku project. Change is constant for Angus, who has also acquired the skills to become a top-notch iOS / tvOS Developer. He has represented Lullabot as an integral part of NBC’s iOS and tvOS team. Angus currently lives in Ottawa, Ontario. Outside of the office, Angus has been a dog trainer for many years and participated in the sport of [Schutzhund](https://en.wikipedia.org/wiki/Schutzhund). He is also an avid fisherman who enjoys the many fishing opportunities Ottawa has to offer, including [ice fishing](https://en.wikipedia.org/wiki/Ice_fishing) in the beautiful Canadian winter. ## Featured resources ... [ More ... ](/resources) [### Form API #states Read ](/articles/form-api-states) [### Debugging Drush commands with Xdebug and PHPStorm Read ](/articles/debugging-drush-commands-with-xdebug-and-phpstorm) [### PhpStorm for Drupal Read ](/articles/phpstorm-for-drupal) --- --- title: "Brock Boland" url: "/about/brock-boland" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Brock Boland # Brock Boland Former Senior Developer Chicago IL Brock Boland discovered Drupal in 2009 after years of generic PHP site development. As the saying goes, Brock came for the code but stayed for the community – the welcoming and friendly community of Drupal developers and site builders hooked him in. His first Lullabot training event really sealed the deal though. That's when he knew for sure that the Drupal world was where he belonged. Brock lived in DC for a number of years, and became very involved in the Drupal community there by helping to organize events like meetups, CapitalCamp, and Drupal Ladder sprints. These days, he's spending more of his free time learning to code for iOS and planning mobile apps to pair with Drupal sites. Prior to joining Lullabot, Brock worked for [Jackson River](https://www.jacksonriver.com/) and built sites for non-profits like [EMILY's List](https://emilyslist.org/), [Amnesty International](https://www.amnestyusa.org/), [Drug Policy Alliance](https://drugpolicy.org/), and [International Rescue Committee](https://www.rescue.org/). Brock recently moved to Chicago after a brief stint in Denver while his wife Erin trained to become a Ruby on Rails developer. Now that they are both web developers, their conversation tends to makes even less sense to the casual eavesdropper. When not coding or hanging out with their dog Lola May, Brock and Erin like to explore their new city. ## Featured resources ... [ More Resources ](/resources) [### When Regular Expressions Go Too Far Read ](/articles/when-regular-expressions-go-too-far) [### Development Team Best Practices Read ](/articles/development-team-best-practices) [### White House's Open Source Plans Previewed at Drupal Meet-Up Read ](/articles/white-houses-open-source-plans-previewed-at-drupal-meetup) --- --- title: "Carwin Young" url: "/about/carwin-young" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Carwin Young # Carwin Young Former Senior Front-end Developer Nixa, MO Carwin Young joined the Lullabot team in 2012 after many long years of disciplined self-learning in the realm of web development and computery-things. As a young teenager, Carwin became deeply enthralled with the fast-paced lifestyle of internet forums, videogaming, and homestarrunner.com: a lifestyle which ultimately transitioned into a career of coding and development. Carwin has contributed to the development of several notable sites including BravoTV, MSNBC, and The GRAMMYs. In early 2015, he and co-author Joe Fender published the book 'Front End Fundamentals,' which is, as its subtitle states, a practical guide to front-end web development. Outside of work, Carwin continues to write code just because he likes it. His most recent infatuation is the Ruby language, although he has also been tinkering with Angular.js, Coffeescript, microcontrollers and whatever else strikes his fancy. Carwin likes generally nerdy stuff like Arch Linux, [Oxford commas, ](http://grammar.about.com/od/grammarfaq/f/QAoxfordcomma.htm) and Vim, but he also has a life outside of his computer screen, wherein he occupies space in southwestern Missouri and upholds the titles of husband, father, homeowner, and all-around outstanding American citizen. ## Did you know? Carwin has read the complete works of Hermann Hesse. ## Featured resources ... [ More Resources ](/resources) [### Creating a Simple Chrome Extension Read ](/articles/creating-a-simple-chrome-extension) --- --- title: "Josh Riggs" url: "/about/josh-riggs" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Josh Riggs # Josh Riggs Former Senior Interactive Designer Portland OR Josh Riggs is equal parts super-star designer, UX-pert, and front-end coder – and to round it all out, he's a nice guy too. Designing for Drupal since version 5 back in 2007, he's used to working very closely with developers and content creators. Josh brings a wide range of design experience to Lullabot, having worked extensively in-house and also at agencies on projects for companies including Coca Cola, Cartoon Network, The Orlando Magic, The Army National Guard, University of Florida, Speed TV, Lapostolle and Wente wineries, and AOL, just to name a few. Josh also led the redesign of FloridaHospital.com, a huge responsive Drupal multi-site installation. Josh lives in Portland, Oregon, with his two favorite girls: wife Angela, and dog Frank. He enjoys drinking his favorite local beers, cooking and hiking, or perhaps doing all three. ## Featured resources ... [ More Resources ](/resources) [### Front-End Design Conference Wrap-Up Read ](/articles/frontend-design-conference-wrapup) --- --- title: "Juampy NR" url: "/about/juampy-nr" type: bio date: 2015-06-23 updated: 2025-08-04 --- # Juampy NR # Juampy NR Lead Engineer Madrid, Spain Juampy is a Lead Engineer specializing in Cloud Engineering. After years as a Software Engineer for Drupal projects and migrations for Bravo, SyFy, and Harvard Alumni among others, he joined NBC News to work with other technologies like Node.js and Go, building complex infrastructures in AWS at the Content Services team. Juampy is a certified AWS Certified Solutions Architect - Professional. A prolific writer, Juampy loves [sharing his learning process at our blog](https://www.lullabot.com/resources?author=108). His solutions have helped some of the world's largest media organizations, such as NBC News, TNT, TCM, and CNBC. Beyond coding, Juampy loves optimizing development workflows and establishing best practices so code is easier to maintain and more secure. He implemented [Tugboat Live Previews](https://www.drupal.org/docs/develop/git/using-git-to-contribute-to-drupal/using-live-previews-on-drupal-core-and-contrib-merge-requests) for Drupal.org, which the whole Drupal community uses to speed up the development and testing of bug fixes and new features. He has also written two books: [Drush User's Guide](https://www.amazon.com/Drush-Users-Guide-ebook/dp/B007SVJ8MA/) and [Drush for Developers](https://www.amazon.com/Drush-Developers-Juampy-Novillo-Requena/dp/1784393789). Juampy lives in a small village near the mountains of Madrid with his wife and daughter. Whenever he has a chance, he goes for a game of squash. ## Did you know? Juampy has five chickens patrolling his house: Doris, Gertrudis, Virgilia, Margareth, and Catherine. ## More about Juampy ### Drupal Contributions: - Added Tugboat live previews to Drupal core and contributed projects. - Co-maintainer of Basic Auth in Drupal core - Co-maintainer of [Media Migration](https://www.drupal.org/project/media_migration) - Co-maintainer of [Diff](https://www.drupal.org/project/diff) - Co-maintainer of [Devel](https://www.drupal.org/project/devel) ### Projects Worked on at Lullabot: - Turner Broadcasting - Harvard Alumni - CNBC - NBC News - [MSNB](https://www.lullabot.com/our-work/msnbc)[C](https://www.lullabot.com/our-work/msnbc) - Pac-12 - Bravo - Oxygen - [Universal Kids](https://www.lullabot.com/our-work/universal-kids) - Today.com - Evergreen State College ### Education and Certifications: - Computer Engineering & Information Systems Degree, Pontificia Comillas University (Madrid) and University of Westminster (London) - Certified Kubernetes Security Specialist (CKS) - Certified Kubernetes Administrator (CKA) - Certified Kubernetes Application Developer (CKAD) - AWS Certified Cloud Practitioner ## Featured resources ... [ More ... ](/resources) [### Updating go1.x Runtime in AWS Lambdas Read ](/articles/updating-go1x-runtime-aws-lambdas) [### AWS Certified Solutions Architect - Associate Exam Tips Read ](/articles/aws-certified-solutions-architect-associate-exam-tips) [### AWS Certified Cloud Practitioner Exam Tips Read ](/articles/aws-certified-cloud-practitioner-exam-tips) ## Featured work ... [ See all our work ](/our-work) - [ ### Universal Kids Simplifying the Architecture for a Popular Children’s Network Universal Kids ](/our-work/universal-kids) --- --- title: "Darren Petersen" url: "/about/darren-petersen" type: bio date: 2015-06-23 updated: 2024-02-05 --- # Darren Petersen # Darren Petersen VP of Projects Denton, TX As an accomplished jazz musician and father of six kids, Darren switched gears a while back to look for more lucrative ways to make a living. He got into web development using PERL and PHP3 back in 1999 and got into Drupal in 2007 while he was head of the University of North Texas Web Development Center. Before joining Lullabot, he worked on various projects for higher education institutions and led the construction of major Drupal sites at [Scholastic.com](https://www.scholastic.com/teachers/teaching-tools/home.html). Though a Drupal development talent in his own right, Darren was a technical project manager for Lullabot and is now the VP of Projects. His expertise in organization and Drupal has resulted in successful projects, including leading improvements of [msnbc.com](https://www.ms.now), re-platforming a major internal news site for Qualcomm, and launching [nammfoundation.org](https://www.nammfoundation.org/). Darren was at the helm of launching the state of [Georgia's GovHub](https://www.lullabot.com/our-work/govhub-building-georgias-digital-future), which included websites and services for more than 80 different programs and agencies. Darren prefers Agile to Waterfall and has an interest in the history of software development and how that’s shaped the way we work today. He runs his scrums from his office in Denton, TX, where he lives with his wife and kids. When he's not overseeing projects, Darren continues making music, having the ability to play six instruments. ## Did you know? Darren plays the saxophone but only has 1.5 lungs. Go figure! ## More about Darren ### Projects Worked on at Lullabot: - Qualcomm - [MSNBC](https://www.lullabot.com/our-work/msnbc) - [NYU Langone School of Medicine](https://www.lullabot.com/our-work/nyu-school-of-medicine) - [Edutopia](https://www.lullabot.com/our-work/building-a-new-edutopia-on-decoupled-drupal-8) - [The State of Georgia](https://www.lullabot.com/our-work/govhub-building-georgias-digital-future) - [Georgia Public Broadcasting](https://www.lullabot.com/our-work/georgia-public-broadcasting) - Ellucian - UMass Amherst ### Education & Certifications: - Bachelor of Arts in Jazz Studies, University of North Texas ## Featured resources ... [ More ... ](/resources) [### Why Architecture Over Enforcement Allows Small Teams to Scale Governance Read ](/articles/digital-governance-architecture-vs-enforcement) [### Governance Without Authority: How to Align Agencies Across a Statewide Digital Platform Read ](/articles/state-government-digital-ecosystem-governance-without-mandate) [### Not Everything is a User Story Read ](/articles/not-everything-is-a-user-story) ## Featured work ... [ See all our work ](/our-work) - [ ### Spredfast Showcasing ‘Smart Social’ for Technology's Biggest Event ](/our-work/spredfast) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) --- --- title: "Micah Godbolt" url: "/about/micah-godbolt" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Micah Godbolt # Micah Godbolt Former Front-end Developer Vancouver, WA Micah Godbolt was born and raised in the PNW (Pacific Northwest, of course) which is where he resides today. Micah has a diverse background starting with audio production (he even wooed Lullabot with a song entitled [“Hello Lullabot”](https://vimeo.com/55253397) with his application!), moving into live audio/video and lighting and then into front end web development and teaching. He is currently teaching himself drupal module development and playing around with Sass, Jekyll and any other fun front end tech he can find. Micah and his wife, Julie, enjoy getting outdoors (weather permitting), taking walks, watching movies and trying to keep their daughter, Emery, out of too much trouble. --- --- title: "Kris Bulman" url: "/about/kris-bulman" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Kris Bulman # Kris Bulman Former Senior Front-end Developer Charlottetown, Prince Edward Island Canada Senior Front-end Developer Kris Bulman hails from Canada’s Prince Edward Island, one of the most desirable beach towns in the world, at least for the month of July. Prior to joining Lullabot, Kris worked for the University of Prince Edward Island. He’s been developing flexible base themes and theming Drupal sites since 2007. His love for Drupal also led to contract work with a number of museums, universities and research companies around the world. Kris has done a lot of work in the open source world including being a co-maintainer of Drupal’s Zen theme, building his own Sass grid system, and he’s dabbled in iOS app design. Lullabot has geeks of just about every stripe, but Kris was our first library nerd. He knows how to operate an [Espresso Book Machine](https://ondemandbooks.com/), which can print, bind, and cut store-quality books on demand from just a PDF file. He also knows how to operate industrial grade scan robots like the ones used by Google for the Google Books project that can open, turn pages, and scan a 1,500-page book in about an hour. In a former life, Kris was also a professional photographer, specializing in panoramic and 360-degree photos of interiors, and landscapes, and his artistic portraits have appeared in local art galleries on the Island. Kris lives with his wife Jen, two young children Claire, 6, and Elliott, 4, and an active Standard Poodle named Sadie who takes him out hiking on Prince Edward Island’s scenic forest trails. ## Featured resources ... [ More Resources ](/resources) [### Carving a Future for Responsive Images Read ](/articles/carving-a-future-for-responsive-images) --- --- title: "Greg Dunlap" url: "/about/greg-dunlap" type: bio date: 2015-06-23 updated: 2024-07-24 --- # Greg Dunlap # Greg Dunlap Former Director of Strategy Monterey, CA - He - Him Greg got his start in Drupal in 2006 at a Lullabot onsite training for his employer at the time, the Seattle Times Company. Feeling an instant kinship with Lullabot Jeff Eaton, Greg accepted Eaton's encouragement to contribute and lead one of the eight core initiatives for Drupal 8. The so-called [CMI initiative](http://groups.drupal.org/node/191283) endeavored to consolidate Drupal's scattered site data, views, content types, and module settings, to a unified, secure API. The system, which shipped as part of Drupal 8, made deploying, testing, and reusing Drupal site configuration more consistent and easier to manage. Greg graduated from college in 1991, majoring in photojournalism and fine art photography. With the US in a deep recession and the future of publishing already on the verge of disruption, Greg was the first to volunteer when his boss, the owner of a Chicago Real Estate newspaper, asked if anyone wanted to help move the paper's database from Paradox for Dos to Paradox for Windows. He's been in the software business ever since. Greg is also a world-class pinball player and has been participating in pinball tournaments for nearly 25 years. For some of his career, he managed to marry his fascination with the game with his software engineering work, writing C++ code to run various games. His Drupal resume includes stints with leading Drupal agencies Palantir and NodeOne, and his body of work includes work for [*Foreign Affairs*](https://www.foreignaffairs.com/) magazine, [Ikea,](https://www.ikea.com/) and the Swedish Government. He authored the Deploy module and was a longtime maintainer of the Services module. He is also co-author of Packt's "[Drupal 7 Module Development](https://www.amazon.com/Drupal-Module-Development-Matt-Butcher/dp/1849511160)." ## Did you know? Greg is an internationally ranked competitive pinball player. ## More about Greg ### Drupal Contributions - Former maintainer of Services and Deploy modules - Initiative lead for Configuration Management Initiative in Drupal 8 ### Projects - American Booksellers Association (ABA) - Georgia.gov - [State of Massachusetts](https://www.lullabot.com/our-work/massgov-covid-19) - Harvard - NYU School of Medicine - [Ames Laboratory](https://www.lullabot.com/our-work/ames-laboratory) - Principal - [DocuSign](https://www.lullabot.com/our-work/docusign) - Penguin / Random House Canada - [Edutopia](https://www.lullabot.com/our-work/building-a-new-edutopia-on-decoupled-drupal-8) - Tesla Motors ### Education and Certifications - Bachelor of Science in Photojournalism, Minor in Fine Art Photography, Northern Illinois University ## Featured resources ... [ More ... ](/resources) [### Designing Authoring Experiences That Editors Don't Hate Read ](/articles/designing-content-authoring-experiences-editors-dont-hate) [### The Benefits of Structured Content Read ](/articles/benefits-structured-content) [### Do You Need a Digital Asset Management (DAM) System? Read ](/articles/do-you-need-digital-asset-management-dam-system) ## Featured work ... [ See all our work ](/our-work) - [ ### AIChE A Sustainable Design System for a Global Professional Association AIChE The Global Home of Chemical Engineers, logo ](/our-work/aiche) - [ ### Spredfast Showcasing ‘Smart Social’ for Technology's Biggest Event ](/our-work/spredfast) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) --- --- title: "Andrew Wilson" url: "/about/andrew-wilson" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Andrew Wilson # Andrew Wilson Former Senior Account Manager Memphis, TN Andrew Wilson has always been drawn to the intersection of technology and education, so it's natural he should find himself leading Lullabot's education sales. Andrew began his career as an admission counselor at Davidson college, which afforded him the opportunity to travel and meet new people within the academic community. Working in higher education also made him a passionate advocate for education reform and accessibility. It was that passion that led him to form [ReadyEssay.com](http://readyessay.com), an online resource for college applicants Andrew founded in 2009. The venture required that he build a website, so he taught himself Drupal...or Duplo, as his mother-in-law still calls it. The rest, as they say, is history! Since then, Andrew has been involved in the Drupal community and most recently led sales and client relations for Zivtech, an open source development firm in Philadelphia. Today Andrew's passion for education has him traveling and speaking at several Drupal events. When he's not on the road, you'll find him helping Lullabot's clients climb Drupal's daunting learning curve. If you ask Andrew, though, he'll say it's really not that daunting if you've got the right tools. Andrew currently lives in Charlottesville, Virginia with his beautiful wife, Sarah, and bad-a\*\* toy poodle, Charlie. He is an avid reader and loves to travel (even if there isn't a Drupal event involved). --- --- title: "Emma Jane Westby" url: "/about/emma-jane-westby" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Emma Jane Westby # Emma Jane Westby Former Education Development Coordinator Owen Sound ON [Emma Jane Westby](https://en.wikipedia.org/wiki/Emma_Jane_Hogbin) (née Hogbin) is an internationally renowned open source software advocate, technical author, and teacher. In January 2010 she was recognized by The Google Diversity Programme for her efforts in increasing female participation in software development. She is a frequent speaker at open source conferences in Canada, USA, and Europe. In addition to her engaging conference presentations, Emma has also worked as a technical college instructor, has worked on curriculum development for enterprise clients, and successfully run her own business, Design to Theme, which provided widely acclaimed Drupal training and digital workbooks. Emma has been teaching internet technology since 2002. She is the author of [Front End Drupal](https://www.amazon.com/o/ASIN/0137136692) and [Drupal User's Guide](https://www.amazon.com/Drupal-Users-Guide-Administering-Drupal-Powered/dp/0137041292), a contributor to [Linux Desktop Hacks](https://www.amazon.com/Linux-Desktop-Hacks-Customizing-Optimizing/dp/0596009119), [Drupal's Building Blocks](https://www.amazon.com/Drupals-Building-Blocks-Quickly-Panels/dp/0321591313), and has written articles for [USENIX](https://www.usenix.org/), [Drupal WatchDog](http://www.drupalwatchdog.net/), [Linux Pro Magazine](https://www.linuxpromagazine.com), and [24 Ways](https://24ways.org/). Emma encourages non-traditional participation in technology through craft and believes that everyone is capable of mastering the tools that surround them. To help engage new ways of participating in technology, she open sourced one of her knitting patterns so that you can make your very own [Drupal Socks](https://emmajane.net/craft/drupal) (as featured in [CRAFTzine](http://blog.craftzine.com/archive/2008/08/drupal_knitting_charts.html)). Emma lives with her husband in Owen Sound, Ontario, Canada, where she has run for office in the Green Party, raises bees, and enjoys sipping on a nice single malt Scotch whiskey. --- --- title: "Olanda Estrada" url: "/about/olanda-estrada" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Olanda Estrada # Olanda Estrada Former Executive Assistant Providence, RI Olanda Estrada was Matt and Jeff's right-hand woman. Not only was she responsible for the day-to-day operation and management of Lullabot's office in Providence, RI, but she also acted as a personal assistant to Lullabot's co-founders. Prior to joining Lullabot, Olanda worked as Financial & Administrative Assistant/Visiting Artist Coordinator with Brown University (Visual Art Department). Although Olanda was not one of Lullabot's uber-techie developers, she could wrestle a Google Doc or iCal invite with the best of 'em. She first developed her affinity for technology when she worked at RI.gov. Olanda studied Business Management and Marketing at Johnson & Wales University. In her spare time, she enjoys creative projects, relaxing, and enjoying life with her significant other, John, and their overactive Cairn Terrier, Tarzan at their home in Providence. Good wine, delicious foods, eclectic music, honest people, and great conversations make Olanda happy. --- --- title: "Mateu Aguiló Bosch" url: "/about/mateu-aguilo-bosch" type: bio date: 2015-06-23 updated: 2024-02-01 --- # Mateu Aguiló Bosch # Mateu Aguiló Bosch Lead Engineer Mallorca, Spain - He - Him Mateu is a lead engineer with over 10 years in [Drupal development](https://www.lullabot.com/resource/drupal-development), during which he has contributed over 3,000 commits and serves as a Drupal core co-maintainer. He has extensive experience in API design, Node.js development and contribution, and distributed architectures in the cloud. Mateu created the RESTful v2 module while working on an NBC project, which laid the foundation for the current decoupled Drupal landscape. He was also the coordinator of and contributor to the Drupal API-First initiative and the co-creator of [Contenta CMS](https://www.contentacms.org/) and most of the decoupled Drupal modules. As a prolific contributor, Mateu has too many modules to highlight; in the last year, he created [Warmer](https://www.drupal.org/project/warmer) and [API Proxy](https://www.drupal.org/project/api_proxy) during his work on a [project for IBM](https://www.lullabot.com/our-work/ibm-cloud). After receiving his Bachelor's degree in telecommunications engineering from the University of Barcelona and completing his Master's thesis at UC Berkeley, Mateu began his career as a speech recognition developer. He was hired by the University in Barcelona to do full-time research about acoustic pattern recognition and language detection but felt constrained chasing fractional improvements in speech recognition. Frustrated that he could not explain what he was doing to family and friends, Mateu switched to web development, where the fruits of his labor were more evident to them. Mateu is passionate about sharing with the community; through IRC, Google Summer of Code, Slack, and [his YouTube channel](https://www.youtube.com/c/mateu-e0ipso). He has presented at numerous DrupalCons, and he mentors Drupal developers and new contributors. Of all the Lullabots, Mateu probably lives in the most envy-inducing place possible. As a resident of the tropical paradise Mallorca, Spain, he enjoys long hikes on unspoiled Mediterranean beaches, playing tennis and soccer, and, occasionally, sampling from the island’s 2400 restaurants. He is a proud papa to two little humans. ## Did you know? Mateu trained his two cats to come after a certain whistle even from one block away. ## More about Mateu ### Drupal Contributions: - [Drupal Core](https://www.drupal.org/project/drupal) - [Typed Entity](https://www.drupal.org/project/typed_entity) - [Component Libraries: Components](https://www.drupal.org/project/cl_components), and [its ecosystem](https://www.drupal.org/project/cl_components/ecosystem). - [JSON:API module](https://www.drupal.org/project/jsonapi) (now part of Drupal Core) - [Simple OAuth](https://www.drupal.org/project/simple_oauth) - [RESTful module](https://www.drupal.org/project/restful) - [Warmer module](https://www.drupal.org/project/warmer) - [API Proxy](https://www.drupal.org/project/api_proxy) - [...and m](https://www.drupal.org/u/e0ipso)[any ](https://www.drupal.org/u/e0ipso)[more](https://www.drupal.org/u/e0ipso) ### Projects worked on at Lullabot: - [MSNBC](https://www.lullabot.com/our-work/msnbc)​​​​​​​ - NBC - [IBM](https://www.lullabot.com/our-work/ibm-cloud) ### Education and Certifications: - Bachelor's Degree in Telecommunications Engineering, University of Barcelona - Master of Science, Language and Speech Technologies and Applications, University of California at Berkeley - Certificate of Pedagogical Aptitude (CAP) ## Featured resources ... [ More ... ](/resources) [### The New Storybook Module for Drupal Read ](/articles/new-storybook-module-drupal) [### Single Directory Components in Drupal Core Read ](/articles/getting-single-directory-components-drupal-core) [### Component Libraries in Drupal Read ](/articles/component-libraries-drupal) --- --- title: "Adrian Young" url: "/about/adrian-young" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Adrian Young # Adrian Young Former Technical Project Manager Saint Paul, MN Adrian Young became fascinated with computers at an early age when his parents brought home a Commodore 64 and he began writing programs using Basic and Logo. A bit later, he began maintaining and customizing bulletin board systems and experimenting with digital art. These experiences, coupled with a life-long interest in drawing and design, led him to choose architecture as his college major (think bricks, not bytes) as a way to combine technical and aesthetic thinking in a single pursuit. Following his undergraduate work, Adrian spent a few years working in architecture firms, designing in CAD, building 3D models and creating digital renderings. Along the way, he was asked to create websites and provide IT support in addition to his architectural duties. Eventually he began to feel that working entirely in the digital realm might be more to his liking, and started pursuing web development—first as a hobby, and later full-time. Adrian began tinkering with Drupal sometime in 2009, and has been developing with it exclusively since 2010. He remains very appreciative of the Drupal community, and loves the opportunities for both creative and technical work in his chosen profession. Adrian joins Lullabot following stints building Drupal sites for public universities, major recording artists and top-tier retailers. He also enjoys music, books, art and design, and spending time with his wife and two young children. ## Did you know? Prior to his work in web development, Adrian has worked as both an auto mechanic and an architect. --- --- title: "Justin Harrell" url: "/about/justin-harrell" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Justin Harrell # Justin Harrell Former Interactive Designer Cleveland, OH Justin Harrell loves all things creative, and has an extensive background in design and illustration, ranging from multinational corporate websites to children’s books. His experience both as an Art Director at an advertising agency, and as a freelancer, has had him working on many complex website designs, bringing clarity and beauty to the web experience for organizations like Daimler Trucks North America, Sherwin Williams, Lincoln Electric, Cracker Barrel, and more. In addition to having the ability to speak in a visual language, he can also turn those thoughts into code with responsive, semantic HTML and CSS. Justin was born and raised in the great state of Ohio, and lives in Cleveland with his fiancé Erin, and their dog Billy. When not in front of a computer, he likes to take in the great outdoors with fishing, hiking, and camping. Another creative outlet for him is brewing up some tasty beer, and as a hopelessly optimistic Cleveland sports fan, it’s quite convenient to have all that home-brewed beer around to drown his miseries. ## Did you know? When Justin was in preschool, he got caught painting the windows of the building with peaches dipped in milk. --- --- title: "John Albin Wilkins" url: "/about/john-albin-wilkins" type: bio date: 2015-06-23 updated: 2023-06-13 --- # John Albin Wilkins # John Albin Wilkins Former Senior Front-end Developer Taipei TW John Albin Wilkins has been a web developer since the Paleolithic Era. In early 1993, he was introduced to NCSA Mosaic, the first graphical web browser, and spent a few hours browsing the entire web (which only consisted of NCSA’s and CERN’s websites). He was unimpressed. Nonetheless, he had nothing better to do and started building websites. In 2005 [In 2004](http://drupal.org/user/11297), John finally learned how idiotic it is to build your own web application framework and discovered the power of Drupal; he never looked back. He is the lead for the [Drupal 8 Mobile Initiative](http://groups.drupal.org/mobile/drupal-8), one of the top 30 Drupal contributors to Drupal 7 and is listed a few times in Drupal’s MAINTAINERS.txt. He maintains several well-used contrib projects, including the [Zen theme](http://drupal.org/project/zen), [Menu Block](http://drupal.org/project/menu_block), [Menu Position](http://drupal.org/project/menu_position), and [Fences](http://drupal.org/project/fences). He is also the co-author of [Drupal 7 Module Development](https://www.amazon.com/dp/1849511160?tag=albinnet-20) and author of the upcoming O’Reilly book, *Sass and Compass*. John currently lives with his wife and kids on the island of Taiwan, where they occasionally play with Lemurs. --- --- title: "Dave Reid" url: "/about/dave-reid" type: bio date: 2015-06-23 updated: 2024-11-13 --- # Dave Reid # Dave Reid Senior Developer Omaha, NE - He - Him Dave Reid started using Drupal in 2006 and was instantly hooked on the code, and shortly thereafter, hooked on the community. He started writing modules and to this day has not stopped; he even wrote a module to keep track of how many modules he has written. In addition to maintaining over 100 modules (more than any single human being should), including such luminaries as Pathauto, Token, Media, Entity View Modes, XML Sitemap, Dave is also an active Drupal Core developer and was recognized as one of the [top ten contributors to Drupal 7](https://www.knaddison.com/drupal/contributors-drupal-7-final-numbers) and [top 100 contributors to Drupal 8](http://drupalcores.com/). When he is not working on modules or core, you can find Dave performing security team duties, helping administer Drupal.org, or participating in the [Drupal Nebraska meet ups.](https://groups.drupal.org/nebraska) Prior to Lullabot, he worked for Drupal firm Palantir as well as several consulting and contracting positions. Dave resides in Omaha, Nebraska, USA, with his wife [Jenny](https://twitter.com/jenreidreads), three kids, and three cats. What little free time Dave doesn't devote to Drupal, he spends rooting for the Nebraska Cornhuskers college football team, collecting LEGO "for his kids," and trying out fun new recipes with his family. ## Did you know? Dave wrote a Drupal module explicitly for the purpose of keeping track of the number of modules he has written. Rodney is the first cat to have run for the Drupal Association board and is the unofficial mascot of the Drupal community. --- --- title: "Jesse Mathewson" url: "/about/jesse-mathewson" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Jesse Mathewson # Jesse Mathewson Former Account Coordinator Providence, RI Jesse Mathewson loves a challenge, and in the Account Coordinator role, she got her fair share. Managing and qualifying all new client inquiries, organizing travel, company events and shipping out swag (and that’s just the tip of the iceberg) were all encompassed in Jesse’s role. Before she joined Lullabot, Jesse was the Marketing Manager for a national franchise company where she balanced the needs & coordinated efforts between three offices. Jesse is from California and loves the sunshine and adventuring outdoors with her family. She graduated from UC Santa Barbara with a degree in religious studies, where she got her Zen on both in class and by spending time on the beautiful beaches. Jesse now lives in RI with her husband, their son and another on the way, and their two cats. --- --- title: "Kris Konrady" url: "/about/kris-konrady" type: bio date: 2015-06-23 updated: 2026-05-22 --- # Kris Konrady # Kris Konrady CHRO Ames, IA - She - Her Kris is a human resources executive who believes taking care of people and taking care of the business are the same thing. As CHRO at Lullabot, she leads the administrative, financial, and human resources functions that keep a fully remote, distributed company running and thriving. Her work spans developing compensation strategy (including a fair-pay process she's particularly proud of), performance management, benefits, payroll, and compliance across multiple countries and states for both employees and independent contractors. She's committed to fostering a culture of transparency and candid feedback because real professional growth requires honest conversations, even the hard ones. With more than a decade in HR and 20+ years of leadership experience, Kris brings a rare blend of operational depth and genuine people-first thinking to the C-suite. She holds a bachelor's degree in theology from Creighton University, which turns out to be excellent preparation for asking hard questions and sitting with uncertainty, along with certificates in bookkeeping and human resources management. In her free time, Kris likes to hike, camp, bake, read, and sit around a campfire eating good food and sharing good conversations with friends. She loves all the seasons in the Midwest and is grateful that her husband makes her laugh every day. ## Did you know? Kris grew up fishing and camping with her family and was a certified Wilderness First Responder who led camping trips for girls. She still believes food tastes better when eaten in the great outdoors. --- --- title: "Lisa Kastner" url: "/about/lisa-kastner" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Lisa Kastner # Lisa Kastner Former Marketing Manager Long Beach NY In her job application for Lullabot, Lisa Kastner answered the question what makes you unique as follows, “In my next life I’d like to come back the same, but with amazing superpowers like flight, telepathy, not getting sunburned. Better hair too!” In this life, Lisa grew up in and around military bases in Turkey, Germany, and Japan. Both of her parents were in the military, and her mother was an OB-GYN with a gun and the rank of major! Growing up Lisa always wanted to be an FBI agent but ended up in a job with less well-defined objectives. As marketing manager, she’s tasked with figuring out what that role means at Lullabot (telepathy would have really come in handy). Having worked in more established marketing roles at Canon, Billboard, and the Specialty Food Association, Lisa finds the lack of an incumbent way of doing things at Lullabot refreshing. Returning to the U.S. from her globe-trotting childhood for college, Lisa went to school in New York and hasn’t left since. She now lives about 50 minutes outside of New York City in Long Beach with her 5-year-old daughter Kyla. When she isn’t marketing at Lullabot, Lisa spends her time running on the beach and doing yoga. Her favorite pose is Dhanurasana, dancer’s pose because it’s the “exact opposite of sitting at a computer.” ## Featured resources ... [ More Resources ](/resources) [### Back From Retreating! Read ](/articles/back-from-retreating) --- --- title: "Amber Matz" url: "/about/amber-himes-matz" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Amber Matz # Amber Matz Trainer Beaverton, OR Amber Matz creates and manages educational content for Drupalize.Me. Amber has over 14 years of experience as a full-stack web developer. She has built PHP/MySQL applications and Drupal sites for small businesses, educational institutions, and major corporations. Amber enjoys sharing her knowledge with others and jumps at the opportunity to speak at local user groups and conferences on a variety of technical topics for beginning to advanced-level audiences. She is a regular speaker at the Portland Drupal User Group and recently presented at DrupalCon North America, Devsigner — a conference for designers and developers in Portland, and will present at PNWPHP in Seattle in the Fall of 2015. She also regularly participates in a code mentoring group here in Portland. One of Amber's primary responsibilities is creating Drupal tutorials for Drupalize.Me. You can find her training materials on Webform, Mapping with Leaflet, Panels, Getting Started with Responsive Web Design and others in the Drupalize.Me Library. She also contributes regularly to the Drupalize.Me blog, writing on topics related to Drupal 8, Object-oriented PHP, the Drupal Community, and upcoming releases to Drupalize.Me's training library. She is also a regular hostess of the Drupalize.Me podcast, a bi-weekly show featuring guests from the Drupal community and beyond. Amber lives out in the 'burbs of Portland, Oregon with her husband and kitty cat and enjoys a variety of hobbies including crocheting and gardening. ## Did you know? Amber plays a red accordion. ## Featured resources ... [ More Resources ](/resources) [### SEO and Customer Data Listen ](/podcasts/drupalizeme-podcast/seo-and-customer-data) [### Project Management Listen ](/podcasts/drupalizeme-podcast/project-management) [### Drupal as a Services Platform Listen ](/podcasts/drupalizeme-podcast/drupal-as-a-services-platform) --- --- title: "Joe Fender" url: "/about/joe-fender" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Joe Fender # Joe Fender Former Senior Developer London, United Kingdom Joe is a former Senior Developer at Lullabot who has always trusted his passion for his hobbies to lead him through life. This was obvious at the age of 10 when he built his first website showcasing his obsession with The Simpsons. In the years following, Joe taught himself languages such as HTML, CSS, PHP, MySQL and Javascript. After obtaining his degree in Computer Science with Games Technology in his hometown, London, Joe’s passion for exciting and innovative technologies led him to Japan where he instantly fell in love with the culture, food, and work ethic. Over the 4 years that Joe lived in Japan, he founded and led a team of 10 young creatives at [Studio Umi](https://www.studio-umi.jp/), a startup based in Kyoto, to become the leading Drupal shop in Japan. His journey with Studio Umi is where Joe discovered Drupal, and he has since used it religiously for all projects that he works on. In his spare time Joe loves to play drums, collect shoes, climb, and work on side projects such as Drupal modules [Tournament](https://drupal.org/project/tournament) and [BracketCloud](https://drupal.org/project/bracketcloud). In early 2015, Joe co-authored the book [Front-End Fundamentals](https://leanpub.com/front-end-fundamentals) with fellow bot [Carwin Young](https://www.lullabot.com/about/carwin-young). ## Did you know? Joe often gets mistaken for Daniel Radcliffe. ## Featured resources ... [ More Resources ](/resources) [### Build native iOS and Android apps with React Native Read ](/articles/build-native-ios-and-android-apps-with-react-native) [### Front-End Web Development Fundamentals Read ](/articles/frontend-web-development-fundamentals) --- --- title: "Matthew Tift" url: "/about/matthew-tift" type: bio date: 2015-06-23 updated: 2026-02-23 --- # Matthew Tift # Matthew Tift Lead Engineer Minneapolis, MN - He - Him Dr. Matthew Tift is a lead engineer at Lullabot, where, since 2014, he regularly serves as the technical architect on large-scale web projects for organizations such as the state of [Maryland](https://www.linkedin.com/posts/lullabot_marylandgov-activity-7414353918618529792-3C2-/), [UMass Amherst](https://www.lullabot.com/our-work/umass-amherst), and [Georgia Public Broadcasting](https://www.lullabot.com/our-work/georgia-public-broadcasting). A committed open-source contributor, Matthew co-maintained the [Drupal 8 configuration system](https://www.drupal.org/about/core/policies/roles-and-responsibilities/past-core-maintainers#s-configuration-api) for a decade and played a key role in organizing the [Olivero initiative](https://www.drupal.org/project/olivero), which became Drupal’s default front-end theme. He has been [contributing to Drupal core](https://www.drupal.org/u/mtift) since 2010, has helped organize Twin Cities DrupalCamp since it started in 2011, and is a regular speaker at conferences such as DrupalCon, All Things Open, Open Source Summit, and GitLab Commit. Outside of work, Matthew is a [yoga and meditation teacher](https://matthewtift.com) with a Ph.D. in musicology from the University of Wisconsin–Madison. He lives near Minneapolis, where he enjoys cycling and playing string quartets with his wife and two children. ## Did you know? Matthew has attended every North American DrupalCon since 2010. ## More about Matthew ### Drupal Contributions: - [Drupal 8 Configuration AP](https://git.drupalcode.org/project/drupal/blob/HEAD/core/MAINTAINERS.txt#L126https://git.drupalcode.org/project/drupal/blob/HEAD/core/MAINTAINERS.txt#L126)[I](https://git.drupalcode.org/project/drupal/blob/HEAD/core/MAINTAINERS.txt#L126https://git.drupalcode.org/project/drupal/blob/HEAD/core/MAINTAINERS.txt#L126) - [NPR](https://www.drupal.org/project/npr) - [PBS Media Manager](http://PBS%20Media%20Manager) - [Accelerated Mobile Pages](https://www.drupal.org/project/amp)[ ](https://www.drupal.org/project/amp)[(AMP)](https://www.drupal.org/project/amp) - [Olivero](https://www.drupal.org/project/olivero) - Organizer of Twin Cities Drupal Camp (since its inception in 2011) - ... and more. ### Projects Worked on at Lullabot: - State of Maryland - [Ames Laboratory](https://www.lullabot.com/our-work/ames-laboratory) - [Georgia Public Broadcasting](https://www.lullabot.com/our-work/georgia-public-broadcasting) - Evergreen State College - National Association of Music Merchants (NAMM) - [Accelerated Mobile Pages](https://www.lullabot.com/our-work/accelerated-mobile-pages) - NBC - New York University - NewsBank - American Booksellers Association ### Education & Certifications: - Doctorate of Philosophy in musicology, the University of Wisconsin-Madison ## Featured resources ... [ More ... ](/resources) [### From Lando to DDEV: Replacing Custom Shell Scripts with Drainpipe Read ](/articles/lando-ddev-replacing-custom-shell-scripts-drainpipe) [### Yvette Erasmus on Building Healthy Relationships with Nonviolent Communication (NVC) Listen ](/podcasts/hacking-culture/yvette-erasmus-building-healthy-relationships-nonviolent-communication-nvc) [### Dori Kelner on Workplace Mindfulness Programs Listen ](/podcasts/hacking-culture/dori-kelner-workplace-mindfulness-programs) ## Featured work ... [ See all our work ](/our-work) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) - [ ### Google AMP-ing up Drupal Google ](/our-work/accelerated-mobile-pages) --- --- title: "Thomas Lattimore" url: "/about/thomas-lattimore" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Thomas Lattimore # Thomas Lattimore Former Front-end Developer Charlotte, NC Thomas is a former front-end developer at Lullabot who began building websites with Drupal in 2009 when he was attracted to Drupal for it’s flexibility and vibrant community. He has been an active member of the Charlotte Drupal User Group since it began in Fall of 2010. Thomas’ contributions to the Drupal community include: presentations at numerous Drupal Camps and meetups, a number of theme related patches to Drupal 7 and 8 core, and contributions to several themes and modules on Drupal.org. Moving forward, Thomas also plans on experimenting with Node.js and AngularJS. Thomas Lattimore was born in a small town outside of Charlotte, North Carolina where he and his wife still reside. In his free time he enjoys spending time outdoors with his wife, playing music (guitar and drums). ## Featured resources ... [ More Resources ](/resources) [### The Unexpected Power of Viewport Units in CSS Read ](/articles/unexpected-power-of-viewport-units-in-css) --- --- title: "Mike Herchel" url: "/about/mike-herchel" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Mike Herchel # Mike Herchel Former Senior Front-end Developer at Lullabot Gainesville, Florida - He - Him Mike Herchel has been passionate about web development since creating his first website in 2001. He co-owned and operated a small development and design agency in the mid-2000s until moving to a hybrid sysadmin/web developer role at a medium-sized non-profit in 2007. Mike started using the Drupal content management system in 2008 and has been involved in the Drupal community since 2010. He enjoys speaking at conferences, sharing knowledge as much as possible, and writing about himself in the third person. You can generally find him presenting on usability, front-end development, and many other topics at various web development conferences. Mike also co-hosted [the Lullabot Podcast](https://www.lullabot.com/podcasts/lullabot-podcast), which talks about the state of Drupal and other web technologies. While at Lullabot, Mike worked on projects such as the Syfy Network, USC Annenberg, NewsBank, Principal Financial, the National Association of Music Merchants (NAMM), and Ames National Laboratory. He's passionate about making accessible websites that are also performant. While working on Syfy.com, Mike focused on making a website that featured heavy use of animation feel performant and silky smooth. Mike also co-hosted [the Lullabot Podcast](https://www.lullabot.com/podcasts/lullabot-podcast), which talks about the state of Drupal and other web technologies. Outside of the digital world, Mike loves everything outdoors, including hiking, fishing, kayaking, college football, and hammocking. He owns an awesome telescope that’s over 30 years old and uses it as much as Florida’s swampy weather will let him. ### Drupal Contributions: - Creator and maintainer of the [Quicklink module](https://www.drupal.org/project/quicklink) - Organizer of the new [Drupal 9 front-end theme initiative](https://www.drupal.org/about/strategic-initiatives/olivero) - Co-organizer of [Florida DrupalCamp](https://www.fldrupal.camp/) ### Projects worked on at Lullabot: - [Syfy Network](https://www.lullabot.com/our-work/syfy) - USC Annenberg - NewsBank - Principal Financial - [National Association of Music Merchants](https://www.lullabot.com/our-work/namm-foundation) - Ames National Laboratory ## Did you know? Mike plays adult coed kickball. His team's name is "Kickin' Sass"! ## Featured resources ... [ More ... ](/resources) [### The End of the Road for Drupal 8, Many More Drupal 9 Beginnings Listen ](/podcasts/lullabot-podcast/drupal8-eol) [### How to Plan a Successful Design Handoff Read ](/articles/painless-design-handoffs) [### Real Talk: Hiring People to Fill Drupal Roles Listen ](/podcasts/lullabot-podcast/real-talk-hiring-people-fill-drupal-roles) ## Featured work ... [ See all our work ](/our-work) - [ ### Pantheon An Amazing, Fast New Site for an Amazingly Fast Hosting Platform Pantheon ](/our-work/pantheon) - [ ### Syfy Creating an Out of This World Experience for Science Fiction Fans SYFY ](/our-work/syfy) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) --- --- title: "Danny Joris" url: "/about/danny-joris" type: bio date: 2015-06-23 updated: 2025-03-10 --- # Danny Joris # Danny Joris Senior Front-end Developer Charlottetown, Prince Edward Island, Canada - He - Him Danny Joris, CPACC is a Belgian-Canadian front-end developer who started his career in web development in 2008, working extensively as a freelancer and with various Drupal companies providing theming and development services. Throughout his career, he's leveraged his Master's degree in Fine Arts from St. Lucas Academy in Antwerp, Belgium, to deliver balanced front-end projects filtered through an artistic sensibility. Through his contributions to the [Islandora framework](https://www.islandora.ca/), Danny has worked with a wide range of organizations, including many higher education and cultural institutions. Danny's areas of expertise include large, searchable image archives and React front-end projects. Among other projects, Danny has used his React and front-end skills to work on [Tugboat's UI](https://www.lullabot.com/our-work/tugboat-has-redesigned-experience-developers-and-qa-teams), a former Lullabot client-only tool that has evolved into a public SaaS offering. Danny's other area of expertise includes providing a11y audits, which he has written about in his article, [*Monitoring Web Accessibility Compliance with Pa11y Dashboard*](https://www.lullabot.com/articles/monitoring-web-accessibility-compliance-with-pa11y-dashboard). Danny enjoys beach days, running and swimming. He enjoys quality family time with his wife and three kids at their home in Prince Edward Island, Canada. ## More about Danny ### Drupal Contributions - [Responsive image batch](https://www.drupal.org/project/responsive_image_batch) - [Simplytest.me](https://www.drupal.org/project/simplytest), credited on 1 issue - [Download & Extend](https://www.drupal.org/project/drupal), credited on 2 issues - [Drupal.org security advisory coverage applications](https://www.drupal.org/project/projectapplications), credited on 1 issue - [... and more](https://www.drupal.org/u/danny_joris) ### Projects - Bravo - Angie's List - Cisco - NBC - [Tugboat](https://www.lullabot.com/our-work/tugboat-has-redesigned-experience-developers-and-qa-teams) ## Featured resources ... [ More Resources ](/resources) [### Monitoring Web Accessibility Compliance With Pa11y Dashboard Read ](/articles/monitoring-web-accessibility-compliance-with-pa11y-dashboard) --- --- title: "Jen Witkowski" url: "/about/jen-witkowski" type: bio date: 2015-06-23 updated: 2024-06-21 --- # Jen Witkowski # Jen Witkowski Associate Design Director Buffalo, NY - She - Her Jen started her career as a graphic designer but fell in love with web design after working on a small in-house website back in 2003. She’s now a Senior Interactive/UX designer with over ten years of experience. Jen loves creative problem-solving and the creative research process. She always takes an opportunity to learn something new, from JavaScript at work to installing a new window at home. Jen has experience with everything from mobile apps to websites, including some branding and print work. She’s used that experience at Lullabot, working with clients such as MSNBC, This Old House, the NYU School of Medicine, IBM, Carnegie Mellon University, and Ames Laboratory. Her work has ranged from creating intuitive user experiences for intricate, data-dense apps to designing navigational solutions for complex website hierarchies. At Lullabot, she was a lead designer of Olivero, the default theme for Drupal 9, and helped Iowa State University create an intuitive user experience for an app that helps landowners understand the benefits and costs of planting prairies and trees on their land. She also worked with the NYU School of Medicine to design a navigational solution that simplified the user experience of a complex hierarchy. Her passion for design drove Jen to earn a Master of Fine Arts Degree in Computer Art from the Rochester Institute of Technology. She's an active teacher and mentor in the design community, having leveraged her experience to teach several workshops and serving as an adjunct professor at a local college teaching web & graphic design. In her free time, Jen enjoys gardening, sailing, cooking, and spending time outdoors. On a sunny day, you can often find her enjoying a good book on her patio. Jen and her rescue dog, Oreo, live in Buffalo, NY. ## Did you know? Jen is an avid gardener who loves cheese. ## More about Jen ### Drupal Contributions - Lead designer of Olivero, the default theme for Drupal 9 ### Projects - [MSNBC](https://www.lullabot.com/our-work/msnbc) - [This Old House](https://www.lullabot.com/our-work/this-old-house) - [The NYU School of Medicine](https://www.lullabot.com/our-work/nyu-school-of-medicine) - Ames Laboratory - [IBM Cloud](https://www.lullabot.com/our-work/ibm-cloud) - [The NAMM Foundation](https://www.lullabot.com/our-work/namm-foundation) - [Carnegie Mellon University](https://www.lullabot.com/our-work/carnegie-mellon-university) - [Iowa State University](https://www.lullabot.com/our-work/iowa-state-university) ## Featured resources ... [ More ... ](/resources) [### Making Low-friction, Accessible Forms for MyMassGov Read ](/articles/making-low-friction-accessible-forms-mymassgov) [### The Unique Challenges of Design Systems at Scale Read ](/articles/unique-challenges-design-systems-scale) [### How to Plan a Successful Design Handoff Read ](/articles/painless-design-handoffs) ## Featured work ... [ See all our work ](/our-work) - [ ### Spredfast Showcasing ‘Smart Social’ for Technology's Biggest Event ](/our-work/spredfast) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) --- --- title: "Matt Oliveira" url: "/about/matt-oliveira" type: bio date: 2015-06-23 updated: 2025-10-28 --- # Matt Oliveira # Matt Oliveira Lead Engineer Grimsby, Ontario, Canada - He - Him Matt Oliveira, CPACC is a lead engineer who thrives on everything from integrating diverse systems to completing complex Drupal 7 to Drupal 8 replatforming projects. While Matt has made many contributions to integration-related Drupal projects, such as Drupal and Apple News, Drupal and Facebook Instant Articles, and Drupal and Fitbit, he's equally at home with Drupal's REST API. His expertise and approachable demeanor have earned him the trust of clients like Bravo and Turner, who ultimately entrusted Matt with the most important pieces of their business: their online presence. Matt has been developing websites professionally since 2009, and Drupal sites specifically since 2010. He first leveraged Drupal for higher education websites, but the robust functionality of the framework led Matt to start using Drupal for a wide variety of websites. In his time with Lullabot, Matt's highest-profile projects have entailed working on complex media and .edu websites, although he'll tackle any development project with a positive attitude. He regularly goes deep on debugging difficult cache-related issues, patiently spending hours in the debugger to not only fix but *understand* the problem. Matt's accomplishments at Lullabot include Drupal 7 and Drupal 8 re-platforming of www.bravotv.com and www.oxygen.com; contributing to the integration between Drupal and Apple Universal Search, Roku Search Feed format and Amazon Fire TV Search; contributing to the integration between Drupal and Facebook Instant Articles; contributing to the integration between Drupal and Apple News; and contributing to the concept Facebook Messenger bot integrating data from Drupal REST API. Matt lives in small-town Grimsby, Ontario, where he was raised on a fruit farm and continues to enjoy the fruits of his family's labor. Ahem. Matt lives with his wonderful wife, Maria, and loves running, biking, skiing, and working out. Matt has also played soccer all his life—even refereeing since he was 12! ## Did you know? Growing up he lived and worked on a fruit farm in Southern Ontario. He worked outside, in the heat, coming in dirty and sweaty everyday. Now he builds awesome websites in his air conditioned home office. He kinda went the other way on that one. ## More about Matt ### Drupal Contributions: - [Facebook Instant Article](https://www.drupal.org/project/fb_instant_articles) - [YouTube Push](https://www.drupal.org/project/yt_push) - [Media MPX](https://www.drupal.org/project/media_mpx) - [Fitbit](https://www.drupal.org/project/fitbit) - [Apple News](https://www.drupal.org/project/applenews) - [Views Load More](https://www.drupal.org/project/views_load_more) ### Projects Worked on at Lullabot: - Bravo - Oxygen - [NYU School of Medicine](https://www.lullabot.com/our-work/nyu-school-of-medicine) - Turner Broadcasting ### Education & Certifications: - Bachelor of Computer Science, the University of Waterloo in Canada ## Featured resources ... [ More ... ](/resources) [### Navigation Blocks and Local Tasks for Drupal's Admin UI Read ](/articles/navigation-blocks-and-local-tasks-admin-ui) [### Using Laravel's Homestead for Drupal Projects Read ](/articles/using-laravels-homestead-drupal-projects) [### Early Rendering: A Lesson in Debugging Drupal 8 Read ](/articles/early-rendering-a-lesson-in-debugging-drupal-8) --- --- title: "Wes Ruvalcaba" url: "/about/wes-ruvalcaba" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Wes Ruvalcaba # Wes Ruvalcaba Former Senior Front-end Developer Columbus, OH - He - Him Wes Ruvalcaba is a designer/developer gone, full-time front-end developer. He started building web sites in the '90s to show off his drawings and continued designing and building personal web sites until it became his career. Wes has a strong love for front-end coding, user experience, and analytics. He has been working in Drupal since version 6 when he started with Highlights for Children, where he led five web site redesigns, a large content data restructuring, and executed a myriad of online campaigns all leveraging Drupal. Wes has taught web design and development at the Columbus College of Art & Design, Girl Develop It Columbus, and takes the opportunity to mentor when possible. Wes lives in Columbus, Ohio with his dog, Lily. ## Did you know? Wes has a BFA in Illustration, where he focused on being a children's book illustrator. ## Featured resources ... [ More Resources ](/resources) [### CSS Pseudo-Elements and Transforms: My Favorite CSS Tools Read ](/articles/css-pseudoelements-and-transforms-my-favorite-css-tools) [### Scaling CSS Components with BEM, REMs, & EMs Read ](/articles/scaling-css-components-with-bem-rems-ems) [### BEM & Atomic Design: A CSS Architecture Worth Loving Read ](/articles/bem-atomic-design-a-css-architecture-worth-loving) ## Featured work ... [ See all our work ](/our-work) - [ ### Syfy Creating an Out of This World Experience for Science Fiction Fans SYFY ](/our-work/syfy) --- --- title: "Maggie Griner" url: "/about/maggie-griner" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Maggie Griner # Maggie Griner Senior UX Designer Charleston, SC Maggie has an extensive background in all phases of the design process, from user experience design and brand strategy to front-end development. She encourages a research-informed process, marrying insights from research with design execution and brand application. Though born and raised in Kentucky, Maggie currently lives in beautiful Charleston, SC—the culinary capital of the South—where she loves gardening and [getting creative in the kitchen](https://www.instagram.com/kindkitchn/?hl=en). Aside from her foodie tendencies, Maggie plays a solid round of golf (she was a member of the Women’s Golf Team at her alma mater – University of Alabama for four years), and “bends” her time to include yoga. In her time at Lullabot, Maggie has led a many high profile redesign efforts including GenealogyBank.com, Pantheon.io and the IBM Investor Relations website. She also helped redesign GRAMMY.com, the corporate intranet for SpaceX and a most recently, the DocuSign post-signing landing pages. Maggie is a budding instructor on [Skillshare](https://www.skillshare.com/user/yellomaggie), as well as a mentor for [Designlab](https://designlab.com:443/), where she helps aspiring designers discover their talents and navigate their early careers. ## Did you know? Maggie is the oldest of six kids. ## Featured resources ... [ More ... ](/resources) [### Designing with Rhythm and Proportion Read ](/articles/designing-rhythm-and-proportion) [### Usability Testing on a Tight Timeline Read ](/articles/usability-testing-on-a-tight-timeline) [### Recording Remote Usability Tests with Invision App and ScreenFlow Read ](/articles/recording-remote-usability-tests-with-invision-app-and-screenflow) ## Featured work ... [ See all our work ](/our-work) - [ ### Spredfast Showcasing ‘Smart Social’ for Technology's Biggest Event ](/our-work/spredfast) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) --- --- title: "Betty Tran" url: "/about/betty-tran" type: bio date: 2015-06-23 updated: 2025-03-10 --- # Betty Tran # Betty Tran Senior Developer London, United Kingdom Betty Tran has always loved coding. She knew it was something she wanted to pursue in a career from a young age- she built her first website about her favorite video games. Betty completed both her Bachelors degree in Computer Science then Masters Degree specialising in Intelligent Web Technologies at Queen Mary University of London in 2009. She started with Drupal after she landed her first job in a small consultancy building websites. Betty went on to build cross platform (iOS, Android) mobile applications using Titanium and most recently has been working on enterprise projects supporting and advising customers with their app development at Appcelerator. Betty is passionate about design patterns and always looks for ways to write clean, scalable and maintainable code. Betty grew up in Shoreditch, the neighborhood in London with the highest concentration of artists in Europe. She eventually moved further east to enjoy long walks in the local country park with her husband, Raj and dog, Bear. Betty’s hobbies include participating in hackathons, baking geeky themed cupcakes and traveling (to experience culture through food). ## Featured resources ... [ More Resources ](/resources) [### Navigation and Deep Linking with React Native Read ](/articles/navigation-and-deep-linking-with-react-native) [### A Recipe for Creating CouchDB Views Read ](/articles/a-recipe-for-creating-couchdb-views) --- --- title: "Matt Robison" url: "/about/matt-robison" type: bio date: 2015-06-23 updated: 2025-03-10 --- # Matt Robison # Matt Robison Content Writer & Strategist Louisville, KY Matt Robison is a senior Drupal/PHP developer who's been working on Drupal projects since 2008. He has developed and launched dozens of Drupal solutions, from successful e-commerce sites to full-fledged web apps. While at Lullabot, Matt served as a member of the leading project team that unified the codebase of Oxygen and Bravo. He also migrated Edutopia.org from Drupal 6 to Drupal 8, modifying the content model while supporting legacy content requirements. He customized JSON:API to feed into a decoupled React front-end for Edutopia.org, and planned and implemented a Total Cost-of-Ownership calculator for Cloud VMWare product. Before joining Lullabot, Matt worked with a consulting firm where he trained new developers on Drupal best practices. He managed and helped develop the migration of a school district’s 15+ websites to Drupal, which included implementing a consistent design system across all themes. He also managed and developed the migration of Lifeguard Press and Shop Bando onto Drupal Commerce. His work on that project included developing a custom personalization wizard. ## Did you know? Matt successfully wrote and Kickstarted a children's book called Princess Hiccup (http://www.amazon.com/Princess-Hiccup-Matthew-Robison/dp/0692403353/). ## More about Matt ### Projects worked on at Lullabot: - GE News - Oxygen/Bravo - [Spredfast](https://www.lullabot.com/our-work/spredfast) - [Rodale](https://www.lullabot.com/our-work/rodale) - [NYU School of Medicine](https://www.lullabot.com/our-work/nyu-school-of-medicine) - [Edutopia](https://www.lullabot.com/our-work/building-a-new-edutopia-on-decoupled-drupal-8)[ ](https://www.lullabot.com/our-work/building-a-new-edutopia-on-decoupled-drupal-8)[(George](https://www.lullabot.com/our-work/building-a-new-edutopia-on-decoupled-drupal-8)[ Lucas Educational Foundation)](https://www.lullabot.com/our-work/building-a-new-edutopia-on-decoupled-drupal-8) - IBM ### Education & Certifications: - Bachelor of Science in Computer Science, Western Kentucky University ## Featured resources ... [ More ... ](/resources) [### Personalization Pitfalls: Why Your “Smart” Content Feels Dumb Read ](/articles/personalization-pitfalls-why-your-smart-content-feels-dumb) [### Why Human-Centered Design Should Drive Your Next Website Redesign Read ](/articles/why-human-centered-design-should-drive-your-next-website-redesign) [### When is Drupal Overkill? Read ](/articles/when-drupal-overkill) ## Featured work ... [ See all our work ](/our-work) - [ ### Spredfast Showcasing ‘Smart Social’ for Technology's Biggest Event ](/our-work/spredfast) - [ ### Rodale Accelerating Rodale’s AMP Pages ](/our-work/rodale) --- --- title: "Chris Albrecht" url: "/about/chris-albrecht" type: bio date: 2015-06-23 updated: 2024-02-01 --- # Chris Albrecht # Chris Albrecht Senior Technical Project Manager Denver, CO - He - Him Faced with the daunting re-architecture of a homegrown CMS in 2007, Chris' company found Drupal, and he hasn't looked back since. On his way to Lullabot Chris worked for local Drupal companies and as a freelance developer, including the National Renewable Energy Lab where he led a team to produce a Drupal-based tool that became a required government resource for commercial and home weatherization. Prior to joining Lullabot, Chris developed a unique editorial layout tool on Blastr.com. He also hosted a podcast called ["Behind the Screens"](https://www.lullabot.com/podcasts/behind-screens) interviewing members of the Drupal community. Chris was ecstatic to join Lullabot as a back-end developer on the SyFy Channel redesign. Seeing that project from the management side, Chris has gone on to many other projects as a lead developer and eventually converted that leadership into a project manager role. Chris took on the challenge of [streamlining IBM’s Drupal experience](https://www.lullabot.com/our-work/ibm-investor-relations) and most recently has been leading a team of more than 30 people as the client migrates to a brand new CMS. When not behind the screen, Chris can often be found in Colorado running through Rocky Mountain trails or teaching folks about the Colorado Gold Rush and the history of his state and town as a local tour guide. ## Did you know? Chris grew up in South Park. ## More about Chris ### Drupal Contributions - [AdChoices Link (formerly Ghostery)](https://www.drupal.org/project/ad_choices_link) - [Auto Weight](https://www.drupal.org/project/autoweight) - [Content Reports](https://www.drupal.org/project/content_report) - [Contextual Elements](https://www.drupal.org/project/contextual_elements) - [DrupalOop](https://www.drupal.org/project/drupaloop) - [File Redirect](https://www.drupal.org/project/file_redirect) - [FileField Download](https://www.drupal.org/project/filefield_dl) - [Ghostery](https://www.drupal.org/project/ghostery) - [KC Tools](https://www.drupal.org/project/kc) - [KeyboardCowboy's Panels Demo](https://www.drupal.org/project/kcpd) - [... and more](https://www.drupal.org/u/keyboardcowboy) ### Projects - SyFy.com - Blastr.com - Cisco's Knowledge Base - Angie's List - Bravo TV - [IBM Investor Relations](https://www.lullabot.com/our-work/ibm-investor-relations) ### Education & Certifications - Certified ScrumMaster ## Featured resources ... [ More ... ](/resources) [### Behind the Screens with Matt Westgate Listen ](/podcasts/behind-screens/292-matt-westgate) [### Behind the Screens with Amitai Burstein Listen ](/podcasts/behind-screens/291-amitai-burstein) [### Behind the Screens with Cristina Chumillas Listen ](/podcasts/behind-screens/290-cristina-chumillas) ## Featured work ... [ See all our work ](/our-work) - [ ### Syfy Creating an Out of This World Experience for Science Fiction Fans SYFY ](/our-work/syfy) --- --- title: "Helena McCabe" url: "/about/helena-mccabe" type: bio date: 2015-06-23 updated: 2026-06-12 --- # Helena McCabe # Helena McCabe Technical Account Executive Orlando, FL - She - Her When she was 11 years old, Helena picked up a book on HTML at the library and started making websites for fun. After being paid primarily in pizza and other miscellaneous barters for a little over a decade, she turned her swapportunity-filled hobby into a career and started working in the web development field professionally. After nearly half a decade of writing code at Lullabot for interesting clients like MSNBC, Syfy, Bravo, IBM, and The GRAMMYs, she discovered that the only thing she loved more than Drupal’s code was its community. Itching to spend more time with the people who fill the Drupal landscape, she applied to join the sales and marketing team to dedicate herself to client interaction. Now that Helena has hung up her hat as Senior Front-end Developer, she works as a Technical Account Executive, bringing her technical experience to the sales department to play matchmaker between Lullabot’s clients and their needs and the services and people that Lullabot has to offer. As someone passionate about digital inclusion, she has spoken about web accessibility at many technical conferences domestically and internationally as an individual presenter, panelist, and a keynote speaker. When she's not behind a keyboard, Helena enjoys oil painting, traveling the world to scuba dive the beautiful ocean reefs, and playing Fallout. Helena graduated Magna Cum Laude from St. Petersburg College with a Bachelors degree in Information Technology Business Management. She calls sunny Orlando Florida home, where she lives very happily with her high school sweetheart, their two beautiful children, and an enormous Golden Retriever named Samwise. ## Did you know? Helena used to teach preschool! While she’ll always miss it a little bit, she does find that her clients in this industry have to be monitored far less closely for the ingestion of art supplies. ## Featured resources ... [ More Resources ](/resources) [### Web Accessibility: The Inclusive Way to Boost Your Bottom Line Read ](/articles/web-accessibility-the-inclusive-way-to-boost-your-bottom-line) ## Featured work ... [ See all our work ](/our-work) - [ ### Pantheon An Amazing, Fast New Site for an Amazingly Fast Hosting Platform Pantheon ](/our-work/pantheon) - [ ### Syfy Creating an Out of This World Experience for Science Fiction Fans SYFY ](/our-work/syfy) --- --- title: "John Hannah" url: "/about/john-hannah" type: bio date: 2015-06-23 updated: 2023-06-13 --- # John Hannah # John Hannah Senior JavaScript Developer Jacksonville, Florida John Hannah is a senior developer who specializes in JavaScript application development. He has worked on large-scale React projects, most notably for NBC and the George Lucas Educational Foundation. John has been working professionally as a developer since 2000. He has worked in higher education, at startups, design agencies and as an independent consultant. When he’s not building websites, you’ll find him spending time with his beautiful family in Florida. ## Featured resources ... [ More ... ](/resources) [### Why Choose React? Read ](/articles/why-choose-react) [### How to Learn React: A Five-Step Plan Read ](/articles/how-to-learn-react) [### Why I Chose to Use Flow for Static Type Checking My JavaScript Read ](/articles/flow-for-static-type-checking-javascript) --- --- title: "Heather Drummond" url: "/about/marc-drummond" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Heather Drummond # Heather Drummond Former Senior Front-end Developer Woodbury, MN Marc Drummond is a front-end web developer who built his first web page in the late '90s with Netscape Communicator. Since encountering Drupal in 2008, Marc has built and themed Drupal sites for a variety of organizations. He particularly enjoys building out complex component-based design systems and is well-versed in integrating Pattern Lab seamlessly with Drupal to keep front-end components in sync throughout the life of a project. Troubleshooting tricky CSS layout issues, animations and interactions is his jam, alongside diving into some PHP preprocessing or some JS to help a site to shine. His background in web development and graphic design for city government gives Marc a deep understanding of the challenges that .gov clients face. He provided many years of service to the board of the National Association of Government Web Professionals, serving as President of the organization during the last year of his leadership there. Marc leverages this background when working with large, complex projects, such as .gov and .edu website projects, and understands the administrative challenges that these types of organizations face. Marc has been a prolific Drupal contributor in his 10+ years within the Drupal community. In recent years, Marc has shifted the focus of his Drupal contributions from code to advocacy as a member of the Drupal Diversity and Inclusion Group. Marc has served on the Leadership Team for the group, and he led the Speaker Diversity Initiative in 2019. Marc is passionate about his Diversity and Inclusion work and strives to help the Drupal community become a more inclusive place that provides opportunities to individuals from underrepresented and marginalized groups. Marc lives in Woodbury, Minnesota, with his wife and daughter, as well as their dog. They enjoy traveling, especially to Disney World. In his spare time, Marc enjoys language learning, as well as working on health and well-being. He tries to regularly engage in mindfulness practices, and endeavors to walk a few 5Ks every year. ### Drupal Contributions: - Contributed front-end development and theming improvements, such as Twig and responsive images. - Co-maintainer of Drupal 8's Responsive Image and Breakpoint modules. ### Projects worked on at Lullabot: - [Georgia.gov](https://www.lullabot.com/our-work/govhub-building-georgias-digital-future) - The Recording Academy (The GRAMMYs) - [Pantheon](https://www.lullabot.com/our-work/pantheon) - Lucid Motors - [NYU School of Medicine](https://www.lullabot.com/our-work/nyu-school-of-medicine) - [Syfy Network](https://www.lullabot.com/our-work/syfy) ### Key accomplishments: - Led the front-end to plan and build a component-based design system viewable in Pattern Lab that is fully integrated with Drupal 8 for the State of Georgia’s 85+ sites. - Planned and built the React implementation for how the data would flow for the GRAMMY's Backstage—a live stream experience for sharing winner, videos, and posts from music's biggest night. - Created challenging CSS-based animations for Lucid Motors as part of their redesign. - Built an extensive component-based design system for NYU School of Medicine that required substantial flexibility for components to work at various widths. - Developed complex interactions and components for Syfy's menu systems and sidebars. ### Education and training: - BA in English and a concentration in Public Service, Albion College - Studied abroad at the University of Aberdeen in Scotland - Certificates in Web Design and Graphic Design, Minneapolis Community & Technical College ## Did you know? Marc studied at the University of Aberdeen in Scotland for a year during college. When visiting Castle Drummond, he asked if there was a reduced admission since he was a Drummond; he was told they'd be glad to charge him double. ## Featured resources ... [ More Resources ](/resources) [### Fundamentals of Responsive Images Read ](/articles/fundamentals-of-responsive-images) [### How to try out AMP with Drupal Read ](/articles/how-to-try-out-amp-with-drupal) [### A Tale of Two Base Themes in Drupal 8 core Read ](/articles/a-tale-of-two-base-themes-in-drupal-8-core) ## Featured work ... [ See all our work ](/our-work) - [ ### Pantheon An Amazing, Fast New Site for an Amazingly Fast Hosting Platform Pantheon ](/our-work/pantheon) - [ ### Google AMP-ing up Drupal Google ](/our-work/accelerated-mobile-pages) --- --- title: "Marissa Epstein" url: "/about/marissa-epstein" type: bio date: 2022-09-23 updated: 2025-03-10 --- # Marissa Epstein # Marissa Epstein Senior UX Strategist Providence, RI - She - Her Marissa Epstein, CPACC, has been drinking the design Kool-Aid ever since she was a nerdy art kid in sunny south Florida. Her work has run the gamut, from packaging brands like Wrigley and Kraft (Macaroni & Cheese) to creating thoughtful websites and experiences for lean startups, beefy publishers, and much in between. Marissa brings a passion for responsive design systems and obsessive attention to detail to every project. Before joining the Lullabot team, Marissa wore many hats as an Art Director for an interactive creative agency: ideation, marketing, branding, information architecture, UI/UX design, managing a design team, and project management. While at Lullabot, Marissa contributed to user experience, design, and content strategy projects for clients such as Georgia Public Broadcasting, Digital Services Georgia (Georgia.Gov), Harvard Library, Pantheon, and the Grammys. She approaches work from both a strategist and designer perspective, leading teams from facilitating discovery workshops to crafting accessible, reusable systems. She loves to plan and document team projects, from markers to interactive diagrams. Marissa is a seasoned speaker, writer, and certified accessibility professional. Marissa resides in a Cape Cod home in Providence, RI, with her humongous dog, Elwood. Besides embarking on adventures in her tiny city (probably in the pursuit of good food), Marissa can be found at design & strategy events, concerts, or reading non-fiction. ## Did you know? Marissa has a designer vinyl toy collection that has taken over her home. She gave up counting toys after collecting 400. ## More about Marissa ### Drupal Contributions: - [Breadcrumb Tweaks](https://www.drupal.org/project/breadcrumb_tweaks), credited on 1 issue - [DrupalCon Portland 2022](https://www.drupal.org/project/drupalcon_portland_2022), credited on 1 issue (Speaker) - [Drupal GovCon 2021](https://www.drupal.org/project/govcon), credited on 1 issue (Speaker) - [DrupalCon Global 2020](https://www.drupal.org/project/drupalcon_global), credited on 1 issue (Speaker) - DrupalCon Los Angeles 2015 Speaker - [Drupal Camp Asheville](https://www.drupal.org/project/dcavl), credited on 1 issue (2020 Speaker) - [Midwest Drupal Camp (MidCamp)](https://www.drupal.org/project/midcamp), credited on 1 issue (2020 Speaker) - New England Drupal Camp (NedCamp) 2019 Speaker ### Projects Worked on at Lullabot: - [Georgia Public Broadcasting](https://www.lullabot.com/our-work/georgia-public-broadcasting) - [Digital Services Georgia](https://www.lullabot.com/our-work/govhub-building-georgias-digital-future) - [AIChE](https://www.lullabot.com/our-work/aiche) - [Pantheon](https://www.lullabot.com/our-work/pantheon) - [Georgia.gov](https://www.lullabot.com/our-work/govhub-building-georgias-digital-future) - [Mass.gov ](https://www.lullabot.com/our-work/massgov-das) - The Michael J. Fox Foundation - [Pantheon](https://www.lullabot.com/our-work/pantheon) - The Recording Academy (Grammys) - NewsBank ### Education & Certifications: - Certified Professional in Accessibility Core Competencies (CPACC) - Bachelor of Science (with honors), University of Cincinnati (Major in Graphic Design, Minor in Psychology) - UX and Content Strategy for Drupal (Evolving Web certification) - Design Forward RI (Advanced certification: Design Strategy) ## Featured resources ... [ More ... ](/resources) [### It Depends: A Website Context Primer Read ](/articles/it-depends-website-context-primer) [### Orientation and Wayfinding: A Quick Overview Read ](/articles/orientation-and-wayfinding-quick-overview) [### Paper Prototyping for Websites and Digital Products Read ](/articles/why-we-advocate-paper-prototyping-digital-content) ## Featured work ... [ See all our work ](/our-work) - [ ### AIChE A Sustainable Design System for a Global Professional Association AIChE The Global Home of Chemical Engineers, logo ](/our-work/aiche) - [ ### Pantheon An Amazing, Fast New Site for an Amazingly Fast Hosting Platform Pantheon ](/our-work/pantheon) - [ ### Spredfast Showcasing ‘Smart Social’ for Technology's Biggest Event ](/our-work/spredfast) --- --- title: "Jessica Mokrzecki" url: "/about/jessica-mokrzecki" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Jessica Mokrzecki # Jessica Mokrzecki Former Senior Technical Project Manager Austin, TX Jessica's work experience lies in Graphic Design, Web Design, and Front-end Development. With these passions, plus her ability to organize and communicate, she moved into the Project Management realm. Jessica loves everything about the world of design and development, finding great joy in seeing an idea come to life on a website or application. Jessica was previously the Senior Manager of Creative Services at Volusion where she worked on all types of projects with SMB and Enterprise customers. Prior to that Jessica was a graphic designer at a small shop, where she learned web design by jumping in the deep end. Jessica has also worn the layout artist hat at an in-house advertising agency for a direct mail clothing company. Away from work, you will find Jessica hanging out with her husband and three little girls. She has recently discovered a love of running and just completed her seventh 5k within 8 months! She also loves practicing yoga and meditation, proclaiming it "the best way to wake up in the mornings." Jessica loves food and enjoys baking new treats with recipes following the Paleo diet. ## Did you know? Jessica has lived in 6 different states and 2 countries during her lifetime and has driven over 10,000 miles across the US making those moves. --- --- title: "Juan Olalla Olmo" url: "/about/juan-olalla-olmo" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Juan Olalla Olmo # Juan Olalla Olmo Former Senior Developer Granada, Spain Juan has been programming since 2002 and been having a blast doing it. As a boy scout, Juan learned to always work on things that “leave this world a little better than you found it.” Juan realized that open source software was one of those things when he discovered Drupal in 2010. He fell in love with the Drupal community while attending his first DrupalCamp and since then has gotten even more involved with the events, the community, and the code. Along with coding, Juan is extremely interested in personal development, motivation and human relationships. He has channeled this interest into presentations, like his "Pretendiendo ser Rockstar Developers” session [at DrupalCamp Spain](https://vimeo.com/218779085) which was one of the best attended of the conference. He presented it again as “Breaking the Myths of the Rockstar Developer” [at DrupalCon Vienna](https://events.drupal.org/vienna2017/sessions/breaking-myths-rockstar-developer) with wonderful feedback from the audience. Juan’s first source of learning is frequently reading books, articles and watching presentations. In his quest towards improving himself Juan is learning and practicing [Biodanza](https://en.wikipedia.org/wiki/Biodanza), Yoga, meditation and mindfulness exercises. He enjoys sharing what he's learned about self-trust, love, joy and inner peace both by presenting and also just having nice conversations with people in the hopes of being a coach and motivator to others. Juan and his family live in Granada in southern Spain--a beautiful city, brimming with rich history. In his free time Juan loves to walk through the city, hike in the mountains, play board games with friends or spend the day with his children on the beach. ## Featured work ... [ See all our work ](/our-work) - [ ### The NAMM Foundation A Purpose-Driven Redesign Celebrating Music Education ](/our-work/namm-foundation) --- --- title: "Daniel Dalgo" url: "/about/daniel-dalgo" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Daniel Dalgo # Daniel Dalgo Former Developer Quito, Ecuador As far back as he can remember, Daniel Dalgo has loved all things related to logic or mathematics. This love is what has led Daniel to view the programming world as a centerpiece. When Daniel was about 12, his dad brought home their first family computer. Daniel still fondly remembers the old IBM PS/1, 286 processor, monochromatic, where all his passions began. Years later, Daniel was introduced to PHP while attending University, then to Drupal in 2005 at his first job (version 4.7). Since then, his involvement in Drupal--and anything related to creating better environments in software development--has increased day after day. Daniel has been both developer and tech leader in different web projects, most of them related to the creation of social networks. He feels as though he’s had the luck to work with the right people at the right time. Daniel loves jogging with his wife, spending time with his kids and playing soccer. He’s always willing to help others and even though he may not talk a lot, you can be sure he’s listening. --- --- title: "William Hetherington" url: "/about/william-hetherington" type: bio date: 2015-06-23 updated: 2023-06-13 --- # William Hetherington # William Hetherington Former Trainer & Drupal Developer Canada Will Hetherington is a former Developer and Trainer at Lullabot. He brings seven years of media streaming experience, along with a solid background as a multi-OS system administrator, to match his seven years working with Drupal. Searching for his calling, he studied science in high school, and started a botany degree—and decided that wasn't for him. Around this same time, Will completed some Cisco certifications and began coding, which was more in line with his real interests of computers and networks. Since then he has been ever drawn by his curiosity into new technologies. He’s also worked to explain and share his discoveries with those around him, both his working colleagues and through community involvement. Will was born in Scotland and currently resides in Canada, after spending part of his childhood in the US. Will is married to Megan, and father to two young children Callum and Audrey. Through his parenting, he’s achieved a Masters in Superhero Informatics and learned all the words to Frozen. Will is an avid cyclist, gardener, cook, and a complete high fantasy nerd. He is also a lover of fine whiskey and will happily swap a dram for a good story any day. --- --- title: "Annalise Kaylor" url: "/about/annalise-kaylor" type: bio date: 2015-06-23 updated: 2023-06-13 --- # Annalise Kaylor # Annalise Kaylor Former Head of Marketing Georgia Annalise built her first social network in 1992, back when they were called bulletin board systems. Fast forward 23 years, and she’s the Head of Marketing for Lullabot. With a diverse background in digital marketing, Annalise began her career in community management, first in forums, then later as the first global community manager for Estee Lauder Companies. Moving to the agency side, Annalise joined Intrapromote, a fully distributed boutique SEO and social media agency as their Director of Social Media. While there, Annalise revamped the social media practice, and launched offices in London and Singapore to accommodate their growing international clientele. Later, she joined Atlanta-based Moxie as a senior digital strategist, working on social media strategy and leading enterprise-level digital initiatives for a varied client roster, including Nike, UPS, Cisco, Verizon, Wells Fargo and a handful of brands under the Coca-Cola umbrella. Annalise is a speaker at a wide variety of industry events including SXSWi, SMX Social, SMX Advanced, Digital Summit, and Pubcon. She was named one of the Top 25 Women in Social Media by Top Rank Blog, and has also been featured in Mashable, CIO.com, Search Engine Journal, and other industry press. Her specialities are SEO, organic and paid social, and big data for content marketing strategy. Annalise is based in Atlanta, Georgia, where she lives with her boyfriend Bill and their chihuahua-pug (chug), Frank. In her spare time, she is a concert photographer, shooting shows on behalf of bands and publications all over the world. Her portfolio includes Elton John, Jack White, Emmylou Harris, The Strokes, The Avett Brothers, Rhett Miller, Florida-Georgia Line, J Cole, and dozens more. ## Did you know? Annalise received her private pilot's license in 2012, and is probably the only person on a commercial flight who secretly hopes they need help in the cockpit at some point. Her plane of choice is the Cessna 172. --- --- title: "Between Releases: When Should I Adopt the Newest Version of Drupal?" url: "/articles/when-should-i-adopt-the-newest-version-of-drupal" type: article date: 2015-09-02 updated: 2015-09-04 --- # Between Releases: When Should I Adopt the Newest Version of Drupal? # Between Releases: When Should I Adopt the Newest Version of Drupal? Karen Stevenson, who originally authored the CCK and Date modules, weighs in on when to adopt the newest version of Drupal and when not to...especially in times of transition between two versions. By [ Karen Stevenson ](/about/karen-stevenson) September 2, 2015 Watching the Drupal release cycle ebb and flow reminds me of sitting on the beach as the waves roll in. There is Drupal 5! It’s getting closer and closer! Finally it crashes on the beach in a splash of glory. But immediately, and initially imperceptibly, it starts to recede, making way for Drupal 6. And so the cycle goes, Drupal 5 recedes and Drupal 6 rushes in. Drupal 6 is overcome by Drupal 7. And now, as I write this, we’re watching as Drupal 7 washes back and Drupal 8 towers over the beach. Each new version of Drupal is a huge improvement on the one before. But each version also introduces uncertainties. Is all that new functionality necessary? Has Drupal core become ‘bloated’? Does it do too much (or too little)? Will it be performant? How much work will it take to implement? Is it still buggy? And, arguably, the most important question of all, when will the contributed modules we need catch up? So when is Drupal “ready” for our clients? If clients want a new site in this between-releases period, do we build it on the solid, safe, predictable older release? Or jump in with the shiny, new, improved release that is just over the horizon, or just released? Or do we wait for the new version to mature further and delay building a new site until it’s ready? We’ve dealt with these questions over and over through the years. Knowing when to embrace and build on a new major release requires careful consideration along several axis, and making the right decision can be the difference between success and failure.Here are the guidelines I use. ## How Complex is the Site? If the site is simple and can be built primarily with Drupal Core, than the shiny new version is likely a safe bet. Contributed modules may add nice features, but creating the site without many (or any) of them will mitigate your risk. Each new Drupal release pulls into core some functionality that was previously only possible using contributed modules. Drupal 5 allowed you to create custom content types in the UI. Drupal 7 added custom fields to core. Drupal 8 brings Views into core. And every Drupal release makes some contributed modules obsolete. If the new core functionality is a good match for what the site needs, we’ll be able to build a new site without using (and waiting for) those contributed modules, which would be a good reason to build out on the frontier. Correspondingly, if the site requires many contributed modules that are not included in core, we’ll have to wait for, and perhaps help port, those modules before we can use the new version. If we can’t wait or can’t help we may have no choice but to use the older version, or wait until contributed modules catch up. ## How Tight is the Deadline? It will probably take longer to build a site on a new version of Drupal that everyone is still getting familiar with than an older version that is well understood. It always takes a little longer to do things when using new processes as when repeating patterns you’ve used many times before. Delays will also be introduced while waiting for related functionality to be ready. Perhaps there is a contributed module that solves a problem, but it hasn’t been ported yet, so we have to stop and help port it. Or there may not be any contributed module that does anything close to what we need, requiring us to plan and write custom code to solve the problem. Latent bugs in the code may emerge only when real world sites start to use the platform, and we might have to take time to help fix them. In contrast, if we’re using the mature version of Drupal, odds are good that the bugs have been uncovered and there is code somewhere to do pretty much anything that needs to be done. It might be a contributed module, or a post with examples of how others solved the problem, or a gist or sandbox somewhere. Whatever the problem, someone somewhere probably has already run into it. And that code will either solve the problem, or at least provide a foundation for a custom solution, meaning less custom code. Basically, if the deadline is a key consideration, stick with the tried and true, mature version of Drupal. There just may not be enough time to fix bugs and create custom code or port contributed modules. ## How Flexible is the Budget? This is a corollary to the previous question. For all the same reasons that a deadline might be missed, the budget may be affected. It takes more time (and money) to write custom code (or stop and port related contributed modules). So again, if budget is tight and inflexible, it might be a bad decision to roll out a site on a shiny new version of Drupal. ## How Flexible is the Scope? If we use the latest, greatest, version of Drupal, is the scope flexible enough to allow us to leverage the way the new code works out of the box? If not, if the requirements of the new site force us to bend Drupal to our will, no matter what, it will require custom code. If we build on a more mature version of Drupal we may have more existing modules and code examples to rely on for that custom functionality. If we build on the bleeding edge, we’ll be much more on our own. ## Where is the Data Coming From? If this is a new, from-scratch site, and there’s no need to migrate old data in, that would be a good use case for building this shiny new site with the latest, greatest version of Drupal. But if there is an existing site, and we need to not only create a new site, but also migrate data from the old site to the new, the question of which version to use gets more complicated. If the source is another, older Drupal site, there will (eventually) be a supported method to get data from the old site to the new site. Even so, that may not be fully ready when the new version of Drupal is released. Drupal 8 uses Migrate module for data migration, but only the Drupal 6 to Drupal 8 migration path is complete, and that migration process will likely improve in future point releases. The upgrade path in previous versions of Drupal was often fraught with problems early on. It's something that never gets fully baked until the new version is in use and the upgrade process is tested over and over with complex, real-world sites. So the need to migrate data is another reason to use the older, more mature version of Drupal (or to wait until the new release is more mature). ## How Important Is the New Hotness? Every version of Drupal has a few things that just weren’t possible in previous versions. CMI (Configuration Management) in Drupal 8 provides a much more rational process for deploying code and configuration changes than Drupal 7 does. Drupal 7 requires banging your head against the limitations of the Features module, which in turn is hampered by the fact that Drupal 7 core just isn’t architected in a way that makes this easy. And Drupal 8 core has built-in support for functionality previously only possible by using one or more additional Services modules in Drupal 7. If these new features are critical features, and if struggling to solve them in older versions has been a time sink or requires complex contributed modules, it makes sense to dive into the latest greatest version that has this new functionality built in. ## How Long Should It Last? A final question is how often the site gets re-built. If it is likely to be redesigned and re-architected every two or three years to keep it fresh, there should be little concern about rolling out on the older, mature version of Drupal. Drupal 7 will be supported until Drupal 9 is released, and that is likely to be a long time in the future. If it will be many years before there will be budget to re-build this site that might be a reason to build it on the latest version, delaying the project if necessary until the latest version is fully supported by contributed modules and potential problems have been worked out. ## It’s Complicated! The ideas above are just part of the thought process we go through in evaluating when to use which version of Drupal. It’s often a complex question with no black and white answers. But I take pride in our ability to use our long experience with Drupal to help clients determine the best path forward in these between-release periods. Published in: - [ Business ](/topics/business) - [ 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: "The sites, they are a'changin'" url: "/articles/the-sites-they-are-achangin" type: article date: 2007-06-27 updated: 2014-03-13 --- # The sites, they are a'changin' # The sites, they are a'changin' By [ Jeff Eaton ](/about/jeff-eaton) June 27, 2007 The past several weeks have been pretty busy, hopping around with various clients and helping them iron out architectural issues in the early plans for their sites. With each new site project we work on, it seems like all of the Lullabots have been watching the 'hot spots' of work move from pure code (writing custom modules, etc.) to architecture and configuration. Drupal has reached a point where 80-90% of the functionality of most sites can be built using nothing but Drupal core, CCK, Views, and a few dozen third-party modules. That impressive stat is made possible by Drupal's architecture, and the steadily increasing quality of many key modules in the contrib section. Unfortunately, it also means that Drupal is currently in a scary 'gray zone' between roll-your-own and plug-and-play. In the dark ages of Drupal 4.6, and even 4.7, if you wanted your site to offer a news section, a free job posting section, and a photo gallery, the answer was simple if sometimes frustrating. You installed image.module, you hunted around in contrib for a Classified Ads module, and you used something like Taxonomy to organize story nodes into a news section. There were rough edges, of course. If you wanted to do something slightly different -- say, listing the location of a particular job when the Classified Ads module didn't support it -- you had to dive right into the code and start hacking. Photo galleries grouped by month rather than taxonomy term? Same issue. Functionality for sites was implemented by relatively 'monolithic' modules that provided custom content types, pages to display and organize them, etc. in one package. If you needed something that worked differently, you hacked one of those modules or you rolled your own from scratch. Today, the situation is quite a bit different. While those kinds of modules still exist, much more emphasis is put on designing your own content types from scratch using CCK and a bag full of Field Type plugins. Then, you roll your own listing pages and filtered RSS feeds using Views module. In almost all cases, the resulting functionality is the same or better, and you can tailor the data model and presentation to your site's specific needs. It makes it quite a bit easier to build your site just so without re-inventing the wheel. The trade-off, though, is increased complexity for the folks who don't need that high level of customization. If all you want to do is add a browsable gallery to your blog, installing CCK, Views, Imagecache, Imagefield, Views\_Grid, and so on, then configuring each of them to work together an produce your Perfect Image Gallery... Well, that's a daunting task. Building a wiki in Drupal is similar -- you combine six or seven modules, configure them to taste, and voila! Wiki! To do that, though, you need to figure out how they all work together and what the correct 'recipe' is. As Drupal grows, and this trend towards LEGO-block style customization continues to dominate, a new class of modules to mange these configuration tasks will have to emerge. We've built a few modules like this for our clients - the much-discussed [Chris Daughtry](https://daughtryofficial.com/) site, for example, uses a custom 'Photo Gallery' module to manage some of the settings for Views, CCK, Imagecache, and so on that produce the site's browsable galleries. It doesn't implement much on its own, but it encapsulates some of the more complex configuration code. Because the module was designed specifically for that site's needs, it makes a lot of assumptions about the environment it'll be running in and doesn't bother with features we knew wouldn't be needed. It's unlikely that that specific module will ever see a public release, but that general approach shows a lot of promise. As we all scurry around building sites with the new tweakable tools, we need to identify ways that our custom solutions can be bundled up and shared with the rest of the community, too... What challenges have you run into with this new 'more granular' approach to site building with Drupal? What solutions have you found? Sound off, folks. It's brainstorming time! 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: "In The Year 2008" url: "/articles/in-the-year-2008" type: article date: 2008-01-04 updated: 2014-03-16 --- # In The Year 2008 # In The Year 2008 By [ Jeff Eaton ](/about/jeff-eaton) January 4, 2008 Everyone's chiming in on the [2008 predictions thread](http://drupal.org/node/204454) at drupal.org, and I can't ignore it, either. A couple of folks have asked for clarification on some of the comments I made, so I figure it's time for another blog post! Obviously, these are my opinions; any differing ones are welcome. **By May, large-scale Drupal 6 sites will begin launching.** Drupal 6 looks like it's cruising towards a release in mid to late January. A lot of really critical modules, though -- the stuff that large and complex Drupal sites depend on -- are not yet finished. Until Views, CCK, OG, the ECommerce package (or Ubercart), and a number of others are ready, Drupal will be awesome but 'not yet ready for production.' It probably won't take long -- Earl Miles, or example, has said that he'll be freed up to concentrate on the Views port shortly, and the CCK port is very nearly complete. The sites themselves have production cycles too, though, and the release of Drupal 6 is when building them *begins*. **As more large-scale sites roll out using Drupal, the community's collection of high-performance scaling techniques for Drupal will grow, and become common knowledge rather than black arts. Memcached and complex database configurations will receive even greater attention.** This one's pretty straightforward, I think. In every one of the sites we've worked with in 2007, high traffic and scalability were priorities. Tools like memcached, and D6's improvements for authenticated users, are both big boosts. It's still ugly to do certain things, like master/slave DB setups, and I think that will be likely to change as drupal.org grows and Acquia targets enterprise-level customer. **A greater percentage of the community will focus on user experience issues, and the value of our long-standing emphasis on progressive enhancement will become more and more apparent. Adding UI 'sugar', reworking troublesome portions of the interface, and improving fundamental workflows will all be easier because we have resisted the temptation to tightly couple client-side script code to server-side processing.** I'm a big believer in the idea of layered design for a platform like Drupal. Core functionality provides a shared set of consistent building blocks for modules, like the hook system, nodes, comments, users, menus, and so on. API modules like Views provide a layer of consistent, predictable functionality that other modules can build on top of, and FormAPI based screens can be decorated and enhanced with a 'layer' of client-side script that degrades gracefully. Strip away any layer and you still have something perfectly useful underneath. That last part is important -- the temptation to take shortcuts, hard-wiring our system to make certain tasks easier but sacrificing the clean divisions between different subsystems -- will only grow. **The groups using Drupal will diversify even further. Large-scale corporate site building, web-app design, hobbyist blogging, and home-grown community management are wildly different domains with different needs. Attempts to compare Drupal to Rails and other frameworks will further muddy the waters. One of the greatest challenges for Drupal in the coming year will be maintaining a coherent and focused project with a clear vision.** This one stands on its own, I think. It's not a bad thing, just a reminder that Drupal is getting a lot of attention -- loads more than many of us imagined when we joined the project! The pulls and tugs in different directions aren't bad, but figuring out how to balance the needs will be an important task. **Projects to rewrite several major core APIs will be launched, and at least one will stall and ultimately be shelved or delayed for the indefinite future -- much disappointment and doomsaying will result.** This isn't a pessimistic statement, more an inevitable one I think. As systems grow, and age, and evolve, refactoring is essential. However, as Drupal grows and expands, it becomes harder to change many of its components without affecting many others. Rewriting page rendering touches the node system, for example, and that's an impressively complex ball of yarn. In some ways, the dreams of the community for Drupal's code currently exceeds its reach: we have lots of developers, but still not enough hardcore Drupal-hackers on deck to affect all of the major changes being discussed in a realistic timeframe. The experience will be good for us: just because we can't do everything doesn't mean we can't do the top priorities *well*! **The community will begin to realize (in part due to the above stalled project) how much [design debt](http://c2.com/cgi/wiki?DesignDebt) Drupal has accrued over the past several years of rapid expansion and development. While the problem will not be solved in 2008, the realization that it IS a problem will help prevent a long-term meltdown.** Design Debt is something that software accumulates as it's written -- as it's extended and evolved. It's not a bad thing, necessarily -- it's an inevitable part of any changing code base. The only way to 'repay' it, though, is to re-examine the system and the decisions made in previous iterations and rework them with the evolving goals in mind. (i.e., refactoring) In a lot of ways, the Drupal community is good about that with its "the drop is always moving" philosophy. But the last several years have seen unprecedented growth, dramatic enhancements to Drupal's functionality, and a huge blossoming of APIs and architectural tools in the contrib repositories. It's easy, now, to hit points where enhancing things by patching or enhancing or hook\_something\_alter()ing is a lot more work than just starting over from scratch on some component. In many instances, that's design debt rearing its head. Recognizing it and taking the time necessary to rethink fundamentals is always worth it. **The non-UI portions of Views will be integrated into Drupal core, and a round of deep structural changes will begin as core pages are rewritten to use it. In the long term, this will be more significant and useful than the shelved API rewrite.** OK, I confess. This is what I'd like to happen alongside the previous 'design debt' issue. Grafting Views into core, and leaving it at that, would satisfy a lot of people and add some great features. However, without refactoring other parts of core, we'd have lots of unnecessary code lying around bulking up core and building Views-like pages without actually using Views. The front page listing of latest nodes? The Tracker module? The listing of content at admin/content? The listing of users at admin/user? RSS feeds generated by Taxonomy module? All of those are doing limited versions of what Views module does. That kind of cruftiness is one kind of design debt: the results of things expanding slowly and steadily without being refactored. if Views goes into core we need to invest the time necessary to build those tools on top of it wherever possible. That kind of redesign of Drupal's internals "pays off" some of our design debt, and puts us in a better position to grow in the future. 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: "Down with OPP, Part II" url: "/articles/down-with-opp-part-ii" type: article date: 2006-06-15 updated: 2014-03-17 --- # Down with OPP, Part II # Down with OPP, Part II By [ Jeff Robbins ](/about/jeff-robbins) June 15, 2006 Another podcast interview with yours truly. [This one](https://twit.tv/itn25) by Amber MacArthur and Leo Laporte for **Inside the Net**. We talk about Drupal, Lullabot, the new TWiT site, and all kinds of exciting things! Direct link to the mp3 file is [here](http://aolradio.podcast.aol.com/insidethenet/ITN-025.mp3). 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: The Future of Fields" url: "/articles/drupalcon-the-future-of-fields" type: article date: 2008-03-04 updated: 2016-04-07 --- # Drupalcon: The Future of Fields # Drupalcon: The Future of Fields CCK in core By [ Lullabot ](/about/lullabot) March 4, 2008 The Future of Fields [A group of Drupal developers](http://groups.drupal.org/data-architecture-design-sprint) recently met in Chicago in order to make Drupal more flexible when it comes to dealing with external data sources. A driving factor was the desire to be able to have internal access to external data sources without having to import huges amounts of data into the database. They also wanted to treat external data as Drupal nodes, for example, being able to reference any Flickr photo and vote on it with the Fivestar module without having to download and store every single photo. The biggest conclusion that the design sprint participants came away with was to focus on getting more of CCK into core, specifically helping make fields first-class data objects. More on some of their insights and open questions were discussed in the Future of Fields session. \[Begins showing a picture of the Data Architecture design Sprint including: chx, yched, bjaspan, crell, karens and nedjo\] Drupalcon used to be a developer conference where they would meet and figure out what was going to happen for the next year, but in Barcelona they realized that it was harder to do that. So they asked about the who are the right people to be involved, and they all flew into Chicago and did a sprint. Barry would advocate this be a trend in future Drupal development, get together a targeted group to make some progress. The DADS (Data Architecture Design Sprint Re-design of Drupal's core date architecture \* Data API \* Object modeling - node object is a dumping ground for stuff. Some is in arrays, some are in classes, some are fake classes. \* Fields in core -- What does that mean? CCK in core. Once the node object is cleaned up, let's clean up how it's rendered. Then realized that started getting really big, and started to focus on getting fields into core, and then things would go from there. \* Many related ideas Their goal was to propose a design at Drupalcon Our "Grand Conclusions" Some key thoughts: What makes Drupal unique? Drupal has the hook-based system Drupal's architecture allows contributed modules to easily add value to content Everything from fivestar to views -- and that there are so many of these modules, and that the fact there are so many of these modules is what makes Drupal unique. The Future Web services -- sharing data from multiple sources \* Consuming - Need to import in Amazon data or RDF \* Providing access to our data -- no way at the moment to export data via JSON without custom coding. \* Enhancing -- Being able to do any Drupal action on fields that is currently limited to nodes Drupal is all about our contributed modules, we need to be able to provide our secret sauce throughout Drupal. Otherwise we're just sucking and aggregating other people's data, and not leveraging Drupal's power and flexibility. So we want to add Drupal's value to the data sent out to web services. If we take photo from a Flickr, it shouldn't have to know it's a node to be able to vote on it. And putting CCK into core, that's a big step towards that. \[Showing KarenS flowchart\] Drupal takes in data, formats and saves data, and produces HTML (and XML, etc) -- The little triangle at the bottom are the Drupal hooks that holds Drupal together. If getting fields into Core is where we want to go, then Karen will talk more about it. KAREN STEVENSON: We all know that getting fields into core is the big issue that we want to do for D7. So why bother? The way CCK handles fields is what we want to do, and it's a good way of looking at the issue. At the usability testing at UMN, people were confused that Title and Body were treated as fields -- even though they don't act the same way as fields. The reason why they behave differently is that they're not in core. One way to consistency is to get fields into core in order to have consistency. In a way, we have developed something better for users than developers. Users can create fields on the fly, but developers need to be able to do that as well. So that's also a motivation to get CCK fields into core. Hopefully people are familiar with CCK. "Field type" is how is it stored, and what is unique about it. A field type tells you how it is stored in the database. The "field" is really the settings, how we set up the field, the length or what type of field. "Field instance" is how bind a field to a content type. The instance also have settings. It's confusing terminology, but that's the way CCK works now. Let's take a look at existing things in core to see what types of things could be fields. Body: text area with a teaser splitter. It's a two-field, teaser area and body area. Created: date field Upload: file field User picture: image field IDs: Number + optionwidgets Taxonomy feels like a field -- but taxonomy has so much special processing, and will be a challenging thing Comments? Fields? Or are they data? They're not sure yet. Several of these things are not even in core CCK like date, file or image field. So it's a double promotion from not existing in contrib to not existing in core. And the reason why they're not in CCK is because they're not simple fields, and all of those things should make it in core CCK. When we do a split between -- no one is talking about taking ALL of CCK and putting into core. That's too much, and would be too much complication What is the Minimum Viable Possibility of how much should of CCK should go into core D7 and what should stay in contrib: Core \* Field API \* Field Storage Engine \* Node CRUD \* WIdgets \* Forms \* Formatters \* Multiple values -- handle them as a whole or as a separate things: like a GMAP -- "Add more" -- Core now has add more button -- Custom \* FIeld validation Contrib \* Field UI -- that'll have to stay in contrib, because it's messy and hard -- Add, manage, diplay tabs \* Fieldgroups \* Allowed & default values -- probably more complicated than we need to go \* Content Copy \* Module integration -- Views, token, pathauto The core module would be called "field.module" and we continue to have a contrib module. Field storage options CCK does dynamic storage of fields, it is done in a per content type, and a multiple content is put into a 'per field' If you share a field between two content types, CCK has to create a new field. Any time you change parameters, you have to alter the schema, and move the data from one table to another one, and potentially loose some data Lots of places where it can go wrong and where data could get lost, and it's worse now because of CCK has a dynamically-defined schema. Store the field information in a field table, and whenever there is a call to determine the schema Ran into race conditions -- if you want to actually change the schema. And the big concern is to get all of this into core without breaking everything and making it a maintaining nightmare. So fixed 'per tables' tables with cached node array \* Simple structure and code \* Downside is that there is a performance hit from numerous sql JOINS One solution is to combine 'per field' tables and intermediate tables better optimized for query \* Duplication of data, yields a larger database They are doing a separate cache for the node object,so that they're not doing the joining as often -- which works fine for node\_load, but we're back to lots of JOINS with views. And currently Views is already doing the JOINS, but they've been going around and round -- it's been a show stopper without figuring this out. Their preliminary conclusion os that simple fields seems to make sense. It gives simple code that's predictable, and let's find out if these JOINS are an issue with performance tests. They imagine coding it up and implementing it, and then seeing if there are killer performance issues. If so, then scrap it and try another approach. The Field API There is a basic API in CCK D6. Having the ability to have modules to create fields creates some problems. \* Should the fields be locked down and not be able to be altered? \* Who is going to own the fields? \* Are they available in the UI? \* Should those fields be shared? A lot of these issues and more need to be worked through In CCK for D6, they are working on how to get some field-based permissions, and so it may also be a Phase II issue for getting these permissions fields into D7 after a big chunk of functionality has been committed in the Phase I. If they had permissions for fields, then modules could do more things with fields. Jeff Eaton has been in discussion with KarenS, and they've agreed that it's better to start with them as being locked down permissions-wise, and then gradually move towards allowing people to have permissions on them. Possible Roadmap \* Can start the process as separating the API from the UI and create a new fields.module in D6 with the idea to move it into D7. \* It'll have to include the basic fields (text, number, optionwidgets) \* Rework existing core fields and transition them into fields.module -- they think it makes sense to start with the Body field. \* Determine what else needs to be done for date , image, file handling etc. \* If they can get something into D7 early in the process, and then possibly get some of the UI into core. QUESTION: Would Page and Story define in the CCK way instead of the way they are? Possibly. The revisions table could go away because the body would become the revisions table (and CCK currently handles revisions on the field level) QUESTION: If fields get into core, then instead of Webform would we now just use the core field.module? Not sure, a lot of things could happen. QUESTION: What about the Image field -- Possibly grouping fields for the alt tags, etc. to have the same table? -- And a fourth option, not dynamically from the UI, but that there is a programatic way to have a hook that says that these fields will always stay together. You could keep consistency in the schema API The API will be able to define how fields will be grouped together. LARRY GARFIELD gets up to talk about Local Fields on Remote Content One challenge: If you want Drupal to talk to foreign data, then it's really, really hard. What do we mean for data sources? \* local nodes \* Entire Amazon catalog \* Flickr photos \* legacy data -- accessible via SOAP, XML, etc. \* Other content from Drupal sites For any given piece of content you want to perform the any of the following operations: \* Display locally \* Comment \* Vote \* Search \* Views These operations don't work for non-local data. Importing ALL of that data can be really bad, because you have huge amounts of data -- and even if it was possible, then keeping it in sync could be really crazy. Lazy node creation also isn't much better. But with everything we've talked about so far -- we don't get anything new and exciting. So what would this enable that we could really get excited about? The Data Architecture Design Spring participants talked about "Institute of Contempory Art" as a case study -- coincidentally is very similar project to what Palatir was working on. \* Data in legacy database that was available via SOAP, and that they didn't want to import b/c needed access via another non-Drupal CMS. \* They had artwork (title, year, image ULR) artist (name birthplace, bio), resources (video, audio, etc) \* Had to figure out how to make all of this available to Drupal. \* The goal is to have the http://example.com/artwork/abc -- and it's not a node in the database, but they want to be able to treat it as a node. So what does it mean to be a node? It has a nid, and some simple properties like created, status, and simple metadata, and then they have the Fields, which are the meat of the content -- title body, comments, taxonomy, CCK fields, and other complex structured data attached on by other modules. So how can you provide a unified structure for the Node and the ICA: "Thingy"? \[Thingy is a temporary name decided in Barelona that won't be called a thingy when it gets into core.\] It has a unique ID: that has an opaque string that has properties (i.e. metadata), and it has fields -- which could be intrinsic to the title or body, as well as extrinsic fields that come from the Drupal database. Anything else that is used that is pulled in doesn't need to be known where it's coming from remotely -- loading the object we shouldn't care where the external data is coming from. They want to have a clear separation between the data source and the data interface. How do you add a field to a thingy so that you can add Fivestar.module and add some votes? Instead of a single column for a nid, you have two columns, you have the the type of nid (node, artwork, artwork), id (12, abc \[menu path on the file system\], abc), Delta column (0,0,1) and then the Vote (2,5,4) The artwork "abc" does not exist anywhere exist anywhere else in SQL other than this vote column. The Delta column is a CCK-specific value that allows for multiple values for CCK fields. QUESTION: What if you have a combination of some images from Flickr and some locally Having a given field with having some values come from some data sources would be helpful, but would be really difficult to implement. The heavy lifting is on the field level. QUESTION: What if the remote data is deleted, how do you sync? We don't know yet, that's yet to be determined, and part of the problem with dealing with external and stale data. The thingy called "artwork" is equivalent to what we currently call a "node." Artwork would be a class, and they we load the object with "abc" to pull it in from the remote system, and the only thing stored locally is the Drupal value-data like a fivestar vote. QUESTION: Works well from files or images, but what about text strings and caching? It would have to be exposed in some form that you can query. But depends on your use case. This group left some of this discussion out of the presentation, and have more info in their group on [g.d.o.](http://groups.drupal.org/data-architecture-design-sprint) What about searching these? Can't search local database AND remote SOAP within one SQL request -- possibly with SPARQL, but will probably still have to do a separate query for each data source and merge results. Views -- probably does not work as we understand it today because Views is tied to a local SQL database. We haven't figured it out yet -- possibly lazy load SQL. Will probably have to just implement it and see what works and what doesn't work. The Views query-builder will live on for local data, but the rest of views is unclear for how it will evolve or adapt for dealing with remote data. NEDJO ROGERS: Brief summary of these ideas and proposals, and we need to hear back and have follow-up discussions for how much this makes sense. They will have to fundamentally rework how core works with data with nodes and users. We also have some assumptions in Drupal that are being challenged: \* All data is entered into the dB via forms for users \* Also anything can be extended at any time. \* All data resides in a local SQL database CCK knows what it's object looks like, we don't have to guess b/c it's aware of it's schema. It's sort of representing the type of data API we're looking for. Let's do it, but let's do it right. By renewing our core Data API so that we can free our Data API from these assumptions. Some of these things can be taken care of now with CCK for Drupal 6. Looking at a minimal implementation of CCK for Drupal 7, and where we take it is an open question. They'd like to hear back any questions and comments, does it make sense to people? Does it ring true as a future direction for Drupal, and use the CCK fields as a broader goal for some of these things moving forward? None of this is going to happen unless someone takes ownership that is going to get involved and help make it happen. If you want to help, and don't know how, then write unit tests so that it will extend the development cycle. Much more info on Daily reports, Final Report, Proposed Content Models 1 and 2, and more ramblings: http://groups.drupal.org/data-architecture-design-sprint Replace the concept of nodes with objects that can be uniquely identified -- how we do it: whether it's something on top of the node vs. something else, then take a look at [Model 1 and 2.](http://groups.drupal.org/data-architecture-design-sprint) QUESTION: Will the data structure that is abstract and flexible enough to have a choice of fully normalized vs. de-normalized with no joins. We don't know yet. So when we think about how can we attach a comment to an external Flickr photo, there is a choice between whether the field table knows what the actual data looks like or another option is something actively being discussed. They do have adding fields to fields in D6. QUESTION: Stale data problem, and data unavailable. Might be implementation specific: dependent on the field on whether it's a Flickr photo or for the museum. But what you say generally is "data unavailable" QUESTION: How would node revisions work if KarenS is saying that using the field.module for the body would get rid of node revisions. Do revisions go away? What about the diff.module? Every CCK field keeps it's own revisions, and so she's is throwing that out, because CCK has something very similar already built in for each field. QUESTION: Also think about the possibility of storing the revisions as a diff, so that you have a whole version control system built in internally. QUESTION: Why not just use XML, and then think as thingies as 'entities' and fields are 'attributes'. It's good for some tasks, but it's not really a good mechanism to using it internally. XML aren't multi-value. You could easily map the thingy structure to XML if you want to export. 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: "Drupalcon Szeged, Day 2" url: "/articles/drupalcon-szeged-day-2" type: article date: 2008-08-28 updated: 2016-04-07 --- # Drupalcon Szeged, Day 2 # Drupalcon Szeged, Day 2 DrupalCon Europe 2008 Recap By [ Angie Byron ](/about/angie-byron) August 28, 2008 Day 2 started out with the [Awesome Testing Party](http://szeged2008.drupalcon.org/program/sessions/testing-part-2-awesome-testing-party), which consisted of a room full of people in pairs learning how to write automated tests. I had hoped for at least 10-20 people there. There were 80+ (!) and together we enjoyed delicious Hungarian pancakes and chocolate. I still have yet to thoroughly review the results, but as of this moment there are 24 of the testing issues bumped, several with patches that need review, and one that even got committed during the session! :) I didn't make it to the [Doc Sprint Planning BoF](http://szeged2008.drupalcon.org/program/sessions/doc-sprint-planning), but Addi tells me that we had some new faces there, and some great ideas for tasks during the Doc Sprint on Sunday. Should be a lot of fun! :) Later in the day was the [Drupal.org Redesign Panel](http://szeged2008.drupalcon.org/program/sessions/redesign-drupalorg-designers), where Mark Boulton and Leisa Reichelt from [Mark Boulton Design](https://dezignz.org/) discussed their strategy and initial findings for the redesign of Drupal.org. It was a fantastic presentation and I think everyone left feeling really excited about things to come. Leisa shared some findings of her initial user research, which included tidbits such as Drupal.org users compensating for the IA problems by navigating the site by typing in URLs manually, which I totally do all the time. ;) Following that, I headed to the [Google Open Source Highlights](http://szeged2008.drupalcon.org/program/sessions/google-and-open-source-highlights-gsoc-and-ghop) presentation by Leslie Hawthorn. Some interesting statistics were revealed about the success of the Summer of Code and GHOP programs, as well as a few success stories, several of them from right here in the Drupal community. :) After that, Charlie Gordon, Leslie Hawthorne, Daniel Wehner and I co-presented the [The DROP Program](http://szeged2008.drupalcon.org/program/sessions/contributing-drupal-drop-program) session, talking about [DROP](https://drop.cwgordon.com/), an initiative to "bubble up" low-hanging fruit tasks in the Drupal community for new contributors to work on. There was some really awesome discussion from the audience about whether point-based karma systems are useful, and how to launch a person from a participant to more of a "mentor" role. Thanks to all who turned up! 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: "Hey! A Lullabot.com Redesign" url: "/articles/hey-a-lullabotcom-redesign" type: article date: 2013-05-02 updated: 2014-04-24 --- # Hey! A Lullabot.com Redesign # Hey! A Lullabot.com Redesign Redesigned, reworked, refined, and rebranded By [ Jeff Robbins ](/about/jeff-robbins) May 2, 2013 Keen Lullabot fans may have noticed that we launched a redesign of Lullabot.com a few weeks ago. It's not just a redesign, but also a re-platforming to Drupal 7, a reworking of the site structure and functionality, and to some extent, a rebranding of Lullabot. A lot has changed in the 3 years since we launched the previous site. Back then, we were a 12-person consulting-focused company with all of the work we could handle. Most of our work came from word-of-mouth. We didn't have a sales person and we didn't want a website which would make the phone ring more than it already was. Instead, we focused our website on our growing education department, offering customized on-site training and public workshops – something that we could scale better than consulting. Since our last redesign, so much has changed: 1. The economic collapse caused us to figure out ways that we could offer more inexpensive Drupal training. This lead to our DVD/download video training, and eventually to our subscription training site: [Drupalize.Me](https://drupalize.me/). Still part of Lullabot, the site currently employs a full-time staff of 5 cranking out videos weekly on a wide variety of Drupal-related topics. 2. The thriving Drupal community began to offer cheaper in-person training often in conjunction with DrupalCamps, DrupalCons, and meetups. With less need for our public workshops, we began to focus on on-site corporate training and add-on training at existing Drupal events. All of Lullabot's training offerings are now handled as part of Drupalize.Me. 3. As the Drupal ecosystem shifted and grew, it became harder and harder to find development partners who were both good and available. We responded by building out our development department. We acquired two small development shops – Rapid Waters and Sprocket Creative – and nearly doubled the size of the company in 2010. 4. With a complete development department in place and so many successful projects under our belt, we began to grow our services beyond the bounds of Drupal. The rise of technologies such as mobile and responsive design married the art and science of making websites in new ways that we were excited to master. We expanded our design and front-end offerings. We expanded into content strategy, community development, and design strategy. We hired the best systems administrator we'd ever worked with and some amazing project managers. Our staff is now approaching 40 people and we're able to offer complete concept-to-launch services for web and mobile projects. 5. We launched a gazillion amazing projects: MarthaStewart.com, WWE.com, Harvard.edu, Pac-12.com, Tizen.org, and many more. We also carried off our 4th year on Grammy.com and did some great work with Turner Broadcasting, NBC Universal, Verizon, Intel, Rackspace, Tesla, Sony Pictures, and many others, several of whom have sworn us to secrecy. 6. We also figured out how to grow. This may seem trivial to some, but it was no small feat for us. We had built an elite team of expert developers and great communicators who all really enjoyed one another. We'd built a great working culture and an environment that was both fun and productive. To be honest, we weren't sure how we could grow and maintain both quality and culture. Matt and I spent a good six months exploring this and ended up creating a [mission statement and core values list](https://www.lullabot.com/values) which became a common reference point and sort of a rule-book to compare against as we began to grow. It's easy to lose track of culture as big opportunities come along and we wanted to make sure we knew what made us unique and special. We also wrote up a manifesto-like employee handbook which expanded on these values and shared many of the systems, philosophies, styles, and methods which have been successful for Lullabot over the years. These things have all become a scaffolding inside which we can build a larger company without compromising quality or culture. For the past few years, visitors to our site left thinking that we were a Drupal education company. The truth is that this end of the business only represents about 20% of our revenue. And these days, all of Lullabot's education efforts are consolidated under Drupalize.Me. So our new site focuses less on education, handing those duties over to the dedicated education team at [Drupalize.Me](https://drupalize.me/) which now acts as a sub-brand of Lullabot. Our new website does a better job highlighting Lullabot's expanded [service offerings](https://www.lullabot.com/what-we-do), our [client list](https://www.lullabot.com/our-work), a few [case studies](https://www.lullabot.com/our-work), and our talented and expanding [team](https://www.lullabot.com/about). Of course, we've still got all of the [articles](https://www.lullabot.com/resources?type%5Barticle%5D=article) and [podcasts](https://www.lullabot.com/resources?type%5Bepisode%5D=episode) which have made us famous. We've got regular [Module Monday articles](https://www.lullabot.com/taxonomy/term/1), and a growing list of podcast series including [The Drupalize.Me Podcast](https://www.lullabot.com/podcasts/lullabot-podcast), [Insert Content Here](https://www.lullabot.com/topics/content-strategy), and [The Creative Process](https://www.lullabot.com/podcasts/the-creative-process). We're also posting more and more [business-related articles](https://www.lullabot.com/taxonomy/term/827) to the site giving voice to more of the non-developers at Lullabot. We find it rewarding to share what we know and we encourage everyone at Lullabot to post to lullabot.com whenever they can. The new site also has a great responsive desktop/mobile design which adjusts to different device widths and maintains readability across different device sizes. We've got retina-based graphics so the images look nice and sharp on your new MacBook Pro. We've got some cool CSS3 animation stuff and a nice HTML5-based player for our podcasts. Our home page better highlights our work and includes a new "Lullabot Labs" section to show off some of the stuff we've done for ourselves. We've also got some cool stuff pulling sidebar blocks from Drupalize.Me. If there's call for it, we'll do another write-up talking about the tech behind the new site. So here it is. As it takes the cobbler years to get around to making shoes for his children, this project took us longer than expected to get finished. We're still finding bugs and making adjustments and improvements. But we're really happy to have a new site that better matches Lullabot's current state of being and showcases some of the amazing work we've been doing. Hope you like it as much as we do! 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: "Front-End Design Conference Wrap-Up" url: "/articles/frontend-design-conference-wrapup" type: article date: 2012-06-12 updated: 2016-04-07 --- # Front-End Design Conference Wrap-Up # Front-End Design Conference Wrap-Up Front-end Design Conference 2012 By [ Josh Riggs ](/about/josh-riggs) June 12, 2012 This weekend, fellow Lullabot, [Kyle Hofmeyer](https://www.lullabot.com/who-we-are/kyle-hofmeyer) and I, attended the Front-End Design Conference and it was amazing! It was great meeting a lot of the attendees, many of whom I’ve admired and followed. Here are some of the highlights of the awesome events. ### The Speakers The event had a line-up of seven featured speakers. In all honesty, I loved every single talk and you could tell there had been lots of preparation for each. [Jason Van Lue](https://www.jasonvanlue.com/) was one of my favorites with a great talk called [“Three Pipe Problems and How Design Can Solve Them”](https://speakerdeck.com/jasonvnalue/three-pipe-problems). He brought up some amazing points about how, as designers, we are problem solvers and he invited the audience to analyze problems that are worth solving. Quoting Cameron Koczon, he said, “If we want design to be seen as more than decoration, we must treat development as more than plumbing.” A great quote and one I hope to better put into practice. [Gio DiFeterici](http://www.giodif.com/) also gave a great talk on Conceptual Design. It was interesting to see his perspective and how he approaches design from a fine art perspective. He showed slides of artists and their work and what they communicate. He made it clear that although the beauty of something doesn't make anything more usable, it’s an essential part of our craft. Another great talk was [Bermon Painter](http://bermonpainter.com/)’s. He talked about CSS preprocessors. I was a bit skeptical before this talk, but he did a great job of convincing all of us of how easy it is to get it up and running to use on your next project. However, one basic truth remains the same: If you write poor CSS, a preprocessor isn't going to magically make it better. [Carl Smith](http://www.ngenworks.com/team/carl-smith/) spoke right after lunch and his talk was titled “Choose!” Carl is such an amazing story teller. He walked us through the lives of some of the most successful people (yes, some were jerks) and talked about the decisions they made. One line I loved from his presentation was, “If you fail at something, just choose something else.” I think it’s easy to forget that failure should never keep us down, it serves as a learning experience for success. Another one of my favorites was [Dave Rupert](https://daverupert.com/). I had the awesome pleasure of going for burgers with Dave and he’s pretty awesome. He renamed his talk to [“Gettin' Flexy with Uncle Dave”](https://dl.dropbox.com/u/3649202/slides/2012/gettinflexy/index.html), and it was absolutely hilarious. Dave has such a unique way of teaching otherwise complex concepts in a simple way. He reduced responsive web design to simple math and even said, “responsive web design isn’t a religion, it’s math.” He’s made some great little jQuery plugins like [FitText](http://fittextjs.com/) and [FitVids.js](http://fitvidsjs.com/) which are a huge help for making responsive sites. ### Other Fun Stuff As I mentioned, it was really great meeting people whom I’d been following on Twitter and others whom I’d actually worked with in the past but never met in person (working from home FTW!). The party was great, the chocolate-covered bacon was deliciously sweet and salty, but I’d have to say that the highlight of the whole conference was learning about the future of fonts, shapes, and colors (the meme of the conference). ### Wrapping-Up the Wrap-Up I would like to extend a personal thank you to [Dan Denney](https://frontenddesignconference.com/), his wife Cherrie Denney, their awesome family and friends who helped us feel welcome, the speakers, and all of those who attended for making this such a great experience. I learned a ton and can’t wait to see you all next year. 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: "Retreats the 'Bot Way" url: "/articles/retreats-the-bot-way" type: article date: 2012-08-01 updated: 2019-01-11 --- # Retreats the 'Bot Way # Retreats the 'Bot Way A summary of the annual Lullabot retreat By [ Tim Smith ](/about/tim-smith) August 1, 2012 This past week, we enjoyed having our annual Lullabot retreat. Lots was done, great conversations were had but, here are the highlights. ## Where We Stayed We stayed with the beautiful people of [Whispering Pines](https://expireddomains.com/domain/wpinescc.com?utm_source=redi) who gave us amazing service. The rooms, meeting places and food was first-class and we can't thank them enough for being hospitable with us. As a distributed company, it's important for us to have time to talk and socialize. The great people at Whispering Pines made this easy and seamless. We didn't worry about a thing. ## It Was High School All Over Again ![Matt roasting marshmallow's by the fire](https://www.lullabot.com/sites/default/files/mattmarshmellow.png) While the parties were fun, what really made the "high school" feeling was the group photo! It's not often that all the 'bots are in the same place so, we took the opportunity to get new awesome pictures of ourselves! We've added nine new people in the past year so getting everyone in the group picture took some creativity. [Stacey Doyle](http://www.staceydoylephotography.com/) took our photos and it was a great experience; she made us all feel comfortable being in front of the camera! ## Company Presentations and Discussions We had quite a few of presentations on the inner workings of Lullabot and they were very educational. It helped us all learn about the processes involved in the jobs of others and to appreciate all of their hard work. As developers or designers, we sometimes forget all of the hard work that goes into making Lullabot work like a well oiled machine. We also had time to have what we call [LullaBOFs](https://en.wikipedia.org/wiki/Birds_of_a_feather_(computing)), where we got together in small groups and talked about new ideas, our future and other interesting internal projects. These sessions were so productive! We were able to come out of most of them with clear action items or at least an idea of what we should do next. It was truly great to see all of our minds work together. ## Ignite Talks and the 2nd Annual Talent Show ![Chelsea making amazing guacamole](https://www.lullabot.com/sites/default/files/chellbell.png) Many gave 5 minute ignite talks on things they were passionate about or a funny topic. All of them were so interesting and it was great to get to know each other in other aspects of life. It was awesome. Really awesome. Awesomely awesome. We also held our 2nd Annual Talent Show where the 'bots showed off amazing talents. We were all surprised at the many hidden talents of the 'bots. We could totally start a band and make it big. ## Wrap Up All in all, it was a great week. We were extremely productive and had a lot of fun. A big thanks to [Chelsea Barwald](https://www.lullabot.com/who-we-are/chelsea-barwald), [Haley Scarpino](https://www.lullabot.com/about/haley-scarpino) and the brilliant owners of Lullabot, [Matt Westgate](https://www.lullabot.com/about/matt-westgate) and [Jeff Robbins](https://www.lullabot.com/about/jeff-robbins) who worked so hard to make this retreat possible. Can't wait till next year! 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: "Making Attachments Appear Across Translations" url: "/articles/making-attachments-appear-across-translations" type: article date: 2008-11-20 updated: 2016-04-07 --- # Making Attachments Appear Across Translations # Making Attachments Appear Across Translations Drupal multi-lingual By [ John VanDyke ](/about/john-vandyke) November 20, 2008 Drupal 6 supports translation of content with the core [Content Translation module](http://drupal.org/handbook/modules/translation). So you can create a page in English, then translate the page to Dutch. But what if you attach a file to the English page? It does not show up on your Dutch translation. And what if you are using [CCK](http://drupal.org/project/cck) and [FileField](http://drupal.org/project/filefield)? That's the case that I want to cover in this article. First, I installed Drupal 6.6, enabled the Locale module, and added the [Dutch translation](http://drupal.org/project/nl) (for the proper way of installing translations, see Addison Berry's screencast, [Installing Drupal with a Translation](https://www.lullabot.com/articles/installing-drupal-with-a-translation)). I installed CCK 6.x-2.1 and enabled the Content module and some others (Node Reference, Number, Text, Option Widgets). I installed the 6.x3.x-dev snapshot of FileField (it's better to use an actual release but I saw some bug fixes going into the module and figured I'd test it). I enabled FileField module. I enabled the core Content Translation module. Then I installed i18n 6.x-1.0-BETA6, which is the latest release of the [Internationalization Module](http://drupal.org/project/i18n). I enabled the Internationalization and Synchronize Translations modules. I needed a CCK content type to test with so I created one: ![Drupal create content type screenshot](/sites/default/files/styles/wide_xs/public/assets/2016-04/createtestcontenttype.png.webp?itok=OZFmrp9U "createtestcontenttype.png") I created a CCK field called Audio to hold audio files: ![Drupal create audio field screenshot](/sites/default/files/styles/wide_xs/public/assets/2016-04/createaudiofield.png.webp?itok=ArnLYXl- "createaudiofield.png") Then I adjusted the new content type's workflow settings to enable multilingual support and synchronization of the CCK field: ![Drupal test type workflow settings](/sites/default/files/styles/wide_xs/public/assets/2016-04/testtypeworkflowsettings.png.webp?itok=DHy1CjBR "testtypeworkflowsettings.png") Now I was ready to enter some content. I created a node in English with an attached music file: ![Drupal create field instance screenshot](/sites/default/files/styles/wide_xs/public/assets/2016-04/createtestinstance.png.webp?itok=8e5n_umQ "createtestinstance.png") Then I used the "add translation" operation of the Translate tab to add a Dutch translation. ![Drupal add translation tab screenshot](/sites/default/files/styles/wide_xs/public/assets/2016-04/addtranslationtab.png.webp?itok=KbyU_oTw "addtranslationtab.png") The audio field could be seen on the node editing form, and the resulting Dutch translation contained the attachment. ![Screenshot of a Drupal node in Dutch](/sites/default/files/styles/wide_xs/public/assets/2016-04/finisheddutchnode.png.webp?itok=_pSasAj- "finisheddutchnode.png") Sharp-eyed readers will notice that on the workflow settings options for the content type, I also have Attachments enabled (and I've got the core Upload module enabled), but I didn't check File attachments under the Synchronize translations section of the workflow settings. That means that I can attach files using the Audio field and they *will* be synchronized across translations, but I can attach files using the regular File Attachments method and they will *not* be synchronized, so I can also have a separate file per translated node. The best of both worlds! The Synchronize Translation module can be used to do much more than synchronize CCK fields; it can also synchronize taxonomy terms, comment and node settings, etc. Here we have just scratched the surface of its capabilities by using it to synchronize a CCK filefield across translations. For more information on Drupal's internationalization capabilities, see the [Drupal Handbook](http://drupal.org/node/133977), the [Internationalization Drupal Group](http://groups.drupal.org/i18n), chapter 18 of [Pro Drupal Development](https://www.amazon.com/gp/redirect.html?ie=UTF8&location=http%3A%2F%2Fwww.amazon.com%2Fo%2FASIN%2F1430209895&tag=lullabot-20&linkCode=ur2&camp=1789&creative=9325), or chapter 8 of [Using Drupal](http://usingdrupal.com/). 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: "Lullabot Sponsors BADCamp 2012" url: "/articles/lullabot-sponsors-badcamp-2012" type: article date: 2012-10-16 updated: 2014-05-01 --- # Lullabot Sponsors BADCamp 2012 # Lullabot Sponsors BADCamp 2012 November 1st - 4th at UC Berkeley By [ Haley Scarpino ](/about/haley-scarpino) October 16, 2012 Lullabot is excited to announce we will be sponsoring [BADCamp](https://badcamp.net/), November 1st - 4th on the UC Berkeley Campus. BADCamp is the world’s largest free Drupal Event with over 900 people registered. BADCamp will be offering free training on Thursday and Friday. Lullabot's [Drupalize.Me](https://drupalize.me/) team will be there teaching our [Community Tools Workshop](http://2012.badcamp.net/program/training-drupal-community-tools-irc-git-0). There will also be a bunch of exciting summits like the [Mobile Drupal Summit](http://2012.badcamp.net/program/mobile-summit) and the [Drupal DevOps Summit](http://2012.badcamp.net/program/drupal-devops-summit), amongst many others. [Registration](http://2012.badcamp.net/register) is still open, however, session submission closed on October 1st. We’ll be there and we’d love to see you there as well! 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: "Lullabot Client Sites Bring Home Blue Drop Awards!" url: "/articles/lullabot-client-sites-bring-home-blue-drop-awards" type: article date: 2012-04-10 updated: 2016-04-07 --- # Lullabot Client Sites Bring Home Blue Drop Awards! # Lullabot Client Sites Bring Home Blue Drop Awards! Featuring Grammy.com, Safari Books Online and MIT-K12 By [ Lullabot ](/about/lullabot) April 10, 2012 We're thrilled to announce that three of our client sites received [Blue Drop Awards](https://www.bluedropawards.org/)! **Grammy.com** was voted [**Best Media Website**](https://www.bluedropawards.org/best-media-website-built-with-drupal/nominees/grammycom), **Safari Books Online** was voted [**Best Marketplace Website**](https://www.bluedropawards.org/best-marketplace-website-built-with-drupal/nominees/safaribooksonlinecom), and **MIT-K12** received 2nd place in the [**Best Non-Profit/Education Website category**](https://www.bluedropawards.org/best-non-profit-education-website-built-with-drupal/nominees/mit-k12). "We're always proud of the work we do for our clients," said Jeff Robbins, Lullabot's CEO. "But to have the community vote for these sites as some of the best examples of the use of Drupal is an honor. We're very grateful to everyone who voted, and congratulate all the winners." “The quality of the nominations submitted to the 2012 Blue Drop Awards was outstanding this year,” said Ben Finklea, CEO, Volacci. “Our hats are off to Lullabot for winning multiple awards in the face of very worthy competition.” Representing more than 630,000 contributors and developers, Drupal is the largest, most active open-source technology community in the world. The Blue Drop Awards were created to promote Drupal to the greater technology community and to recognize innovative, next-generation websites and the companies that built them. With thousands of sites leveraging Drupal, the new awards program was designed to be the first democratic, voter-driven recognition program for Drupal members. To see all the winning projects and check out the other nominated websites please visit www.bluedropawards.org. For more information on how to volunteer or participate as a sponsor next year, please contact Ben Finklea, CEO, Volacci at (512) 632-4222 or Ben@Volacci.com 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: "Constructive Criticism" url: "/articles/constructive-criticism" type: article date: 2012-05-10 updated: 2016-04-07 --- # Constructive Criticism # Constructive Criticism The Way to a Better Web By [ Tim Smith ](/about/tim-smith) May 10, 2012 A common theme in our amazing industry is excitement. Excitement about technology. Excitement about the progress of web standards. Excitement about how our young industry is continually refining its process to create a faster, more beautiful and accessible web. It excites me to talk about it; even more so everyday when I wake up and remember where I work. It's in these times, more than ever, that we need criticism. Not just any type of criticism, but *constructive* criticism. What exactly does that mean? The dictionary defines it as the following: > "Advice that is useful and intended to help or improve something." —dictionary.com I like this definition a lot, especially the part about being "useful" and "to help or improve something." Though we can A/B test two things once they exist, the truth is, design isn't a mathematical equation. There are times we wish it could be, but it's not. Rarely is there a singular obvious solution for a given problem. Don't get me wrong, design is not magic. Design is just tough, especially when there are so many moving parts (responsive design pun intended). This is something I've really loved about working at Lullabot. Jared, our Creative Director, always has feedback for me. He pushes me to improve and iterate on everything I work on. His years of experience trump mine and allow him to see things that I don't. Therefore, as time goes by I examine everything much more and I'm more aware of the details. He's making me a better designer and I'm eternally grateful for that. Remember that behind every website is a person trying to do the best work they can. We're at a time where technology is moving a lot faster than we can sometimes design for. That means we'll make mistakes. We'll have content that we don't know how to deliver on every device. But, guess what? That's perfectly fine. We will learn from these missteps; and because we're a part of such an awesome industry, we'll share our successes and failures and offer constructive criticism to those who haven't learned yet. The way to a better web isn't by public ridicule; it's by actually caring. As cliché as it may sound, we have the power to make the world a better place and that starts by the way we treat each other. Now, get back to work, we have tons of it. 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: "Glue Code" url: "/articles/glue-code" type: article date: 2007-07-09 updated: 2014-05-02 --- # Glue Code # Glue Code By [ Jeff Eaton ](/about/jeff-eaton) July 9, 2007 Here at Camp Lulalbot we've been whipping up a series of sites that differ from our normal work: they're meant to be built with no custom code, only contrib modules and UI-driven configuration. Sure, there might be a bug here or there to fix, but the idea is to speed things up dramatically by sticking to 'pure' contrib code. In a lot of ways, it's dazzlingly cool. We've been able to put together a relatively advanced social site with some great community features in record time. But on the fuzzy edges -- the tweaky little bits of the designs, and a few ideas we thought would be simple -- it's apparent that doing some things WITHOUT custom code is really annoying and convoluted. Some of this is due to the law of diminishing returns. Making simple, easy-to-use administrative interfaces for the really COMMON tasks pays off nicely for developers. Things like making custom content types, building listing pages, and so on are so common that Drupal's UI-based builders for the tasks have become pretty powerful. The more esoteric the tasks get, though -- the more site-specific -- the harder it is to provide good tools for non-developers. A task that might take 30 minutes and 20 lines of PHP in the hands of a developer could be performed by any site administrator using a configurable UI-based wizard. Building that wizard, though, could easily take hundreds, thousands of lines of code and man-months of development time. Where's the line between 'needs to be automated' and 'seriously, just stick some PHP in...'? All this is to say that I have newfound sympathy for the non-developers who want to put together complex sites with exacting specifications. You can get 90% of the way there pretty easily, but the final 10% will require compromise, or custom code. Drupal has massively accelerated the process of putting together feature-rich sites for us, but sometimes -- as with LEGO building blocks -- you don't have just the right piece in your bucket of bricks. 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: "2011 and Onward" url: "/articles/2011-and-onward" type: article date: 2012-01-05 updated: 2014-05-03 --- # 2011 and Onward # 2011 and Onward Lullabot's Year In Review and Where We're Headed By [ Jeff Robbins ](/about/jeff-robbins) January 5, 2012 January 1st of 2012 is the beginning of Lullabot's 7th year. When Matt Westgate and I launched the website and the company on the first day of 2006, we had no idea how successful we would become. Each year around this time, I find myself looking back with appreciation. I thought I'd take a few moments to sum up my thoughts about Lullabot's 2011 and where we're headed in the future. ### Accomplishments As we close out 2011, we have 30 people at Lullabot – most of whom are full-time employees – and we're [still hiring](https://www.lullabot.com/jobs). In 2011, we led the Drupal-based relaunch of [MarthaStewart.com](https://www.marthastewart.com/) and [WWE.com](http://wwe.com); we played a key role in the relaunch of [Harvard.edu](https://www.harvard.edu/); we helped Sony Pictures move many of their sites to Drupal; we kept [Grammy.com](https://www.grammy.com/) running through the 2011 awards night; we built The Grammys website for the 3rd year in a row (be sure to watch the CBS telecast on February 12th); and we worked on some super secret stuff that we're not allowed to talk about. Our Drupal video training site, [Drupalize.Me](https://drupalize.me/) continued to grow and bring Drupal knowledge and expertise to thousands of subscribers. We built iOS, Android, and Roku apps for Drupalize.Me to make it easier to access our huge training library. And we launched [Videola](http://videola.tv), Drupalize.Me's underlying platform, as a separate Drupal distribution and hosted solution for enterprise video management and ecommerce. Oh yeah, and we also ran our third [Do It With Drupal](http://doitwithdrupal.com) conference, which hosted about 250 attendees in Brooklyn, NY in October. The 35-or-so sessions from DIWD, including keynotes by Jeffrey Zeldman and Josh Clark, are being posted weekly to Drupalize.Me. Also in 2011, after 5 years as a completely virtual/distributed company, we signed the lease on an office space in Providence, RI. We don't tend to work out of that location much, but it's nice to have a home base for meetings, workshops, and the Providence Drupal meetups. I know I'm supposed to be cool and aloof about my own company, but I'm just so proud of the talented team we've put together, the amazing clients we've been blessed to work with, and the collective impact of 6 years of experience and knowledge on Lullabot. ### Scaling Culture I spent a lot of time in 2011 thinking about culture and whether it would be possible to expand the Lullabot team without losing our mojo. When we had 5 people at Lullabot, I didn't think we'd be able to scale our "vibe" past 8 people. When we had 10 people, I didn't think we'd be able to stay tightly-knit past 15. Things have certainly changed. But we've been able to find such friendly and talented people that even though Lullabot continues to change, we haven't lost the feel of the company. But when we crossed 25 employees, it started to become apparent that if we wanted our values and culture to scale, we were going to need to write some things down. When we were a young company, it seemed silly – even narcissistic – to try to write down the formula that made Lullabot Lullabot. But as we grew, interacted with other companies, and started hiring people with different backgrounds, we found ourselves explaining the same things over and over again. It wasn't enough just to have people see how others were doing things and imitate. We have our own ways of hiring, communicating, and approaching problems. And as a virtual company, there are very few givens. It's become an art to keep the team tightly-knit even though we're spread apart. If Matt and I were going to be able to grow the company, we would need to make sure our ideas, philosophies, and practices were documented, serving as a solid foundation on which to build the company. So for me, the big theme of 2011 was "culture." I spent a lot of time thinking and writing about Lullabot's core values as well as the philosophies and practices that make Lullabot successful, rewarding, and fun. At our annual company retreat, we shared a first draft of the Lullabot core values. We had a lot of good discussion about what they mean to us as a company, and as individuals. We're still fine tuning the exact wording of the values, but plan to have them on our website in the next few months. I also spent a lot of time helping to set up resources so that Matt and I could keep ourselves free enough to monitor and support the team, make sure that things are running smoothly, and make sure that we keep attracting top-tier employees and clients. ### Looking Forward I also spent a lot of time thinking about where Lullabot is going and how we'll get there. When we started out, we wanted to conquer the world with Drupal. We wanted to be the best, most Drupal-y, company out there – promoting Drupal, showcasing what could be done with it, and growing the community. I would venture to say that we achieved that goal for a number of years. But these days, not only has Drupal proven itself as a worthy solution with a very healthy community, but there are a lot of other very Drupal-y companies out there. Over the years, we've gained a lot of expertise in what I might cheekily call "Drupal-adjunct technologies and practices." Lullabot has gotten really good at project management, project estimation, and clear client communication and involvement. We know about all there is to know about building web infrastructures, scaling and performance strategies, and database optimization. These days we're doing excellent design and UX work and we're providing complete concept-to-launch services. We're doing mobile strategy, design, and development. We're venturing into IPTV development and strategy. We've also added content strategy, community building, and social media strategy to our list of services. And of course, we're continuing to do bang-up Drupal strategy, development, and training. We're not just a Drupal company anymore. I think we'll always have Drupal at our heart and you’ll still see us at your favorite camps and cons, but it's time to acknowledge that it has become only one aspect of what we do. Lullabot's future is as a complete web strategy, design, and development agency. We're also continuing development of our current products, including [Videola](http://videola.tv) and [Drupalize.Me](https://drupalize.me/) - and we've got a few more in pipeline. We've always had very lofty goals, even if those goals don't tend to be financially based. They tend to be more like "work with the best companies in the world," "kick ass on all projects," or "make sure the work is fun and rewarding." We know these goals are sometimes a stretch, so we're always appreciative when we're able to achieve them. Not everything we've attempted has been a great success, but I'm glad we've got the resources, time, and energy to keep trying things. And when you list off our accomplishments, we look pretty good! Sometimes I think that one of these days we'll figure it all out. We'll finish making all of the decisions that need to be made. Lullabot will run itself and we can just lean back and relax for the next 10 years. But we keep getting excited about new things. We decide to take on a fun new project, learn a new technology, build a cool new product, or start providing an exciting new service. We keep challenging ourselves. We keep growing and changing. And honestly, we enjoy the challenges. I doubt that the next 6 years will look much like the past, but we're building on 6 years of experience and knowledge. I look forward to the challenges, the changes, kicking more asses, having more fun, doing more work we can take pride in, and working with more talented and lovely people. Thanks to our clients, our friends, and everyone who's been involved with Lullabot over the past 6 years. We couldn't have done it without you. 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 Season of Slashdot's Discontent" url: "/articles/the-season-of-slashdots-discontent" type: article date: 2008-05-01 updated: 2014-05-03 --- # The Season of Slashdot's Discontent # The Season of Slashdot's Discontent By [ Jeff Eaton ](/about/jeff-eaton) May 1, 2008 It's been a scratchy couple of days in the tech blogging world for Drupal, as a review of David Mercer's new Drupal book veered off-topic and turned into a [discussion of Drupal's appropriateness for various projects](https://books.slashdot.org/story/08/04/30/1346258/building-powerful-and-robust-websites-with-drupal-6). As is to be expected in a fast-moving Slashdot discussion thread, opinions were heated and curmudgeonly grumbling was the order of the day. Today, [Acquia's Jeff Whatcott notes](http://jeffwhatcott.com/drupal/content/huh-files-why-wordpress-drupal-and-other-cms%E2%80%99s-are-bad-innovation-brainnovate) that blogger Scott Miller is writing that [Wordpress, Drupal, and other web frameworks are bad for innovation.](http://brainnovate.com/index.php/2008/04/why-wordpress-drupal-and-other-cmss-are-bad-for-innovation/) Miller admitted readily that he'd never used Drupal, and the emphasis of his post was on people using blog and CMS frameworks to build custom applications when they should be starting from scratch. Still, for those of us who drink the Drupal Kool-Aid, it can be tough seeing these kinds of statements tossed around. How should we, the folks who build and use Drupal, approach these kinds of discussions? To some extent, they are inevitable. The more visible a given technology or project is, the more critics will chime in. Back in the [2007 Predictions](http://drupal.org/node/105423#comment-183465) thread on Drupal.org, I said that backlash would be coming as Drupal transitioned from a niche web CMS to a framework with broader usage. > \[In 2007 and beyond\] Drupal will 'arrive' and no longer be seen as the hot newcomer. This will be evidenced by individuals and companies that use 'Drupal' as a resume-fodder buzzword without actually knowing how to work with it, an increase in of 'Drupal backlash' on sites like SlashDot and Digg, and blog stories by disgruntled former Drupal users. This won't be an inherently bad thing: it's what every system experiences. As Yogi Berra once said, "No one goes to that restaurant anymore: it's too busy." That's exactly what we're seeing -- Drupal has a much higher profile than it did just a couple of years ago. Where does the backlash come from? Some have legitimate criticisms that we should take to heart. Still others just didn't find Drupal a good fit. A handful don't know much about Drupal at all, and are just parroting stuff they read on Slashdot or expressing annoyance at the latest group of enthusiastic framework advocates. That's certainly nothing new -- every technology faces that. ColdFusion? ASP.Net? Django? Even the Rails community saw controversy when one of its influential members [became disillusioned.](http://www.zedshaw.com/rants/rails_is_a_ghetto.html) Drupal's successes with both large and small sites over the past several years demonstrate that we don't have anything to *prove*. While it's obviously not a good fit for every project, Drupal has proven itself as a great tool for building content-centric community sites. As the folks at Acquia are fond of saying, Drupal isn't just about blogging or social networking or portals: it's a *social publishing tool*, and a great one at that. The challenge for all of us, I think, is to stay in the realm of the pragmatic. Drupal isn't a perfect piece of software (such things don't exist!) and those of us who work with it daily are aware of its limitations as well as its strengths. We have a commitment to improving it, educating others about its strengths, and honestly discussing its weaknesses. One of the common threads in "I tried Drupal and got burned" stories over the last several months is a feeling of being snookered -- site stakeholders were told that it could do anything, and were frustrated when "anything" proved more difficult than anticipated. Honesty, and the assurance that goes with it, can be a great strength for Drupal evangelists and the community in general. 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: "Mentorship Consulting: A Client Primer" url: "/articles/mentorship-consulting-a-client-primer" type: article date: 2013-05-16 updated: 2014-05-03 --- # Mentorship Consulting: A Client Primer # Mentorship Consulting: A Client Primer By [ Andrew Berry ](/about/andrew-berry) May 16, 2013 Mentorship consulting is one of the [many services that Lullabot provides](https://www.lullabot.com/what-we-do), and is something we’re known for in the Drupal community. When we work with potential clients to describe what we can do for them, it can sometimes be very difficult to explain how a consulting relationship works. This is especially true if they have never participated in consulting engagements with us or another agency. It may seem counter-intuitive, but the success of a consulting engagement depends just as much on how a client approaches an engagement as it does on our expertise. Here’s a few lessons I’ve learned. ## Bootstrapping Your Consultant At Lullabot, when we begin a new project of any kind, we always start with a kickoff call. Everyone who was involved with the pre-sales discussions, and everyone who will actually be on the project is invited to attend. Ideally, there’s some overlap between the groups, especially on the technical teams. This is especially important for consulting engagements where we may be brought into a project mid-stream. During the kickoff call, **set up the lines of communication between your team and your consultant**. We recommend that as much as possible be done with text, such as [group chat](https://drupalize.me/tutorial/using-irc-internet-relay-chat), an online project management tool, or even email. This allows for discussions to be easily captured and turned into documentation as needed. Of course, voice is still useful, especially during screen sharing sessions, so it’s worth figuring out if Skype or Google Hangouts will work, or if everything needs to be done over a conference line. Also during the kickoff call, you should **set some broad goals for the engagement, but be prepared to change them as the engagement progresses**. Often, we have clients who come to us to investigate a very specific issue, and we quickly find out that there are unrelated, but more serious problems that are worth addressing first. Much of our team’s expertise is in identifying and tracing issues in code or process. Finally, once the kickoff is done, try to **give your team time in their schedule to ask questions and learn** from your consultant. Often, we are brought in for consulting and we find that the client’s team is overloaded or near a project launch. If your team is spending 35 hours a week with heavy development and coding, it leaves very little time for them to actually use the consulting hours that have been contracted. Further, the team won’t have adequate time to review and consolidate their learning. For a 10 hour-per-week consulting arrangement, we usually recommend that you allocate 2-5 hours from your team to be used with conference calls and other communication. If that sounds like it’s not feasible with your team’s current workload, consider clearing some time before the consulting engagement begins. ## Distributed Learning Lullabot is [distributed across multiple time zones and countries](https://www.lullabot.com/about). We don’t have regional offices that employees work out of, so we are used to communicating and learning online. This is a contrast to most of our clients (and most of the tech industry) where offices and location-oriented teams are the norm. We’ve found that treating co-located teams as if they are distributed ensures that everyone is getting the most out of a consulting relationship. It also helps improve team communication overall. It’s very important to allow **anyone on your team to work with the consultant**. We try to encourage discussions to occur in a “group” context. For example, try to use group chat rooms instead of one-on-one instant messaging. This encourages conversation and brainstorming, and prevents segmenting information among individuals on your team. It also means that you’ll be better equipped to utilize your consulting hours. As you adapt to having one of our consultants on hand, it’s a great opportunity to **build a culture of sharing and learning**, both within your organization and within your industry. Document what you learn from your consultant for future reference inside your company. Company-wide Wikis or intranet tools are perfect for this sort of documentation. Likewise, if your company has a blog, use it as an opportunity to consolidate and share what your team is learning. Even if “the web” isn’t core to your company’s business, there will always be lessons your team learns through our consultants that are of a non-technical and industry specific nature. Sharing those lessons will help establish your company as a leader among your peers while solidifying the knowledge within your team. **Always assume someone else has solved your problem, and solved it with a better solution than your own**. Even though Lullabot is a well-established expert in Drupal, content strategy, and design, we are always looking at other teams inside of Lullabot and other companies for ways to improve a given solution. When you work with Lullabot, you aren’t just hiring a single consultant; you’re hiring an entire company of knowledge and expertise. ## The Difficult Parts A consulting relationship with Lullabot isn’t just about upgrading your development skills and learning Drupal. It’s about getting a new perspective on how your team works and becoming experts in your own specialty. Sometimes, this process can be a bit uncomfortable, especially if your company has a legacy of static processes and resistance to outside expertise. To start, **be open to process improvements as well as technical improvements**. Sometimes, technical issues are just a symptom of problems with documentation, project management, or team communication. We’ve worked on many high profile sites as well as our own products. There’s never a single process or solution to project management or communication problems, but our experiences in this area let us contribute a whole range of solutions that your teams may have never encountered before. Sometimes, working with a consultant means **holding your team to a higher standard**. Simply saying “we can’t” because of existing processes, a lack of motivation, or knowledge kills team morale. Take the suggestions given to you by your consultant, figure out how and when to implement them, and **recognize the effort and results your team achieves** through any particularly difficult changes. Finally, **make continuous improvement a core value of your team**. Sometimes, clients are discouraged when they realize the gap between their current skills and where they want to be. Instead of trying to change everything all at once, just try to improve each sprint to be a little bit better than before. Not only will this allow your team to focus on one specific area of improvement at a time, but evaluating the changes at the end of a sprint or project will be much simpler. ## Wrapping up Consulting engagements can be one of the most difficult, yet most rewarding, offerings available from agencies. Nothing makes me happier to see a client’s team go from a junior level of expertise to where they are teaching me about what they’ve learned. I know it’s rewarding for our clients to be able to see themselves become our peers, and not just our client. If you put the energy into getting the most out of a consulting engagement, it may well be one of the best decisions you can make. 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: " The Friday Roundup: DrupalCon, UX Critiques, Mobile, and Microdata" url: "/articles/the-friday-roundup-drupalcon-ux-critiques-mobile-and-microdata" type: article date: 2011-06-03 updated: 2014-05-04 --- # The Friday Roundup: DrupalCon, UX Critiques, Mobile, and Microdata # The Friday Roundup: DrupalCon, UX Critiques, Mobile, and Microdata Links Corralled by Lullabots By [ Jeff Eaton ](/about/jeff-eaton) June 3, 2011 ### DrupalCon London The big Drupal news this week is the announcement of [DrupalCon London's official session list](http://london2011.drupal.org/conference/chosen-sessions)! Lullabot's Nate Haug will be teaching attendees about [Form Builder's new changes for Drupal 7](http://london2011.drupal.org/conference/sessions/introduction-form-builder-new-interface-fields), while Jeff Eaton discusses [the differences between products and frameworks (and why we should care)](http://london2011.drupal.org/conference/sessions/product-framework-and-core-what-it-means-and-why-you-should-care). Meanwhile, the city of Croyden is [chatting with attendees](https://x.com/) via Twitter and suggesting great night-life spots for after the conference! ### The Shoemaker's Web Site [Fog Creek Software](https://fogcreek.com/) (famous for its FogBugz bug tracking software and the opinionated essays of its founder Joel Spolsky) recently re-launched their web site. They've written up an [interesting post about the challenges of growing beyond "Product Launch Cycles"](http://blog.fogcreek.com/our-marketing-is-up-fog-creek-and-what-we-did-about-it/) to ongoing continual improvement -- both for their products and their site. ### UX-A-Palooza Usability and design critiques are all the rage this week: Daring Fireball's John Gruber [weighs in on the changes in Windows 8](https://daringfireball.net/2011/06/windows_8_fundamentally_flawed), engineer Majd Taby reviews [the good and the bad in Google Chrome's interface](https://majd.blog/2011/05/31/google-chrome-why-i-hate-it-and-continue-to-use-it.html), and designer Luke Wroblewski takes a deep look at [Netflix's evolution towards a streamlined interface that puts the content first.](https://www.lukew.com/ff/entry.asp?1347) Meanwhile, UX designer Andy Budd writes a kicky essay titled "[I Don't Care About User Experience.](https://www.andybudd.com/archives/2011/05/i_dont_care_about_user_experience)" (Spoiler: He actually does.) If you're looking for some meat-and-potatoes tips for your own software, UX Design Edge has [a great article that's full of low-hanging fruit](https://www.uxdesignedge.com/2010/03/dont-design-like-a-programmer/). ### Hello, Microdata! The three juggernauts of the search engine landscape -- Google, Yahoo, and Bing -- have just launched [Schema.org](https://schema.org/), a clearinghouse for information on the structured data formats recognized by their search crawlers. They've also announced *[microdata](https://html.spec.whatwg.org/multipage/md-LC/)*, a new format for embedding metadata in HTML pages. Naturally, Drupal's resident RDFa guru Lin Clark has already spun up [a new project to support the format](http://drupal.org/project/microdata)... ### Mobile Apps and APIs Jeff Eaton and Karen McGrane of Bond Art + Science will be presenting on mobile strategy at the [Web Content Conference](http://www.webcontent2011.com/making-most-mobile) in Chicago next week. If you're still trying to decide on *your* strategy, an [inevitable infographic has been making the rounds... ](http://blog.buysellads.com/2011/05/the-app-option-does-your-business-need-one/?view=infographic) ### Jen Rocks The Web In Friends-Of-Lullabot news, [O'Reilly author](https://www.oreilly.com/library/view/~/1565925157/), [interviewer of rock stars](https://cookingwithrockstars.com/), longtime web designer, and [new Fiat owner](http://twitpic.com/522mvy) [Jen Robbins](http://www.jenville.com/) appeared on [The Big Web Show](https://bigwebshow.fireside.fm/50) with Jeffrey Zeldman to chat about UX and design issues. 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 Friday Roundup: APIs, Drupal Drama, and DIWD Fantasies" url: "/articles/the-friday-roundup-apis-drupal-drama-and-diwd-fantasies" type: article date: 2011-09-23 updated: 2014-05-04 --- # The Friday Roundup: APIs, Drupal Drama, and DIWD Fantasies # The Friday Roundup: APIs, Drupal Drama, and DIWD Fantasies Links Corralled by Lullabots By [ Jeff Eaton ](/about/jeff-eaton) September 23, 2011 ### FCC Sponsors API Work The recent launch of a newly-redesigned [FCC.gov](http://seabourneinc.com/projects/fcc/) web site is a big win for Drupal, and a great example of public-access APIs being built into large government web sites. Seabourne Inc.'s post about the migration also details the new contrib module, [Content API,](http://drupal.org/project/contentapi) that the project spawned. Built on top of the popular Services module, it automatically exposes select content types on a Drupal site via a REST interface. A little bird told the Lullabout Roundup Cowboy that a D7 version of the module is coming soon, as well... ### Give it a REST Speaking of REST and web APIs, [an interesting article by designer Luke Wroblewski](https://www.lukew.com/ff/entry.asp?1392) discusses the potential for API driven and server-side tools to simplify the implementation of responsive web designs. Confused? Check out [Jared Ponchot's article on responsive design](https://www.lullabot.com/articles/responsive-adaptive-web-design), and developer Steve Klabnic's article, [Nobody understands REST.](http://blog.steveklabnik.com/2011/07/03/nobody-understands-rest-or-http.html) ### All Documentation Isn't Equal ProgrammableWeb has a great [guide to writing API documentation](http://blog.programmableweb.com/2011/09/12/the-six-pillars-of-complete-developer-documentation/) for developers. Helping developers leverage a set of tools requires more than just PHPDoc, and this article outlines the essentials. Given the explosive growth of API and "framework" style modules in the Drupal ecosystem, this guide should be a new Bible! ### Drupal Dramarama Since DrupalCon London in August, blog posts and debates about [the future direction](http://www.unleashedmind.com/en/blog/sun/the-drupal-crisis) of Drupal [have been](http://drupal4hu.com/node/302) *[hot stuff](http://ca.tchpole.net/node/4).* Kent Bye's [Drupal Drama podcast](https://www.lullabot.com/podcasts/drupalizeme-podcast/drupal-drama-burnout-d7-retrospective-product-vs-framework-debate) and [Jeff Eaton's Drupal 8](https://www.lullabot.com/articles/understanding-8-where-drupal-is-and-where-its-going) article here on Lullabot.com attempt to summarize what's up. For those nervous about the discussions, taking a peek at [other open source projects'](https://www.scribd.com/doc/37113340/Why-Django-Sucks-and-How-we-Can-Fix-it) internal debates [might put things in perspective](https://odino.org/367/why-joomla-sucks-why-the-joomla-platform-sucks-even-harder-and-how-we-can-fix-this/)... ### News of the Web World For those seeking for news beyond the Drupal sphere, it's been a busy couple of weeks. Twitter has released Bootstrap, [a new HTML/CSS framework](http://twitter.github.com/bootstrap/) that unifies various typographic and layout elements. The big-data guys over at Development Seed have written about their freshly designed web site, [built using the Jekyll page rendering framework](https://developmentseed.org/blog/2011/09/09/jekyll-github-pages/): it spits out static files and stores its data on GitHub! Meanwhile, scientists at UC Berkeley have figured out [how to reconstruct video from human brain activity...](http://newscenter.berkeley.edu/2011/09/22/brain-movies/) Can a FieldAPI "MRI Input widget" for media fields be far behind? ### Fantasy Sites Galore Finally, the Lullabots have been hard at work prepping for the October 12th-14th [Do It With Drupal conference.](http://doitwithdrupal.com) The ever-popular "Fantasy Sites" track will feature a series of Drupal-powered clones of well-known web sites, including [NetFlix](http://2011.doitwithdrupal.com/blog/learn-build-your-own-netflix-diwd-2011), [Meetup.com](http://2011.doitwithdrupal.com/blog/learn-build-meetupcom-do-it-drupal), and more. Rumor has it Joe Shindelar, the [mastermind](https://www.youtube.com/watch?v=LPIHzH0eMkI) behind the [Marquee Module](https://github.com/eojthebrave/marquee), is working on [a fantasy site of his own...](https://evernote.com/skitch) 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's Continuous Tax, or: Should We OOP?" url: "/articles/drupals-continuous-tax-or-should-we-oop" type: article date: 2007-09-30 updated: 2014-05-04 --- # Drupal's Continuous Tax, or: Should We OOP? # Drupal's Continuous Tax, or: Should We OOP? By [ Jeff Eaton ](/about/jeff-eaton) September 30, 2007 Blogger Crazy Bob recently commented on something he calls ["The Continuous Tax"](http://crazybob.org/2007/09/gavin-king-on-activerecord.html#9100660737606468207) -- the time and energy that must be invested in building and maintaining APIs to do something your programming language doesn't handle for you automatically. The discussion in question focused on the nature of ActiveRecord in Ruby On Rails and how it compares to strongly-typed ORM systems in Java. That won't mean much to most people in the Drupal community, but the underlying conflict mirrors a hot debate currently brewing in our neighborhood. Specifically, should Drupal use PHP's new OOP features? Traditionally, Drupal has used plain vanilla procedural PHP to acomplish a wide variety of cool tricks. (It's a good lesson for those who say that design patterns can only be used with OOP languages; Drupal's hook system and theming system use naming conventions to implement visitor patterns, chains of responsibility, and so on. The entire FormAPI system is, as many have argued, a lightweight object oriented system implemented with arrays.) While many underlying concepts of object orientation are embraced, Drupal was also written when PHP's OOP features, well... kind of sucked. That will change about a year from now, when Drupal 7 is slated for release. We're dropping support for versions of PHP earlier than 5.2, which means that a host of OOP improvements in the language will be at our disposal. Some obvious candidates for OOP refactoring are already in the pipeline: PHP 5's improved PDO database library is OOP by nature, so we'll be using it under the hood. There are, though, dozens of other parts of Drupal that might benefit from an OOP refactoring. Should we take advantage of that in Drupal 7? Why not make 2008 "The Year Drupal Goes OOP?" The easy answer is, "It already works." One of geek guru Joel Spolsky's cardinal rules is, ["Rewriting from scratch is a mistake."](https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/) All the gains you think you'll make by tossing out your old crufty code and starting fresh are usually wiped out by the energy needed to recreate years of tricky debugging and carefully balanced edge cases. And make no mistake -- reimplementing Drupal with PHP's OOP language features would be a complete rewrite. Because so much of Drupal's code is dedicated to achieving OOP-like functionality in procedural code, it will require fundamental rethinking of core architectural issues like "hooks" and "nodeapi" and "how we store data." So, does that mean we shouldn't use OOP? No. The flip side of the coin is Crazy Bob's comment about the "Continuous Tax." Drupal's use of PHP makes sense for a CMS that needs to be accessible to a wide audience. But the lack of language features in earlier versions forced Drupal's developers to roll their own solutions to the problems OOP is often used to solve. The developer time we need to invest in ongoing maintenance and enhancement of these custom solutions is the "Continuous Tax." How do we balance these two imperatives? That's an excellent question -- and a lot of great answers came out of the sessions and conversations at Drupalcon Barcelona. In my next post, I'll take a stab at summarizing some of them. 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: Drupal.org Redesign Panel Discussion" url: "/articles/drupalcon-drupalorg-redesign-panel-discussion" type: article date: 2008-03-07 updated: 2016-04-07 --- # Drupalcon: Drupal.org Redesign Panel Discussion # Drupalcon: Drupal.org Redesign Panel Discussion Notes from redesigning Drupal.org By [ Angie Byron ](/about/angie-byron) March 7, 2008 The Drupal Association decided on Wednesday night that their number one priority for the next year was to focus on planning, coordinating and raising funds for a redesign of Drupal.org. In this panel, Nedjo Rogers talked about the aims and lead-up to the redesign. Tiffany Farriss discussed the scoping into an RDF, Angie Byron gave an update about the progress so far, Daniel Zhou talked about how his pivot module will aid in module evaluation, Derek Wright talked about improving the developer toolsets. Finally, Kieran Lal talked about the fundraising efforts, and then it ended with an open discussion. NEDJO ROGERS -- Aims and lead up to the redesign. Some of the aims for the redesign was that Drupal.org should continue to engage the community, getting through the RDF process, identify the remaining work at the implementation level and help promote a fundraising campaign. Nedjo is a relatively new member to the Drupal Association (DA). The DA needs to support the Drupal community, and that is represented if we take a look at the founding statutes of the DA. Last night, there was a meeting of the permanent members of the DA where the redesign of Drupal.org was decided to be the number one priority for the next year. This has to be DA's number one priority. What are some specific goals for the Drupal.org websites? There are two major goal areas: 1.) Increase Drupal adoption and market share and to get the word out to various groups, and to showcase what's possible with Drupal. 2.) To increase the quality and capacity of Drupal development. Drupal.org is the primary contact for learning and carrying out development. What is the Status of Drupal.org currently? It's evolved organically over the years. It's got huge strengths, extremely active user base and a unique setup with skilled and dedicated community managers, but at the same time there are growth pains and needs. The current site predates the exponential growth of Drupal, and there are clear pains and needs. So Drupal.org needs to build on and support the Drupal community, and so it needs to strengthen the current community. TIFFANY FARRISS – Scoping of the Redesign Tiffany is a strategist, and has a lot of experience with dealing with RFPs. So what needs to be done to get ready for an RFP for Drupal.org? The work that we need to do now is to look at the strengths, challenges and opportunities for Drupal.org and create a targeted RFP for Drupal.org. The RFP will be pointed for an IA firm with wireframes and to have a single design. Design by committee does not work, and they need to just pick a firm that has a aesthetic and IA vision along with a firm deadline to work towards. We already have some insights and user profiles, and we need to harness this knowledge and condense it into the RFP. We also need to be clear with who is going to help manage the project and who will be involved with the process. We need to get thoughtful and coherent responses from the firm for how they will participate with the Drupal community, and to make our home a better place for us and for other new people coming into the community. ANGELA BYRON -- Progress so far We've done some initial research, and we don't need a marketing firm to tell us who the community is. So who uses Drupal.org? What are the different personas? 1.) Mary the Manager -- She's told by the dev team that we're using Drupal. She needs to get into Drupal.org and figure out who's using it, how is it being used in various different sectors -- government, non-profit, commercial. How can she get Drupal to do what she wants. 2.) Wendy the Webmaster -- She's used to HTML developing sites by hand or even developed her own proprietary CMS system and got tired of maintaining it. Needs to know what modules are available, and where to get site recipes for how to build a wiki, etc. 3.) Danielle the Designer -- She's concerned about how to get Drupal not look like Drupal, and how to convert PSD file to theme. 4.) Dan the Developer -- He's got a CVS account and developing modules, and needs all of the latest developer news. So we have to be sure that we don't have a redesign where the end result conflicts with the goals for different personas, and for example, make it impossible for devs to get the information they need. So what about splitting up sites into subsites -- Right now, we have Drupal.org and groups.durpal.org, and so we're discussing splitting it up into more sub-domains. 1.) drupal.org 2.) downloads.drupal.org 3.) docs.drupal.org 4.) developers.drupal.org This would allow us to have more modules on the sub-domain sites that are more specialized -- like having a wiki module on docs.drupal.org or the diff module from developers.drupal.org. It will also allow us to have a person or team maintain the themes area. But there are also a lot of challenges with this approach -- like it's annoying that you have to log into to g.d.o and d.o. And no way to aggregate all posts across both sites, and not being able to search across all sites or view issues and docs in same search There are still some issues and open questions: then we could have a panel page on the developer.drupal.org site that gives relevant information. So at the moment everything is shown in d.o., but this would allow more navigation and specific landing pages with more targeted info for each persona. \[Showing Dries' survey chart results\] Dries also asked the community what should be improved on d.o. Finding modules sucks, search sucks and finding documentation sucks. And so these are the areas that we're going to need to improve upon. We're planning on having a top-down process with the IA and design of the site because design by committee doesn't work in this case. We need to choose a firm that we trust. We're also going to have a bottom-up approach to come with our own solutions for module-finding and other pain points that we have. So again, we're going to have two approaches: 1.) IA and design of the site by a small firm. 2.) And the community will handle the implementation of custom Drupal coding where a lot of them can be handled by DROP tasks where people can help get involved. It will help empower people to actually get things into their own hands and to help get stuff done. KIERAN LAL -- This is actually a fairly complicated process, and can be a landmine issues when a lot of the work is done by volunteers. Specifically, what happens if you start paying people? Navigating this is a difficult issue, because you don't want to upset people. The first step was to make it the number one priority for the Drupal Association, which took 8-10 months of work. And we haven't found the DA lead for this project yet. In about 3 months, there will be the d.o. doubling and our servers are predicted to crash again. But we're trying to avoid that with a lot of help from Narayan. If you do a redesign and add more features, then you have more load. So by May 15th, Drupal.org should crash again, and so they've added more servers to be prepared for that. But we've now built the political will, and also built up with what is going to be needed on the infrastructure backend. DANIEL ZHOU -- Aiding module evaluation w/ pivots I'm going to present about pivots, which is an approach to make it easier to find and evaluate pages. Wow, there are a lot of modules, and it's hard to know whether or not a module is good when you're on a page, and it's also hard to find similar modules. You could use ratings or votes, and you could also use quality metrics like number of downloads. These haven't been implemented yet. The approach that I've been able to has been to mine information from the Drupal.org forums and to make recommendations. Try out the pivots module on the project pages like on the scratch drupal site at: http://scratch.drupal.org/project/tinymce -- which is for the TinyMCE WYSIWYG Editor. Notice that there's a block on the right-hand side that show related conversations -- ranked by the how recent it is. This makes it easier to see if the module has been active. You can post a message in the forum, and it'll show up on the module page. Another nice thing is that if you click through to the conversation, then it'll list the other modules that were mentioned in the conversation. So it'll be easier for you to get to other modules. The module that they're using is to find matches, for example the devel and image module. It will display the related modules on the module project page, and will rank other posts because TinyMCE and FCKeditor are mentioned in conversations over and over again. It'll be going onto Drupal.org real soon. This will able you to access a module, and find complement and substitute modules -- this will be available as a contributed module as well. NEDJO ROGERS: This will not be the complete solution, but through the redesign process we're able to already create some improvements right away. DEREK WRIGHT -- Improving developer toolsets Helps maintain the project.module. There are a lot of different problems with finding modules on Drupal.org -- there's 3000 project nodes right now, which was great for 2-3 years ago. But the explosive growth means that the current system can't deal with that. Now it's time to make my characteristic Drupalcon plea for help. We need more tools evaluating modules -- \[showing endless scrolling on the module page\] There are also a lot of other things that are very useful, and things that are in the works -- quality and popularity metrics, which are very valuable because they can be calculated automatically. Things like: \* Does this module have any official releases? \* Does it have any documentation? \* How many open bugs are there? \* How often are their CVS commits? \* How many people have downloaded it? \* How many sites are pinging back with the update status? And we can display this information on the side, and it gets a score of say "15" and different people will interpret this info differently. Like just because there's not a lot of CVS activity doesn't necessarily mean that it's a bad module. So it's not the end of the story -- like we probably need humans to give feedback as well. Like modules bundled into installation profiles and other site recipes. But we need to help stop the bleeding first, and so take a look at this two issues -- http://groups.drupal.org/node/6186 -- which is an aggregation of the conversations about the project node redesign. Also http://groups.drupal.org/node/7191 -- which is a page of project quality metrics -- assembled all of the discussions and gave his feedback for what needs to done and when. Summer of Code -- Drewish worked on project module metrics and created a whole framework to plug into these metrics and wrote a plug-in that fits into update\_status.module. It's all about ready to go, and they still have some discussions about how it should be displayed. Users should be able to click a node and go see a view of different metrics. Should also be able to a page and sort by how many people are using a specific module. Last thing is that a lot of possible solutions, the main roadblock is that it is determined by gnarly code within project.module -- but now views exist, and there are 4-5 people who understand project queries and all of the insane project query code, but about 4000 people who understand views. So maybe we'll have a contest with all of the available fields from a database dump, and then let people create a number of different views from it. So come to the code sprint on Friday to help finish Views-ifying the project.module. KIERAN LAL -- Fundraising Kieran first got involved in understanding Drupal through CivicSpace, and then OSCON in 2005, and laid out three things 1.) Need legal entity to take money and handle legal issues 2.) Need more usability testing 3.) Improve Drupal.org, and improve most important code of the project.module. 10,000 people cooperate via the project.module on drupal.org, and so it's vital to the community. Nedjo jumped in and maintained the project.module back in 2005. Kieran then moved into fundraising role. Now let's take a look at the different ways of raising money. Drupal Associations has a four-part funding strategy 1.) Memberships of the DA -- where you get two benefits: get listed on the website, and you're able to show a logo 2.) Started advertising on Drupal.org/hosting, and they're on Drupal.org/paidservices & Drupal.org/books 3.) The Ad on the features list of Wordpress.com rakes in about $1 million dollars at $7 a pop. Drupal should be able to make $2-3 million if we put a box ad there. For example, Drupal.org made more in 14 days with a box ad on hosting page than in 3 months of Google Ads. 4.) Drupal hosting -- According to drupal.org, there are 11 companies in the world that do Drupal hosting. But we have a list of 70+ companies and about 25,000 hosting companies in the world. Most of the hosting services in the world don't have a presence on Drupal.org. Matt Mullenweg has said that a real benefit with the ad relationship with hosting companies is that anytime there is a dispute with the company, then it gives them leverage to get it fixed. Looking at Consulting companies on Drupal.org -- 30 are listed on Drupal.org, and the list is about 10x bigger, but it's weird and hard to get listed on Drupal.org. Have to put some time and money into making these lists more comprehensive. He's with Acquia, and was able to sell that Drupal was an amazing community to investors. So there is a lot of talent on the DA who are able to have skills in fundraising. Michael Meyers & NowPublic.com went out and raised $13 million for their website. Laura Scott, CEO of PingVision just did Popular Science, and they know how to scope a large site. With a little bit of effort and focus, they can raise $1-2 million for the Drupal.org redesign, and the potential is for there. May not need all of that money, especially if Angie can get an army of High School students to come and help out. So you'll be able to put it on your Resume that you were involved in the Drupal.org redesign, and I'll give you recommendations on LinkedIn. It'll be an amazing year for the Drupal.org redesign. Some people try to evaluate how much the code of Drupal.org is worth -- and arrive at an approximate value of $27 million, and look at the community and size of the community and value the community at $10 million or so. So maybe we need to also open up our data. The more you give, the more you get. Focus on being able to give out the data, and see what people can do in terms of mashing it up, visualizing it and making it more accessible for everyone. QUESTION: Four different personas, four different designs and cross-linking? Joomla.org has their own forum site and download sites that have different looks -- The different sub-domains should look as similar as possible, and just be different underneath the hood. QUESTION: What SPAM controls in forum postings in order to game the pivot module. Not sure what to do about that yet. As an aside, there are two modules, the PHP implementation and it doesn't scale very well. They also have another implementation in Java, which is a little bit faster and thinking about building in a Lucene engine. QUESTION: Is the pivots module searching lists.Drupal.org? No, they're not searching that data QUESTION: The listserves are drying energy from the forums, and discovered lots of energy in the listserves. Shouldn't you tear down the listserves entirely? I mean, wouldn't it set a high goal for the redesign of the website that we'd want to be eating our own dogfood? But it's tearing down our community to have a separation between our forums and support listservs. There is an OG2List module, it's a great idea and it makes sense, but it's a lot of work. We do need to get the listserv discussions available via search. But it's a fairly large red herring to go down that path. It's on the nice-to-have list, but it's complicated. We spent $10,000 doing scalability and testing so that the OG2list so that it could handle 10,000 people, so would need to raise a lot of money to implement something larger than 10,000. And why do that when mailman works just fine? QUSTION: Working on the newsletter, had a large discussion about creating a standalone news site on Drupal. Set up a working group and proof of concept -- http://Drupal-newsletter.org -- where you can sign up to become an admin. He didn't previously know about the redesign efforts, and just wanted to alert people to the newsletter efforts the possibility that it could be included within the redesign efforts -- possibly news.Drupal.org -- Personal vision is to create model of other news organizations. The larger question here is -- I have an idea of foo.Drupal.org -- there a zillion of them, who are willing to do them. Things like security.Drupal.org, planet.Drupal.org, design.Drupal.org -- people have the skills and ability, but then there's a quality problem. For example, there's the bluebeach theme maintainer of Steven Wittens who got upset of the lack of quality on the front page posts and in the project in general. We have to have the quality and the resources. Keep forging ahead. QUESTION: Any work on the Drupal slogan and other marketing and tackle other aspects? Yeah, should we redesign the logo? And it was cute two years ago. And do we need to revive the brand and slogan? There are 300 people with very strong opinions about that. QUESTION: What's unclear is how are we going to go about making decisions from a distributed group of people. There will be fragmented efforts on little sub-parts, and so how do you formalize decision-making in a distributed environment? Formalizing the chaos is a bad idea. DA is the only formalized structure, but most of the work should continue to happen in the organic way in a way that it always has. The proposal of the Drupal Association has restricted the money spending to the IA and design, and the technical implementation will be left up to the community. The branding and marketing, and overall navigation and structure of Drupal.org needs clear-decision making from the DA. So what's probably going to happen is that we'll get a team of about 10 people to get together an RFP for the IA/design firm. And then they'll be a decision-making process for which firm to choose, but not get into the details of choosing design colors. And we don't want to formalize the distributed implementation of custom coding tasks, and just stick with the normal scratch-your-own-itch way that this type of stuff has always been done. So you should split off a working group onto groups.drupal.org where you can discuss the higher-level aspects and come to a final decision, and then come back to the community with some actionable issues that reference all of the previous g.d.o discussions. QUESTION: What about listing current status of maintainership of the module? Derek is in favor of announcing status of the project with an additional taxonomy like "abandoned," "maintained," "paid maintainership," etc. Derek is against adding an easy way for pinging the module maintainer directly, because he's already overwhelmed with people who haven't even read all of the release notes, documentation, etc. QUESTION: What about aggregating information, and duplication or information across sites? There's also a proposed my.drupal.org would be your profile page that aggregates all of your relevant information. There is also the idea of having an id.drupal.org that has an OpenID server that will allow profile details to be shared across the various different sub-domain sites. QUESTION: Like to see the forums disappear into different groups and to centralize it in the support forums. Especially since there are already a lot of legit bug reports in the forums Need to make it easier for people to get help for the module. The pivot module does only search the forums, and not the issue queues. QUESTION: Aggregating content from other sites as well? This can't happen this unless with have single sign on, and we also need to have search work across multiple sites. Sony or Fast Company could ban together in order to get these higher-level things happen. We could update Drupal.org to 6 if we didn't have all of these other things on the site, and we've got to solve these other issues first. And there may already be solutions to these problems. This will come from the RFP process where we get feedback from the IA people that this makes sense. QUESTION: Could we ever get api.drupal.org except for contributed modules? Yes. Here's how you can help -- Go sign up on the http://groups.Drupal.org/Drupal-org-redesign-analysis 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: "We're Writing the O'Reilly Drupal Book!!!" url: "/articles/were-writing-the-oreilly-drupal-book" type: article date: 2007-07-10 updated: 2016-04-07 --- # We're Writing the O'Reilly Drupal Book!!! # We're Writing the O'Reilly Drupal Book!!! A new book on Drupal, published by O'Reilly By [ Angie Byron ](/about/angie-byron) July 10, 2007 It's official! We just inked the deal and we're cranking up our word processors. O'Reilly's first Drupal book will be Practical Drupal Drupal Jumpstart **Using Drupal** and it will be authored by team Lullabot: Myself, Addison Berry, Jeff Eaton, Nathan Haug, James Walker, and Jeff Robbins. From our experience training aspiring Drupal developers, we know that one of the most difficult parts of building a Drupal site is figuring out which modules to choose and how to fit them all together. Drupal Jumpstart **Using Drupal** will be a project-based, guided tour of the best contributed modules, along with instructions about how to combine them to make useful websites. The topics covered will be appropriate for everyone from the complete newbie who knows nothing about Drupal (we'll have a chapter to get you up to speed), to the site builder who's done a couple Drupal sites and wants to know how to take their skills to the next level, to the uber-hacker who wants to know a couple of advanced tricks. This how-to/recipe-style book will focus on several common types of Drupal sites and offer step-by-step instructions on how to build them from scratch. We're really excited to get started on the book, however, as [Matt ](https://www.amazon.com/gp/redirect.html?ie=UTF8&location=http%3A%2F%2Fwww.amazon.com%2Fo%2FASIN%2F1590597559%2F&tag=lullabot-20&linkCode=ur2&camp=1789&creative=9325) and [Robert](https://www.amazon.com/exec/obidos/ASIN/1590595629/lullabot-20) know fully well, book writing takes a lot longer than anyone would like. Expect the book to be oriented toward Drupal 6 and released next year. We'll keep you posted on our progress! 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: "Voting is now Open for the Blue Drop Awards" url: "/articles/voting-is-now-open-for-the-blue-drop-awards" type: article date: 2013-04-17 updated: 2014-05-05 --- # Voting is now Open for the Blue Drop Awards # Voting is now Open for the Blue Drop Awards Give a shout out for your favorite Drupal sites! By [ Jared Ponchot ](/about/jared-ponchot) April 17, 2013 Voting is now open for the 2013 Blue Drop Awards. Lullabot is honored to have helped design and build three sites nominated for Blue Drop Awards this year in three different categories. [Tech Guy Labs](https://bluedropawards.org/best-media-website/nominees/tech-guy-labs) has been nominated for *Best Media Website*, [PAC-12 Videos](https://bluedropawards.org/best-sports-website/nominees/pac-12-videos) for *Best Sports Website* and [Tizen.org](https://bluedropawards.org/best-association-website/nominees/tizenorg) *Best Associate Website*. All of our nominated sites are using Drupal 7. #### Tech Guy Labs [Tech Guy Labs](http://techguylabs.com) is an innovative site that supports Leo Laporte's nationally syndicated radio program, The Tech Guy. The site is unique in that it breaks each episode of the show down into individual segments like call-in questions and news stories. This allows Leo's staff to include more detailed transcripts, and it also allows listeners the ability to add their own input to on-air questions. The site allows for rich content to support each episode segment while still providing an attractive overview of each episode. In addition geocoding allows the Tech Guy Labs team to easily build interactive maps of caller questions and stations that air the show. This site was designed and built entirely from concept to launch within 12 weeks. #### PAC12 Videos The PAC12 Conference wanted to create a TV everywhere experience where subscribers to the Pac12 cable network would be able to access and watch Pac12 Network content across the web, tablet, and mobile devices. The site is unique in that it provides an immersive video experience and integrates the feeds of Pac12 Network content and the authentication systems of multiple cable MSO's who carry the Pac12 Network. Lullabot led the architecture and development of the [PAC12 Videos site](http://video.pac-12.com/videos) from its inception. #### Tizen.org The [Tizen.org](https://tizen.org/) website is a fully responsive destination for the Tizen developer community, and serves as the central hub for Tizen's documentation, events, and communication. This site was a complete overhaul and was built using a lean agile process that integrated both design and development for a cohesive and clean experience. Tizen.org serves as the primary destination for Tizen's developer community, so it has a high degree of visibility and importance within the Tizen open source community. We'd love your support at the Blue Drop Awards this year, so [log in](https://bluedropawards.org/user/login) and vote today! 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: State of Drupal by Dries Buytaert" url: "/articles/drupalcon-state-of-drupal-by-dries-buytaert" type: article date: 2008-03-03 updated: 2016-04-07 --- # Drupalcon: State of Drupal by Dries Buytaert # Drupalcon: State of Drupal by Dries Buytaert An update on the Drupal project from it's founder By [ Angie Byron ](/about/angie-byron) March 3, 2008 Dries Buytaert announced that Drupal 7 development will have 3/4 of a year dedicated to innovation assuming 100% unit test coverage and support from the Drupal community. Projected code freeze will be somewhere around 10/15/08. There was also a huge push for making fields first-class citizens in preparation for making Drupal a cornerstone of the Semantic web. More details about **State of Drupal and the State of the Drupal community** down below. Only released Drupal 6 a month ago, February 13 -- worked on it more than one year. Compared to Drupal 5, had more contributors, 400+ to 740+ Almost double the number of contributors and number of patches. \* Drupal 6 has many more features. \* Extended permission schemes. \* Added OpenID \* Added action and triggers. \* Drupal 6 is easier to administer, added an installer, and it looks much nicer and friendlier to use. \* Drag and Drop people seem to love \* Signatures improved -- lots of UI improvements \* Easier to Theme Drupal 6, and easier to pick up for designers. Should see a lot of themes next year. \* Lots of new APIs, including the theme\_devel.module to take theming to the next level (applause) \* Drupal 6 is also more secure \* update\_status.module was one of the biggest feature requests, which can do e-mail notifications for updating modules \* Also removed PHP filter module to make it more secure. Password String checker to make passwords more secure. \* Easier to scale Drupal as well. \* Central logging server. \* Conditionally load PHP files to reduce the load. \* Block-level caching, which as a big performance improvement \* Database for master-slave arrangements to use more database servers \* Lots of scalability work done \* Drupal 6 is also easier to developer for \* Drupal 6 is for more people -- localize interface and internationalize content. **SUMMARY:** More featuers, easier to administer, easier to scle, easier to theme, easier to develop for, more people. \* Thanks to the 100s of developers. \* Thanks to OSU OSL \* And thanks to thousands of people who help support, document and evangelize. GROWTH OF DRUPAL If you look at the downloads -- Drupal 6 was downloaded 100,000 times in the first month compared to 50,000 for Drupal 5. Continue to double with each release. 20,000 Drupal installations are pinging back, and quite amazing to see how many are out there already. Drupal 6 is exactly what we expected it to be: AWESOME. The state of our union is strong. A lot of the work we did last year, will bear fruit this year, and likewise for the work we do this year. Despite our successes, we still have a way to go "Crossing the Chasm" is a book that is written about this. When are *you* going to buy one: 1.) LATE ADOPTER: Never. Not until hell freezes over. 2.) LATE MAJORITY: Until I've seen enough of them and I know they will get serviced well. 3.) EARLY MAJORITY: After most people have switched. 4.) EARLY ADOPTER: I'll be (or was) the first on my blocks A lot of people are watching Drupal, but we still have to make the transition to become mainstream. It's an extremely difficult transition. History that this is a "do or die" transition. This requires an unusual degree of unity. It’s very hard to recreate momentum after you lost it. We must move forward very hard to make it happen. \[shows some Google trend stats\] -- It's important for Drupal to maintain this momentum. To move forward, and to make that transition, at least 3 things need to be done. 1.) Redesign our home drupal.org \[applause\] -- where we gather and congregate 2.) Drupal 7 has to be a "killer" release 3.) Must make some strategic, but sweeping changes. Drupal 6 improvements + growing install base + growing ecosystem = new group of users Every major release brings a new and larger group of people. \[Showing screenshots of evolution of Drupal admin interface\] New people come on board, and they're not developers, and this transition has been happening and needs to evolve the UI even more. "Bandwagon" effect: We get a new group of people and will be a model for the next group of people. We did great in Drupal 5 and Drupal 6, and there's one place where we need to adjust our house to accommodate the next batch of users. (i.e. Drupal.org) Current group of users is still in pain. Ratings for modules, better documentation, faster page loads, better search, better navigation, Drupal services, user ratings, new themes, e-mail notifications Special project for 2008: drupal.org redesign. Help plan at http://groups.drupal.org/drupal-org-redesign-analysis We need resources (might be hard) money:, project management, develop resources, designer resources, willingness to accept change. Let's talk a bit more about Drupal 7 Did a survey where he collected 1000 responses over 30 days Want better media handling, custom content types, WYSIWYG editor, better performance, tools to organize content, basic view-like module, automatic upgrade... \[more in the graph\] Developer features: performance for authenticated users, views module, node access system... \[more in the graph\] What should the focus be for Drupal 7? 24% focus on end users 44% Enterprise 32% Web application developers Lots of dev e-mail lists of whether Drupal is a content management system or a developer framework. What does this all mean? Put it all together, and it's quite easy: 68% of respondents want Drupal to focus on Drupal as a product 32% focus on Drupal as a framework 7 end user features and 3-developer ones 1.) Better media handing 2.) More CCK Content types & fields 3.) WYSIWYG 4.) Better performance 5.) Better tools organization tools 6.) Basic views-like module in core 7.) Automatic upgrate 8.) Improve node access 9.) Better internal APIs 10.) Better external APIs (import / export) Can't completely trust statistics 85% of respondents have at least one Drupal website already set up. But how many people give up on Drupal? \[photo of a frustrated user giving Drupal the finger\] Usability testing -- Saw people fail for 3 days in a row. They were common tasks that take us 30 seconds to do, it to 30 minutes in the usability lab to find. Quote from the usability 'I didn't expect to feel so stupid. I don't like feeling stupid.' Another one: 'I already lost the past I created' -- not promoted to the front page. We came out of the lab with a lot of insights (talke about in more detail here: http://tinyurl.com/22lkrv) They used heat mapping software. \[showing eye tracking movement movie\] \[Lots of laughter about the eye tracking software of someone’s eyes darting all over the Password checker after typing and causing a red error like message -- lots of laughs, and Dries says: It gets less funny after a while\] Several evaluators backed out of certain admin pages because they thought they had broken something due to the red warning box text. Continue to make Drupal easier to use. Candidates for feature removal: Remove the throttle and ping modules. Who will be the next branch maintainer? Looking for such a person at the moment, who has these qualities: 1.) Experience 2.) Responsibility 3.) Time to work on D7 4.) Have a vision for D7 I should have taken a booth in the job fair to recruit a branch maintainer. Didn't pick Gabor after 2 months of development on Drupal 6. When will the code freeze be? Preferred frequency of Drupal releases: \* 65% people that 1 major Drupal release per year is ideal \* 20% people that 2 major Drupal release per year is ideal \* 14% people think that 3 major Drupal release per year is ideal \* 1% people think that 4 major Drupal releases is ideal Historically, we’ve had a 5-month development cycle, and then 7-month code freeze to iron out the bugs. If we assume that we’re staying with a 1-year release cycle. That would put a code freeze should be on May 15, 2008 -- which seems a bit short. So Dries is making an exception with some conditions: 9-month development cycle + 3 months bug fixing IF Drupal has 100% unit test coverage Puts the code freeze at October 15, 2008 \[Applause\] It's up to us to generate the test coverage, otherwise he will be forced to shorten it. Other reasons to do unit testing: Design for testability leads to better APIs Encourages experimentation and innovation Another reason is to fund the next Drupalcon You will contribute one dollar per test that your patch broke -- \[chx standing ovation\]. \[joking of corse\] Drupal 7 and testing Write unit test, functional test and integration tests ship core tests with Drupal core still have to figure out what test frameworks to use, and if we can ship them with core as well. Been thinking about this, and he encourages more discussion to happen. Lime.php testing framework, is really small. Doesn't cover all functionality. Simpletest also doesn't cover all Drupal functionality either. Drupal 7 and Beyond. We’ll be preparing for more wagons and going mainstream. We will be moving from infinite extensibility to infinite interoperability. \* Web 1.0 = Web content management \* Web 2.0 = Web 1.0 + user management + infinite extensibility -- Drupal has strong user functionality to handle thousands of users, and thousands of Drupal modules. \* Web 3.0 \[Dries personally hates the term\] = Web 2.0 + infinite interoperability = Web 2.0 + data portability + web service APIs = Semantic Web. "Dude we've been talking about the semantic web for 10 years. Pass the rack pipe, please!" \[laughs\] It's the right time and the right place for the Semantic Web. There’s going to be Less focus on functionality -- more focus on data \* Integrate data from different sources \* Allow other people to reuse your date \* Decentralize data so that no party owns all the data \* You can do cool stuff with lots of data Exporting your data so that it can be imported \* Extend Drupal so it supports multiple output formats (e.g Generate XHTML, XML or JSON for a node. \* Figure out when to serve what output format (e.g. different responses to different HTTP headers) Data API group on g.d.o. -- More power to fields and less power to nodes \* Decompose content into small pieces \* Treat fields as first class citizens instead of notes \* Connect the fields with semantic sugar \* Put that into Drupal 7 ASAP An event node in Drupal 6 -- the relational data model Organizer: Acquia Band: Orbit ... etc An event node in Drupal 6 -- the relational data model Orbit is a band Band plays at event band starts on date band ends on date You have a subject, predicate and ojbject Any knowledge in this world can be decomposed in RDF triples of subject predicate and object So a lot of these problems have been solved at the design level. \[Shows example of FOAF file that generates XML\] Another thing about RDF is that everything can be described as a graph. RDF works with multiple data sources as well Connect our event with other external information about Orbit SQL -- relational database syntax is similar to SPARQL - RDF graph. Can access multiple data sources from one query. RDF makes the Internet ONE BIG DATABASE "SPARQL = Views on Steroids" Let me show you a video of the future -- implemented in Drupal implemented by Arto -- got a lot of the pieces here. Look at Wikipedia, which is the OLD model of one big text field. Can export the same info in RDF in a machine-readable format Can add a SPARQL query, which will import data for different sources. Show the results of the query on the next page. Shows a table of data as the result. Type -- Can export in all different types. Look into events in Boston http://simile.mit.edu/exhibit/ Import into Google spreadsheet -- and then export out in to a Google Map. Real easy once you have the pieces of technology. Scraped Drupalcon username info and combined it with the drupal.org module maintainers list, and created a faceted search so that we can see all of the German-maintained modules or drill down in any different ways. See where people are coming from. \[Pointing to an Asian arrow on the map\] This person is one with Jetlag. \[laughs\] "The future is here. It's just not evenly distributed yet." Visualization graph of example of all of the RDF data that is out there including: DBPedia, US Census data, RDF Book Mashup, W3C WordNet, CIA factbook, FOAF, Musicbrainz. The opportunity is that every single Drupal site can be an RDF repository that we can start to mash into to semantic web \[overlaying Drupal on these websites\] The Social graph just connection people -- we have an opportunity to make a graph that connects EVERYTHING. We’re moving from the World Wide Web (WWW) to the Giant Global Graph. Tim Berners Lee This is a big opportunity \* Better search \* Better targeted ads \* Deeper integration \* Better personalization \* Import/export \* CCK and views in core \* Drupal.org integration In thinking about the drupal.org redesign, with things like SPARQL, it'd be great to be able to get different views of the RDF view We should get some of this crack in Drupal 7 More power to fieles \* XML documents are to exchange messages / date \* RDF tis used to make it uniform, predictable and to untap it's potential RDF to create distributed views Keep it fast The semantic web aligns well with my vision for Drupal \* Make Drupal really easy to use \* Eliminate the webmaster -- no need to write HTML \* Eliminate the developer with CCK and Views - assembling the pieces \* Eliminate the designer -- more tools like Views and CCK for the designers \* Eliminate the publisher -- have the users control how he / she wants to see and experience the data The world is watching to see if anything can be made of Drupal. We must move forward 1.) Work on a better home. 2.) Work on the Drupal 7 killer release 3.) Prepare for the semantic web and help drive and push innovation -- it really is the future The future is *here*, and it is in this very room. 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: "Do It With Drupal: Earl Miles (Views) and Karen Stevenson (CCK) Profiled" url: "/articles/do-it-with-drupal-earl-miles-views-and-karen-stevenson-cck-profiled" type: article date: 2008-11-12 updated: 2014-05-05 --- # Do It With Drupal: Earl Miles (Views) and Karen Stevenson (CCK) Profiled # Do It With Drupal: Earl Miles (Views) and Karen Stevenson (CCK) Profiled By [ Jeff Robbins ](/about/jeff-robbins) November 12, 2008 [ ](http://www.doitwithdrupal.com/) ![Do it With Drupal Seminar](/sites/default/files/styles/wide_xs/public/diwd-home-news-white.png.webp?itok=M8iuFfU1 "diwd-home-news-white.png") For those of you who haven't been keeping up with the [Do It With Drupal blog](http://www.doitwithdrupal.com/blog), we've been posting profiles on some of the speakers who will be appearing at the event. Recent posts include short profiles of [Earl Miles](http://www.doitwithdrupal.com/blog/earl-miles-point-views) (author of Views) and [Karen Stevenson](http://www.doitwithdrupal.com/blog/karen-stevenson-puts-k-and-s-cck) (co-maintainer of CCK). Whether or not you're planning on coming to the event next month, it's important to recognize the achievements of the people contributing such wonderful work to the Drupal project. Karen and Earl are just two of the [list of great speakers](http://www.doitwithdrupal.com/speakers) appearing at [The Do It With Drupal Seminar](http://www.doitwithdrupal.com) in New Orleans. Check out [the schedule](http://www.doitwithdrupal.com/schedule) for the latest list of sessions and topics. We're really excited about the way things are coming together. Seats are still available and we hope that you'll be able to join December 10 - 12 in New Orleans for this exciting event! 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: Zen and the Art of Drupal: The Philosophy of Drupal Development" url: "/articles/drupalcon-zen-and-the-art-of-drupal-the-philosophy-of-drupal-development" type: article date: 2008-03-07 updated: 2016-04-07 --- # Drupalcon: Zen and the Art of Drupal: The Philosophy of Drupal Development # Drupalcon: Zen and the Art of Drupal: The Philosophy of Drupal Development How we work on Drupal By [ Angie Byron ](/about/angie-byron) March 7, 2008 The philosophy that has guided the code in Drupal core is one of the biggest assets of the Drupal project. But it's never been disseminated in a structured way to the community and it's also evolved over time. With the rapid growth of Drupal and the number of new people coming into the community, then it's more and more important that Drupal be more proactive and intentional in promoting the design philosophies that have helped fuel Drupal's growth and success. In the final session time slot of Drupalcon, Jeff Eaton and James Walker kicked off this conversation about "The Art of Drupal." The first idea is that Drupal won! It is no longer a question that Drupal can't be used. It's no longer the new kid on the block. Industries are starting to look to Drupal first, and that's awesome and exciting. With all software, it's not perfect, and won't be used in every case every where. But it's now being used by very large businesses and communities. We not only have legitimacy in being able to do big complicated projects, but also to be able to drive technology forward with innovation. There are other standards organizations like OpenID that are looking at what Drupal is doing and is something that we should be proud of. James still remembers a basement in Antwerp, Belgium, when Drupal was still the new kid on the block. Let's look at the lines of code as an indicator of our growth. In 2005, core Drupal was about 35k lines of PHP code. Today, it's 50k code with about 8% code -- and that's not even including comments or whitespace. And if you look at contrib, it went from 300k in 2005 lines to nearly 1.8 million lines of code today. Contrib has added about a million and half lines of code in those 3000 modules over the last three years, which doesn't even include different branched versions of the modules -- that's just the HEAD versions. This is the work of the community trying to solve interesting problems. So Drupal contrib is moving at a frighteningly-fast pace. That's a 3-year sprint with things like CCK and Views getting a chance to evolve in contrib. Before that, building websites looked very, very different and we can do some amazing things now. A lot of hard things have been solved in contrib like with Views and CCK, and the user relationships for mapping out social graph relationships, and we've ironed out a lot of things and changed how those things get built. You used to have to grab core, and then the contrib modules were actually named by the feature that they gave you -- like "event.module." Now it's more like a lego-building block approach of taking small bits of building blocks and putting it together. The Form API is a huge win that now allows other modules to plug into each other, and a bunch of modules can combine with each other in the process of capturing clustered user input. Modules are interacting together to create an aggregate experience. You have to throw five modules together for building an image gallery instead of just one. Event module still exists and is still used, but the direction is moving towards the small pieces being glued together, which creates new sticky problems. We're moving towards more importance on the fields and a finer-grain focus. \* Nodes vs. Everything -- Should users be nodes? There are solutions in contrib that are layered on other solutions \* Nodes vs. Fields \* Output formats (XML, JSON, RDF) to tie into other web standards -- these are questions that are starting to be answered in the contrib module world. These are more layers of complexity that we're starting to build on top of other layers. It's not an easy thing, especially when you're coordinating volunteers from all over the planet. \* Configuration management How do we do that in a way that won't kill us in a way when we start to build a big site? Or start to build towards solving the other tough problems? We can only stack so many dishes on top of other decisions before we need to go back and re-architect some fundamental issues. Design Debt: increases if you do not refactor: until the cost of adding new features is greater than it would be to code them from scratch. This is why Drupal is so willing to break APIs in order to prevent the design debt from building up. QUESTION: As commercial entities enter, they want slower releases. Most people want faster releases according to Dries' slides. From a pure project perspective, it's one of the ??? things we face. The problem isn't building cool websites or aggregating content. The next challenge is how do we make the Drupal design, and how the pieces click together, and how do we catch that up with the other 3-year code sprint. This is the beginning of a conversation that needs to start, and we'll be pointing to some existing articles that have been written about these things. FIVE POINTS -- Here are some general principles that we've been thinking about: 1.) Code is for People. A lot more time is going to be with people looking at the code than you're going to spend writing it. So code clarity is very, very important. I've released a lot of codes without any documentation, so Eaton is part of the problem with this. Part of the problem is that we all have to understand all of the pieces, whether it's by a function-by-function, or whether it's at the API-level, it's critical that we consider that our code is part of a collective conversation of how websites get built and how to solve problems. This doesn't mean that you have to write a 50-page manuel for each module that you write, and that we shouldn't release a solution that isn't pretty. One of the main goals is for other people to understand what we write. 2.) Loose Coupling means Happy Code An example of how this applies in Drupal, all of the bricks don't know they're part of a lego car, they just know what's immediately around them. On the other hand, the other car on the other side was designed to be a red car. As we build pieces that connect to each other, they have to have knowledge of what they can connect with. As we're building pieces that connect to each other, the fewer assumptions of what will be there, then the better. For example, image cache resizes images and image field takes an image and puts in a field on a node, and they communicate very well with each other. With both of these first two concepts is that when you contribute something to a community that is this size, then they're going to take that code and try to do something with it. The clearer that you've said how it'll work and the less assumptions that you make, then the better. We are usually scratching our own itch in a specific scenario, and if you contribute it back, then other people are going to be using it in a different scenario. With image field and image cache, it would've been easier to build image cache on top of image field, but since it's split from each other, then other people have been able to use image cache with the profile.module, and it's a much more useful tool that way. 3.) Love your Hacks No real actual website that has to ship someday -- will always have code that specializes. It'll have to have a hack, and you can love them as hacks and not as lego pieces. Remember when you're doing a hack vs. building a lego piece for someone else. Sometimes it's best to say it's site-specific and interesting that made my site better and faster, and it could be spun off in a more generalized way later. This is one of the ways that our community way and value of sharing can come back and bite us. Sometimes these things would make good articles or blog posts, but aren't worthy as standalone modules. You're going to spend more time to take the hack that worked in your environment and try and put it out in community. Like to make a rotating banner, then he just make a view, make an image field, and then write 10 lines of PHP code to auto-switch. Love your hacks, but document them for why you did it. So that five months later when a security patch comes out, then you can go back and remember. Or when you have someone else is coming in and have to deal with your hacks. 4.) Layers Protect You When you're building a module that does something complicated or exposes an API for awesomeness, building layers of functionality protects you and others. If you expose 100% of every single edge case -- then although it may be awesome and you want to solve cases -- but the issue is that it's own module to expose the data to other blocks and node types. Separating those things from each other is a very good thing. It creates a better design, because it's shielding you from the complexity of an XML-based webservice like Amazon.com. Then may have CCK fields and other modules that do one special thing, the stacked layers are good. But they need to be designed to work that way. If you start with the layer with Amazon integration, and you have the first layer of the guts - integrating with the service, which is unit testable, and you can see that the bottom layer is working properly and see what changes to the lower layers have. You want to build on top of a strong foundation, and not on top of a house of cards. The take-home point is that when you build tools that other people will be building things on top of and iterating with other modules. Then you need to be sure to think about what is the bottom layer and who's job is it to serve what type of node type. Then it's easier to change the user interface as well. 5.) Simple Means Less From the usability study results, there's no way to make something simpler than by taking things away. You can shuffle around, but sometimes the only way to make it simpler is to take them away. The better your layers are, the easier they are to strip off. Layered code exists in a lot of places. Modules that are the heart of many sites are written like that. Views Core and the Views UI and fairly separated out, and it's possible to have a different UI for views. That's what we're trying to encourage. In some parts of Drupal, we have kept on adding lots of information in collapsible field set over the years, and now have 747 cockpit syndrome on our admin pages. In Drupal 6 with Actions and Triggers interface, there's a lot more to the system that isn't integrated in the UI. Another important issue is that this is not an information dump. Design is a conversation in this open source project. How data storage should work? How should views be integrated? How should we interact with outside system with JSON, XML, etc? These are the types of questions that we should be thinking about when we remember that we should write code for other people, keep our code loosely coupled, love our hacks for what they are, separate functionality with different layers, and keep things simple when possible by stripping out edge case functionality. There will be a series of articles that eaton and walkah will be writing about this that will continue this discussion. They're serious issues, and we can't ignore the design debt. And the fact that we can deal with them now means that we have succeeded and it can allow us to think about the future. QUESTION: Is there a model to capture site configurations in some sort of way? Yes, the Patterns.module is one that is trying to capture that -- http://drupal.org/project/patterns 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: "Drupalcon Szeged, Day 1" url: "/articles/drupalcon-szeged-day-1" type: article date: 2008-08-27 updated: 2014-05-06 --- # Drupalcon Szeged, Day 1 # Drupalcon Szeged, Day 1 By [ Addison Berry ](/about/addison-berry) August 27, 2008 Whew. So day 1 of Drupalcon Szeged was a whirlwind of fun. Szeged is a very cool town. Lots of things going on, easy to get around and the weather is awesome! I'm not a good note-taker at all but here are the highlights for my day: During Dries' State of Drupal presentation, he announced that webchick is the [new maintainer for Drupal 7](https://www.lullabot.com/articles/webchick-named-drupal-7-maintainer). Of course, Angie rocks and is taking the year by storm. It has been so cool to see her recognized so much for all the amazing things she has done and continues to do. I'm so proud to be not only her co-worker, but also her friend. Inspiring to say to the least. Right after that I headed off to give my [Gentle introduction to Drupal coding presentation](http://szeged2008.drupalcon.org/program/sessions/gentle-introduction-drupal-coding) (a PDF of the slides is on that page too). This was the first time I'd done this presentation and I got lots of really good feedback from folks who attended. It was quite a bit of fun and I look forward to expanding on intro coding types of presentations in the future. We had ourselves a tasty lunch and then headed back for the second keynote by Rasmus Lerdorf. He went through some very cool stuff about optimizing PHP code and ran some analysis of various frameworks (like Cake, Code Igniter, etc.) and Drupal. We didn't come out so well in the performance side compared to a lot, but Rasmus acknowledged that it wasn't an apples to apples comparison. The Q&A period afterwards was awesome. Next on deck, I went to the [Intro to testing](http://szeged2008.drupalcon.org/program/sessions/testing-part-1-intro-testing) session given by Angie, Florian and Charlie Gordon. I now have the basic idea, so I feel prepared to earn lots of chocolate at tomorrow's testing party. That one starts at 9 am so we are also going to be provided with Hungarian pancakes too! I wrapped up my day o' sessions by doing to the [DrupalChix BoF](http://szeged2008.drupalcon.org/program/sessions/drupalchix) with Angie and about 20 other women. It is great to see the DupalChix gatherings gaining in size and this was definitely only a fraction of the total number of women here. The official tally reported 10% women in attendance, which is a nice increase from the 7% of the previous 2 cons. After sessions ended I was in a meeting regarding the drupal.org redesign and then had dinner with a bunch of people in Szeged. 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: "White House's Open Source Plans Previewed at Drupal Meet-Up" url: "/articles/white-houses-open-source-plans-previewed-at-drupal-meetup" type: article date: 2009-11-17 updated: 2016-04-07 --- # White House's Open Source Plans Previewed at Drupal Meet-Up # White House's Open Source Plans Previewed at Drupal Meet-Up The White House New Media gives a talk on their switch to Druapl By [ Brock Boland ](/about/brock-boland) November 17, 2009 The White House New Media team broke its silence to the Drupal community at the [DC Drupal Meet-Up](http://groups.drupal.org/node/34998) on Monday evening by [giving a brief talk](https://vimeo.com/7669508) providing some context on their switch to the Drupal content management system and broader efforts for having the US government openly participate in and help foster open source projects. After the initial [announcement of WhiteHouse.gov going Drupal](http://techpresident.com/blog-entry/whitehousegov-goes-drupal), aside from a [post from Dries,](https://dri.es/whitehouse-gov-using-drupal) and a round of commentary and reactions summarized [here,](https://www.lullabot.com/articles/white-house-drupal-coverage-the-roundup) there hasn't been a lot of new public information about the [White House's](https://www.whitehouse.gov/) switch to Drupal and their future open source plans. But that changed after Monday night when three members of the White House New Media team spoke at the Drupal meet-up in Washington, D.C. ## What are the White House's future plans with open source? The biggest news from the night was a few announcements about the White House's plans for engaging with open source development communities. Dave Cole, the White House Deputy Director for Technology, said that the White House New Media team has been working with the White House legal council to figure out how to participate and contribute code back into the Drupal community. They can't promise a timeline for when that'll happen since it's pretty unprecedented for the Executive Branch to be participating in an open source project and to be directly engaging the Drupal community. They also want to start holding "Development Challenges" for the international Drupal community in order to help figure out some ways to take the best ideas that are already out there, and see how they can be used for the public good. The White House New Media team is also looking to solidify all of this into an event coming up early next year. The specific details and timing is still uncertain, but they're looking to hold a "camp here in DC for open source developers to both collaborate on what public sector development should look like in open source as well as ways that people can engage with the White House" and specifically the Executive office of the President. Sounds something like a "White House Open Source Drupal Camp" to me. More details in video uploaded by [Development Seed](https://developmentseed.org/)/> and other Drupal-specific highlights down below. [White House New Media Team on Using Drupal](https://vimeo.com/7669508) from [Development Seed](https://vimeo.com/developmentseed) on [Vimeo](https://vimeo.com/). ## White House New Media Team Excited to be Leveraging Open Source Macon Phillips, the White House Director of New Media, started off by expressing their excitement to moving to a content management system that will give them a lot more flexibility. They're interested in ways of using technology to help amplify the President's message, but also to open up the White House and work on pushing forward other transparency and open data efforts from the government perspective. They also want to help create opportunities for people to participate in government, and leverage the open source community and development towards that goal. Then Dave Cole talked about why the White House changed their CMS platform, what they actually built, and where they're going next. ## What was the White House's motivation to switch to open source? Obama came into office using the previous CMS technology from an existing contract from the Bush era, but the White House New Media team was able to change the design and information architecture of that proprietary CMS. They continued to work on new projects and built out tools to expand functionality, but their moment of insight came after integrating the [Google Moderator for their Open for Questions event.](https://techcrunch.com/2009/03/24/white-house-using-google-moderator-for-town-hall-meeting/) They successfully integrated Google Moderator, but they saw a lot of opportunities to do a lot more if they were to collaborate with open source communities. They also realized that "it didn't make sense to reinvent the wheel every time" that they wanted to integrate collaborative crowdsourcing tools. And they seriously started to investigate open source alternatives to better leverage their time and resources. ## Maintaining the Existing User Experience Cole then gave the floor to Nick Lo Bue, the Creative Director, and he had the opportunity to say a few words about the goals of matching up the user experience of the original re-design within Drupal. From a creative perspective, Lo Bue was pretty frank in expressing some of the challenges he faced by saying, "The best thing designer wants to hear is that there's no content management system. The second best thing that they want to hear is that it's a closed source content management system. And the worst thing you want to hear is that it's open source." Apparently, the plug and play nature of open source created a lot of inconsistencies in the user experience, and Lo Bue said that they had to push their development partners hard to not abandon their commitment to the brand experience, findability and accessibility that they had already created. They clearly wanted to expand that initial experience from what was available from the proprietary CMS, but they had a good foundation and start that they wanted to properly translate to Drupal with enough diligence and attention to detail. It'd be interesting to hear more from Lo Bue as far as whether Drupal was able to be flexible enough to be able to match all of their biggest user experience priorities, but that's about all he said on that front. He moved on to talking about that there are many different Drupal modules that are available, but just because something is available doesn't mean it's the best option. They have a clear desire to call upon the community to get more feedback on different modules. They've been learning as they've been going on, and they intend to rely upon the community even more as time goes on. Cole reiterated the importance of the creative vision for the success of the project and encouraged developers to collaborate with designers whenever possible. ## Some Drupal Functionality on WhiteHouse.gov The biggest functionality that the New Media team was looking for was dynamic and linked content and data. So both in terms of flexible ways of presenting and finding information, but a clear commitment towards being an early adopter and provider of semantically linked data. One of the biggest improvements to the site was using [Apache Solr](http://drupal.org/project/apachesolr) as the search engine because it allows for content to be found in so many new ways. Their search used to just be a keyword search, but now they are getting a much more dynamic experience with faceted search. They're also looking for ways to expand that with customized search alerts that can be e-mailed out like Google Alerts. They also have a lightweight implementation of RDFa in order to provide linked data with a lot of their primary source content, and have it exposed in that way. They're trying to reverse the trend of sending out data in the more locked-down format of PDF. They also spent a lot of energy on figuring out how to have a collaborative development platform in order to incorporate a lot of different groups in the development of the project. According to the [Tech President post](http://techpresident.com/blog-entry/whitehousegov-goes-drupal), they had a lot of different partners involved with the development process including [General Dynamics Information Technology (GDIT)](https://www.gdit.com/) as the prime contractor, and the subcontractors of [Acquia](https://www.acquia.com/), [Phase2](https://phase2.io/), [Terremark Federal Group](http://www.terremark.com/) and [Akamai](http://www.akamai.com/) as the CDN. It sounds like the White House New Media team had to create a new collaborative infrastructure for all of these subcontractors in addition to their own in-house development team. And as with anything at this level of government, security was probably a primary concern and probably a reason why many of the involved development shops have been relatively quiet about their involvement. The New Media team hopes to be talking more about their collaborative development platform in the coming weeks and months to come. But Cole said that there's a lot of rigidity when you have a site that needs to scale as much as whitehouse.gov, but they still really wanted to maintain an agile development cycle. They're also really concerned about open collaboration tools, and they had already implemented comments, ranking, Facebook live chat, and were happy to see that a lot of these are already built within Drupal. So they're leveraging a lot of code that the community has already developed. ## Where are they going next? As mentioned in more detail above, Cole talked about how they're discussing with their legal council the logistics of contributing code back to the Drupal community. They're looking to start engaging open source developers more both with Development Challenges and plan on having an White House Open Source "camp" early next year focusing on how to best leverage open source software for the public good. ## QUESTIONS ### How much customization was done on the project? Most of the functionality was already available from Drupal, and most of their custom development work was around scalability. They did a lot integration work with the Content Delivery Network that they're using (Akamai) in order to host national events, to provide an additional layer of security, and to be able to refresh pages automatically as users input new content. ### How much time spent on project? They didn't have a total number available, but Mason did say that Dave Cole spent a lot of time researching CMS options. He also said that most of the time was spent trying to figure out procurement logistics, security issues and the cultural issues of working with open source. What was really exciting was that the time spent was more about educating the right people and walking through the implications, rather than dealing with obstruction or obstinence. ### What is your hosting strategy? They have to be careful for how much they can reveal for security reasons, but Cole did say that "there are multiple instances of the site that are running in redundancy and synced with each other in different geographic locations." And in addition they have a CDN wrapper around that for global distribution. ### What are the biggest features that you want to add next? They have about a list of at least 60 feature requests for the project, but the number one issue is user account management and figuring out a user authentication system. This issue happens to have a lot of privacy implications, and it's not just matter of the technological implementation of user account management, but they're also considering the social media best practices of single sign-on and OpenID. They have to thinking about dealing with the range of users from skeptical to technologically immersive users. Another feature is a way to save a search, and then send out e-mail alerts whenever new information appears containing that keyword search. Overall, they have a lot of considerations to think about in order to things done, and they're hoping that this is showing people their commitment to where they want to go in terms of collaborating with the Drupal community and engaging other open source projects for the public good. 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 Art of Presenting" url: "/articles/the-art-of-presenting" type: article date: 2010-09-03 updated: 2014-05-07 --- # The Art of Presenting # The Art of Presenting How to create presentations that entertain & inform. By [ Matt Westgate ](/about/matt-westgate) September 3, 2010 I had the opportunity to present to a local organization in Salt Lake City on the techniques Lullabot uses for making kick ass presentations and how we streamline our presentations so they're reusable amongst our teaching staff. Watch the video below and [download the slides](https://www.lullabot.com/sites/lullabot.com/files/Matt%20Westgate%20-%20The%20Art%20of%20Presenting%20by%20Lullabot.pdf). - Ditch PowerPoint. PowerPoint prefers boredom, repetition and information fatigue. - Do a cold open and talk about something relevant: the weather, the setting, world news. - Kill the bullet points. People don't retain bullet points. They retain the story. - Don't apologize for a demonstration not working as expected. Either figure it out together with the audience or simply move on. - Simple, but not simplistic. You're audience is smart. A one-by-one reveal of bullet points is simplistic. A large photo with a brief message is simple (and brilliant). - When creating reusable slides, use learning objectives and outlines to supplement the presentation while leaving room for each presenter to share their stories. - Make it fun! Connect the presentation to what you're passionate about. 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 Gets a Latin GRAMMY!" url: "/articles/drupal-gets-a-latin-grammy" type: article date: 2012-09-06 updated: 2014-05-07 --- # Drupal Gets a Latin GRAMMY! # Drupal Gets a Latin GRAMMY! Lullabot Launches LatinGRAMMY.com By [ Jeff Robbins ](/about/jeff-robbins) September 6, 2012 After three straight years of successfully designing and launching [GRAMMY.com](https://www.grammy.com/), Lullabot is pleased to announce the re-launch of the [Latin GRAMMY](https://www.latingrammy.com/) website. The Latin GRAMMY Awards honor music that is recorded in Spanish or Portugese and attracts millions of viewers on event night. > The Latin GRAMMY Awards telecast is one of Univision's most watched events, ranking as its highest-rated special. Last year's 12th Annual Latin GRAMMY telecast was watched by 11.1 million viewers in the United States who tuned in to all or part of the live three-hour broadcast on the Univision Network. The show delivered an average audience of 5.7 million total viewers 2+ and positioned Univision as the No. 1 broadcast network for the entire night among adults 18–34 for the second consecutive year. The 12th Latin GRAMMYs also generated more than 25 million impressions on Twitter throughout the evening, helping the Latin GRAMMYs and Univision win the night for social TV as well. Additionally, the Latin GRAMMY Awards on Univision continue to attract more Hispanic viewers in the demographic groups of total viewers 2+, adults 18–49 and adults 18–34 than the combined audiences of the biggest awards shows on primetime television. The Latin GRAMMY Awards telecast is broadcast to more than 100 countries. — from [GRAMMY.com](https://www.grammy.com/news/the-latin-recording-academy-univision-announce-new-deal/) The previous Latin GRAMMY site was powered by a custom CMS built on Ruby on Rails. It allowed for different content types, categories and translations. The new site is built on Drupal 7 and standardizes the editorial workflow between Grammy.com and the LatinGrammy.com website. We'll soon publish a more in-depth article on how the team, made up of [Brock Boland](https://www.lullabot.com/who-we-are/brock-boland), [Jerad Bitner](https://www.lullabot.com/about/jerad-bitner), [David Burns](https://www.lullabot.com/about/david-burns), [Angus Mak](https://www.lullabot.com/about/angus-mak), and [Ben Chavet](https://www.lullabot.com/about/ben-chavet), handled the content migration and other challenges. Check out the new [Latin GRAMMY](https://www.latingrammy.com/) website! 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: "Do It With Drupal Schedule and Early-bird" url: "/articles/do-it-with-drupal-schedule-and-earlybird" type: article date: 2009-09-24 updated: 2014-05-07 --- # Do It With Drupal Schedule and Early-bird # Do It With Drupal Schedule and Early-bird By [ Jeff Robbins ](/about/jeff-robbins) September 24, 2009 We've posted the first draft of this year's [Do It With Drupal schedule](http://www.doitwithdrupal.com/2009/schedule). It's still got several missing pieces and we need to link it up to [speaker bios](http://www.doitwithdrupal.com/speakers) and full session descriptions, but the broad strokes are there and we're getting really excited about the 2nd annual DIWD. We've also got an early-bird registration rate of $895 which ends tomorrow (Friday) night. So get off your butt and register already! Find all the specifics at http://www.DoItWithDrupal.com We look forward to seeing 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: "Attacking the Glue Code Problem" url: "/articles/attacking-the-glue-code-problem" type: article date: 2007-07-13 updated: 2014-05-08 --- # Attacking the Glue Code Problem # Attacking the Glue Code Problem By [ Jeff Eaton ](/about/jeff-eaton) July 13, 2007 My last couple of posts to the Lullablog have mostly been rumination about the state of Drupal site-building, and the need to make certain kinds of tweaky tasks easier for folks without PHP skills. Some of these gaps are difficult to close, but others are pretty simple once you realize them. The key is isolating the specific problem that is common to lots of sites, and figuring out how to solve it in a simple, easy-to-reuse fashion. Want an example? Yes, I thought so. Over the last year or two several large-scale Drupal projects (including MTV UK and the New York Observer) have used custom CCK content types for their section 'landing' pages, daily cover pages, and so on. This technique works really well: it lets editors and content managers tweak the details of "The Front Page" without disturbing the 'live' version, tools like Scheduler and Actions/Workflow can be used to put additional editorial workflow controls on the new front page layouts, and there's no need to whip up a special maintenance UI for the various topical/sectional landing pages. Since they're nodes, you get them for free. The trick is that you want the url for your section, or your landing page, to always display the right node. For example, http://www.newspaper.com/section/entertainment should always display the latest published 'front\_page' node in the entertainment category. Without manually mucking around with path aliases every time you make a new node, there's no way to do that without custom code. This kind of stuff is easy to tuck into a single function or two in a custom module, but it's still a hurdle for folks that don't have PHP experience. So! There's our problem: make it possible to set up custom url paths that display the latest node of a particular type, at a standard bookmarkable location. I took a look through code that we've written for clients in the past to do similar things, and found a snippet that I could re-use. When a user hit the module's custom url path, it loaded a pre-defined view, picked the top node, and displayed it. I gave it a bare-bones UI and added a couple of convenience features (like support for caching, to reduce load on high traffice sites). The result? [The Top Node module.](http://drupal.org/project/top_node) The heart of the module is only 50 lines or so of code; the complicated part was putting a nice editing interface onto it. (This demonstrates the proof of my earlier comment: most 'glue code' tasks are pretty simple, to the point that it takes more code to give them a user interface.) There are already a couple of feature requests for it, and a handful of people are interested in helping add them. Overcoming the challenge of 'glue code' in Drupal doesn't have to be painful. It just requires developers to step back, identify the common things that we have to tweak and hack on every site, and spend a bit of time to make it reusable. Let's brainstorm -- what small, granular bits of functionality do YOU find yourself hacking together on every Drupal site you build? And how can you turn that 'glue' into a useful tool? 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 Drupal in Video" url: "/articles/using-drupal-in-video" type: article date: 2013-07-11 updated: 2014-05-08 --- # Using Drupal in Video # Using Drupal in Video The O'Reilly book is being rolled out in videos By [ Addison Berry ](/about/addison-berry) July 11, 2013 We've started a brand new series of series! We are turning the great O'Reilly book, [Using Drupal, 2nd Edition](https://www.oreilly.com/library/view/~/9781449305543/), into videos lessons. Instead of being just one series, it will be thirteen individual series—one for each chapter of the book (plus appendices). We've created a [new guide on Drupalize.Me](https://drupalize.me/tutorial/invoking-rules-events) to house the new series as they come out, and you can see that the first two are already up there. In addition to the first series, [About the Using Drupal series](https://drupalize.me/course/about-using-drupal-series-preface), the first video from the [Drupal Overview chapter](https://drupalize.me/course/using-drupal-chapter-1-drupal-overview), "What is Drupal?," is also free. 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: "Closing the Drupal Module Repository: My 2¢" url: "/articles/closing-the-drupal-module-repository-my-2" type: article date: 2008-04-01 updated: 2014-05-08 --- # Closing the Drupal Module Repository: My 2¢ # Closing the Drupal Module Repository: My 2¢ By [ Jeff Robbins ](/about/jeff-robbins) April 1, 2008 By now, most of you have probably heard about the plans to close the Drupal Contrib Repository (DCR) to new module projects. While I understand and generally agree with most of the reasons for this decision, I wonder if it isn't a bit hasty or shortsighted. First let me outline my understanding of the reasons behind this decision: There are currently close to 2,750 modules in the DCR. These are the modules that have been created by and are maintained, to a varying degree, by the community. They are not part of the core Drupal distribution and they're quality, performance, and usefulness varies greatly. While there are general guidelines that module developers are supposed to follow when contributing their work, many developers misinterpret or outright ignore these guidelines. Many developers contribute modules which have names which are confusing and jargony (Login Toboggan!?) or just similar to other modules (Feed, FeedAPI, SimpleFeed, Feed Field -or- Link, Link Node, Link to Content, Link to Us, Links Block, Links Views RSS, Links Package). And watching the feed of new modules, it seems like the vast majority of new modules duplicate the functionality of existing projects in one way or another. These developers may feel they have come up with a better way to implement a given feature, but the truth is that usually their modules are just confusing in different ways. Additionally, a simple download of the DCR is almost 500MB. In addition to the bandwidth costs shouldered by the Drupal Association, we need to consider Drupal administrators across the globe who have only a dial-up connection. A download of this size would take almost a week over a 56K modem connection and that's assuming that no one tries to call in during the download. By closing the DCR to new modules, more focus can be put on improving the modules that are currently in the repository. Additionally, more developers can, and should, focus on the themes, theme engines, install profiles, and translations repositories. Since I am an American, I don't really know much about the translations repository, but there is certainly a lot of work that can be done creating more and better themes, theme template engines, and installation profiles. For instance, there are only four different contributed theme engines and this includes Zengine, which is poorly maintained, poorly documented, and isn't really a theme engine at all. Where is the FrontPage theme engine? Clearly more work needs to happen in this area. Perhaps better theme engines would lead to better themes, an area which also appears to be lacking in the DCR. A cursory review of the themes directory leads one to believe that Drupal does not lend itself well to building beautiful websites. I agree that more effort could be put into creating more beautiful, creative themes to act as an example of the flexibility of Drupal's presentational layer. Certainly Drupal's explosion in popularity has lead to an explosion in modules offering a great wealth of flexibility. And I can understand the want to focus on the quality of the existing modules over quantity. And yes, modules like CCK and Views certainly offer administrators the flexibility that they need without having to write another module. But completely closing the module repository to new projects!? Of course, I am unable to think of any functionality that a website might need which doesn't already have an existing module that can handle it. But we're talking about the future here! Potential advances in science and technology could merit a new module or two over the next ten years. Perhaps something to do with the SkyNet project, or to integrate with alien technologies during the takeover. I just think that we, the Drupal Development Community (DDC), should keep our options open, never say "never", and continue to foster development efforts in the hopes that one day someone may have a new idea for a module to improve Drupal and thus, the Internet. 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: "How Drupal Will Save The World" url: "/articles/how-drupal-will-save-the-world" type: article date: 2007-06-20 updated: 2014-05-08 --- # How Drupal Will Save The World # How Drupal Will Save The World By [ Jeff Robbins ](/about/jeff-robbins) June 20, 2007 *For almost a year now, I have been gestating an idea about Drupal fitting into a larger world view. As part of the development community, our focus is making Drupal as flexible as possible in order to meet our own needs and those of our clients. In this process, we put into place most of the pieces that position Drupal as a solid, free, easy-to-use website-building application for the rest of the world. This is the first draft of my thoughts, which will grow and evolve as people within and outside the Drupal community contribute their feedback. Thanks in advance for your comments and input.* ## How Drupal Will Save the World A few weeks ago, I attended the [NetSquared](http://www.netsquared.org/) conference in San Jose, CA. The conference's mission is "to spur responsible adoption of social web tools by social benefit organizations". This basically means: *Web 2.0 for Good*. The conference brings together web experts with non-profit organizations, philanthropists, and investors in order to come up with ideas to make the world a better place. I was amazed at the number of groups who were either already using Drupal or setting up their system to use Drupal. Others were doing technology research and asking questions to which the logical answer appeared to be "Drupal". ## What is Drupal? If you are unfamiliar with [Drupal](http://drupal.org), just imagine it as a giant bin of free Lego-style building blocks for creating any type of web site. Drupal is an incredibly powerful platform. Its modular system and underlying application framework can allow rapid deployment of incredibly feature-rich sites. All you need to do is imagine what you want to build and start putting together the pieces. Drupal's extensive user permission system has made it known as a community site building platform, and its underlying API allows plug-ins to alter data, create pages and content, and change the way a site works. In addition to basic blog-like text-based content management, this flexible system has spawned modules to manage image, audio, video, add date/time-based information to any content and display it on a calendar and/or in an iCal feed, tag any content with geo-location data and then plot it on a map or allow searching by geography. There are modules to add ecommerce functionality, manage user buddy lists and allow private messaging between users as well as set up public and private groups within a website where users can set up their own special interest groups with their own mailing lists, calendars, etc... and more and more (over 700 right now) of these building blocks are being contributed by an international community of programmers every day. ## Giving a Voice to the Voiceless My favorite story from the NetSquared conference comes from Kim Lowery of [Kabissa](https://kabissa.org/) who [talks about](https://www.disney.com/) a village in Nigeria that had agreed to let an oil company access their land and resources in exchange for clean water and school buildings. After a few years of letting the oil company get what they wanted, it became apparent to the people of this village that the oil company was not going to fulfill its end of the bargain. Ten years ago, these villagers would have had no recourse. But these days, they have their own website and the leverage that goes with it. They scanned the original contract and posted it on their site and followed up with a few emails to Amnesty International, Human Rights Watch, and the like. And before long they had a campaign going to bring people's attention to the oil company's failure to deliver on its promises. A few months later, the oil company began showing up to the village, taking more interest in their needs, and began delivering on some of their promises. The web can be a powerful force for social change. It can give a voice to those who might not otherwise be heard. It can also bring together those with similar needs empowering both with the simple knowledge that they are not alone. The participants at NetSquared recognize this. And many of them have turned to Drupal as the platform on which to begin this empowerment. They have been impressed with how flexible the application is and how they can add large chunks of functionality quickly by using the many free modules. However there was hesitation to use Drupal because of word-on-the-street which went a bit like this: "Drupal is what you want. It will solve all of the problems that you're having. But it's really hard to set up. And don't do it wrong or your site won't work at all!" ## Frustrations These groups expressed many of the same frustrations: - Drupal is confusing and difficult to set up. - The learning curve is steep. - They are unable to find good documentation, developers, and general help in creating and understanding their Drupal web sites. - It is especially frustrating to them because they can see the light at the end of the tunnel. They see beautiful Drupal sites offering features similar to those that they are hoping to implement, but they often can't figure out the route to successfully reach that end. These are not technical people. And they want to build sites for even less non-technical people. They want to bring Drupal to third grade teachers in Indiana, church-basement activist groups, street orphans in South Africa, or Vietnamese farmers. These people may have very little experience on the web. The idea of "filtered HTML" is likely beyond their concern. And administrative idioms such as "node", "taxonomy", "vocabularies", "terms", even "menu items" and "blocks" are usually only understood after some conscious effort. Drupal professionals and enthusiasts quickly forget how foreign these "Drupalisms" might have appeared when they first lay eyes on them. There is a saying that most Open Source software like Drupal is built **by** developers **for** developers. When the software is free and the developers are generally unpaid, there isn't necessarily a good reason to go the extra mile to make it user-friendly-- particularly when it comes to user interface. This is slowly changing, but Drupal's configuration and learning curve can still feel quite overwhelming for a non-technical site administrator. We need to get Drupal into the hands of many more ordinary users so that we can break out of this developer-centric view. And in order to do that, we need to get a lot more user-friendly. A skilled Drupal developer can piece together something like *a community events management system with discussion boards and event-related photo galleries and mailing lists* in a day or so. However to the Drupal novice, this project could easily take a several months spent getting up to speed on Drupal's core concepts, experimenting with various modules, trying to figure out what is reasonable to ask of Drupal, and deciding how to wire things together. The process of reducing complexity to ever more elegant simplicity can be done. We even know how to do it. But it will take time, money and vision to make it happen. ## Possibilities Anyone who has used Apple's *iMovie* or perhaps Apple's *Automator* can understand how even very complex operations and configurations can be simplified into a friendly user interface that the average user can begin to grasp. It's also important not to ignore the "play with it" fun aspect of these drag-and-drop interfaces. They invite experimentation which quickly leads to an understanding of the system. Drupal could become the server-side counterpart to Firefox. It could be to web servers as [Ubuntu](https://ubuntu.com/) is to the desktop -- as beautiful as it is flexible. It could be inviting to end users rather than off-putting; fun rather than frustrating. It could spark the imagination and inspire even novice web administrators with its many possibilities. We have a very solid foundation on which to build this vision, however I believe that we've reached a bit of a stuck place. Drupal has lots of back-end developers. Drupal needs front-end users. We need a way to "prime the pump" and introduce front-end people to the project. We need to show them what Drupal can do. We need to expand beyond our current base of techies. As a village becomes a city, it needs to address needs like paving the roads and removing the garbage. Small problems become large as the community grows. The road workers and garbage collectors need to be paid. Work such as maintaining the drupal.org documentation, forums, the CVS repository, or tuning the drupal.org servers used to be small tasks. They are now large tasks. The Drupal Association was created to address some of these needs. It funds support activity *around* Drupal's code such as purchasing and maintaining the hardware to run drupal.org, and organizing Drupal conferences. However most of these changes need to happen *within* the code, reaching the limits of what the association is responsible for. Drupal is outgrowing volunteerism. I believe that if Drupal is to grow to its full potential, it is time to start paying people do do these essential tasks. ## What If... What if we had unlimited funds to grow Drupal into its full potential as an indispensable web tool that is simple to use and lets people get control of maniacally complex data. Let's think big! The groups that I talked to at NetSquared had many common problems with Drupal that they are trying to solve in individual ways. What if we take an umbrella approach to fix all of these problems and more? What if we could assemble an expert team of Drupal developers and provide them with all the guidance and resources they need? What if we could make it our goal to make Drupal the best website building application the world has ever seen? Where would we start? What would we do? Here are some ideas: - Drupal's user interface and configuration system need to be overhauled with the guidance of usability and user interface experts. In Drupal-fashion, any new interface conventions need to be flexible and available to all modules. An interface style guide should be written to provide guidance and recommendations to developers as they create new modules. - Drupal should have a core library of beautiful and self-explanatory icons and design elements available to developers to help facilitate clear non-text interface elements. Icons use less screen real-estate, are quick for the eye to find, and do not require translation. - Drupal.org should be an inspirational Drupal site with lots of marketing-style information. It should offer easy-to-find resources for both new and experienced Drupal users. It should provide clean, solid, documentation as an easy barrier of entry. - [api.drupal.org](http://api.drupal.org) should be maintained, easier to use, and benefit from Drupal's strengths as a community-building platform. [PHP.net](https://www.php.net/) makes its developers feel like they have all the development information they need right at their fingertips. There are comments and guidance on almost all of its functions. There is no reason that our site couldn't offer many of these same features. It might even be possible to extend the site to encompass some, if not all of Drupal's contributed modules. - We should talk with "ordinary" web users and ask them what features they want and expect of an easy-to-use content management system. These people often don't get much of a voice in the uber-technical Drupal community and their requests for items like a solid, easily configurable, highly integrated WYSIWYG editor go unanswered. We need to hear and understand the needs of these users and address them one by one. - Drupal needs more extensive and sexy themes to allow site builders to quickly get what they are looking for. - Drupal should have maintained, funded distributions (pre-configured packages of Drupal with add-on modules and themes) to act as a quick-start launch pad for various common website types. - Drupal should have maintained, funded translations of both core and popular contribution modules. - Drupal needs marketing experts to promote it and bring it to the people. Simply "being good" isn't good enough. People need to hear about it. - More effort needs to be spent exploring parallel non-HTML delivery of content. Drupal already excels in this area, but it should be clear and easy to set up SMS or WAP interfaces for Drupal as well as text-to-speech and telephone-based audio content management interfaces so that those without web-access can still benefit from Drupal's strengths in managing content. These problems will not be solved efficiently during evenings and weekends. This needs to be a concerted effort with funding behind it. Although perhaps the community could get there on its own, the potential benefit is so staggeringly huge that I believe that there are groups out there who would be willing to volunteer funds in order to realize this vision. ## Wrapping Up I can foresee a time when a small village in Nigeria will be able to open their $100 laptop, connect to the $100 server they have set up in their town hall, click "make a website" and effortlessly put the pieces together to communicate with the world and with each other. Or a third grade teacher in Indiana, working right in front of her students, will be able to plug Drupal modules into the school website that allow her class to exchange messages with a third grade class in India. We have most of the modules already. Our techies are developing more daily. What we are missing is the "effortlessly" part. With a concerted push, we could solve these problems once and for all and provide free, easy to understand, user-friendly software to empower anyone, anywhere on the planet, to create the website that they need, to provide the communication that they need, to make their lives better. This is how Drupal will save the world. 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: "New CCK and Views Training Videos Available" url: "/articles/new-cck-and-views-training-videos-available" type: article date: 2009-03-05 updated: 2014-05-08 --- # New CCK and Views Training Videos Available # New CCK and Views Training Videos Available By [ Matt Westgate ](/about/matt-westgate) March 5, 2009 [ ](http://store.lullabot.com/products/learning-views) ![LearningCCK-cover-small.jpg](/sites/default/files/styles/wide_xs/public/LearningCCK-cover-small.jpg.webp?itok=37lQmLH1 "LearningCCK-cover-small.jpg") The [Lullabot Learning Series](http://store.lullabot.com/collections/lullabot-learning-series) continues with two new Drupal tutorial videos. These high-definition videos are available today as digital downloads. The two new videos dovetail together to create a single example project, beginning with content setup in the CCK video and continuing through to content listings and permissions in the Views video. **Learning CCK** is a hands-on look at Drupal’s Content Construction Kit. In this tutorial video, we show you everything from CCK basics such as adding and displaying fields to more advanced topics such as CCK’s database storage mechanisms, field-level permissions, and how to theme CCK’s output. Each chapter builds upon the last as you build and configures content types for a university job board. The running time is 1 hr 50min. Read the [full description and chapter listing](http://store.lullabot.com/products/learning-cck). **Learning Views** is a hands-on look at Drupal’s Views module. In this tutorial video, we begin with a general overview of Views’ interface and capabilities. The team then builds a complex example showing more advanced Views features such as arguments, relationships, displays, access control, exporting, and theming a view. We continue to use the university job board from Learning CCK as the sample site. Total running time is 1hr 53min. Read the [full description and chapter listing](http://store.lullabot.com/products/learning-views). [ ![Lullabot-LearningViewsForDrupal.jpg](/sites/default/files/styles/wide_xs/public/Lullabot-LearningViewsForDrupal.jpg.webp?itok=5mnUMTEg "Lullabot-LearningViewsForDrupal.jpg")](http://store.lullabot.com/collections/lullabot-learning-series) While the videos are aimed towards Drupal 6, most of the theory and knowledge is common to Drupal 5 and above. These videos will sell for $75/ea, however through March 13th, we're offering a Drupalcon special of only $50 per video, so [download your hi-res digital copy today](http://store.lullabot.com/collections/lullabot-learning-series)! 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: "Rapid Waters Team Joins Lullabot" url: "/articles/rapid-waters-team-joins-lullabot" type: article date: 2010-03-01 updated: 2020-01-22 --- # Rapid Waters Team Joins Lullabot # Rapid Waters Team Joins Lullabot Today we're proud to announce that the team at Rapid Waters Development will be joining Lullabot By [ Jeff Robbins ](/about/jeff-robbins) March 1, 2010 ![Rapid Waters Development](/sites/default/files/styles/max_900/public/u2/rwd-240.png.webp?itok=MYGtEtOv "rwd-240.png") Today we're proud to announce that the team at [Rapid Waters Development](http://rapidwatersdev.com/) will be joining Lullabot. Jerad Bitner and David Burns founded Rapid Waters, a Drupal-focused development company, in 2009 after years of development experience with Sony Music, MothersClick, and Lifetime Television. Rapid Waters recently helped Lullabot launch IgniteShow.com for O'Reilly Media. Since we began in 2006, Lullabot has been as a small consulting and education company focused on high-level technical planning and architecture, developer training and mentorship, and server performance tuning. As Lullabot takes on more projects like [Buzzr](https://buzzr.com/) and the thriving [Lullabot Store](http://store.lullabot.com), we find ourselves doing more development on both client and Lullabot internal projects. This acquisition marks the beginning of a new development division at Lullabot. These new dedicated development resources will allow us to provide more complete Drupal solutions. We're still a small company concentrating on high-end projects, and our focus will continue as a consulting and education company, however now we're able offer better hand-in-hand development resources for our clients. We're really excited to have the amazing team at Rapid Waters' join Lullabot. And we're excited to be able to better meet our clients' needs and provide more efficient resources to build and launch their sites. 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: "Command Line Basics: Install Drupal" url: "/articles/command-line-basics-install-drupal" type: article date: 2010-09-06 updated: 2014-05-12 --- # Command Line Basics: Install Drupal # Command Line Basics: Install Drupal By [ Addison Berry ](/about/addison-berry) September 6, 2010 **Note: This video is no longer available because it is outdated. You can see an updated method for [using Drush to install Drupal](https://drupalize.me/videos/using-drush-install-drupal) from the command line**. In this video we'll take on the specific task of getting everything in place to run the Drupal installation script, all from the command line. We'll get a copy of the Drupal code (using three different methods: FTP, CVS and Drush), copy our settings.php file, and then create a new database and database user. 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: "Back From Retreating!" url: "/articles/back-from-retreating" type: article date: 2013-12-17 updated: 2014-05-12 --- # Back From Retreating! # Back From Retreating! A peek inside Lullabot's 2013 company get-together By [ Lisa Kastner ](/about/lisa-kastner) December 17, 2013 ![All of the Lullabots in one place!](/sites/default/files/styles/wide_xs/public/field_regular_upload/team.jpg.webp?itok=_lfsXCCO "team.jpg") Last month, we posted a news article about embarking on our annual Team Retreat to Puerto Aventuras in Mexico! Being a newcomer to Lullabot and to a distributed team environment in general, meeting everyone in person in an amazing locale like Mexico was invaluable. From day one at Lullabot, the communication between all employees and the tools provided to do so are abundant and easily accessible. Feeling part of a real team and company is a non-issue, more so than I could ever imagine. Still, there’s something to be said for direct eye contact, body language, hearing someone’s sense of humor in person, and witnessing the passion that spills out when they present to a full room of coworkers. That face to face interaction that people working in offices take for granted is often the glue that binds working relationships. It solidifies that what you’re doing, working for, and who you are working with are “real”. !['Bots on the Beach](/sites/default/files/styles/wide_xs/public/field_regular_upload/beach.jpg.webp?itok=RhNZVMGt "beach.jpg") Since the last team retreat a little over a year ago, the Lullabot family has more than doubled in size! Organizing and delivering 43 grown adults to 4 villas just outside of Cancun Mexico is no small feat! All made it safe, sound and ready to take on the jam-packed week. Each day started with a group breakfast overlooking the breathtaking Caribbean waters and then followed with a number of meetings and presentations going over the current status of departments, projects, and various goings-on at Lullabot. Seeing and hearing this from the people who were most passionate about each topic was energizing. It helped to connect all of us to each other, and the common goals at Lullabot. Important take-away from these meetings - Lullabot has had a tremendous year thanks to all of the incredibly talented and motivated ‘Bots! !['The yearly all-company Rock, Paper, Scissors show-down](/sites/default/files/styles/wide_xs/public/field_regular_upload/rps.jpg.webp?itok=cP6u7mMS "rps.jpg") Throughout the week we typically took all meals together, helping us build the real-life relationships that carry through to IRC chats, Yammer conversations, Google hangouts during the rest of the year. Every evening there was a special event: Trivia Night, Ignite Talks, a Talent Show, a Storytelling night... We finished the week in the only way possible: with a Fiesta! Everyone was encouraged to participate in one or more of these events. We showcased Lullabot knowledge, passions, talents, hilarious stories and finished it off by celebrating our time together with not just our new coworkers, but friends. So, perhaps we could say that Lullabot didn’t really go on a “team retreat” at all… It was a “team uniting adventure!” ![underwater.jpg](/sites/default/files/styles/wide_xs/public/field_regular_upload/underwater.jpg.webp?itok=McjfyE5h "underwater.jpg") 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: "Latin Grammys Graduation" url: "/articles/latin-grammys-graduation" type: article date: 2012-10-23 updated: 2014-05-12 --- # Latin Grammys Graduation # Latin Grammys Graduation A case study in re-platforming latingrammy.com By [ Jerad Bitner ](/about/jerad-bitner) October 23, 2012 ## Requirements The previous version of the site was built on Ruby on Rails in a custom content management system that allowed administrators of the site to have content types, categories, and even translations. This was all stored in a MySQL database with a simple schema that allowed for a pretty easy migration into Drupal. The task was to re-create the entire site within Drupal, keeping the exact same content types, content, and existing design with the exception of a new homepage, new header and new footer. The new homepage design is based off of the current [Grammy.com](https://www.grammy.com/)'s homepage design. **Previous Homepage**: ![old](https://evernote.com/skitch) **Newest Homepage**: ![new](https://evernote.com/skitch) The Recording Academy wanted to re-platform this on Drupal for a couple of reasons. The first is that since Grammy.com is on Drupal 6 and will be upgraded to Drupal 7 before the next annual awards show, we could leverage some of the work in building the Latin Grammys website with Drupal 7 for the upgrade of Grammy.com itself. The second reason was to bring the site more inline with Grammy.com in terms of capabilities in content editing, advertising, and future enhancements to the site could be leveraged from any improvements that end up being made on Grammy.com. ## Migration For the migration work on this project, we decided to create a custom drush command that could run the various migration scripts. The scripts are split out by content type: each script migrating the pertinent information for a particular content type. They are comprised of a simple function that runs bootstrapped Drupal code to take data from the old Ruby database and transform it for the new Drupal database. You can find the code for this custom drush command, `$ drush lmi` in [this gist](https://gist.github.com/82e48a3749d55d1dcaa6). We then have one simple bash script that runs drush commands in succession, like so: ``` # Clean install using our installation profile written with profiler. drush site-install lagra -y drush upwd admin --password="admin" drush fra --force -y drush dis overlay -y drush cc all # Migrate taxonomy terms. drush lmi tag drush lmi genre drush lmi award # Migrate content. drush lmi event drush lmi nominee drush lmi page drush lmi photo drush lmi podcast drush lmi press_release drush lmi sponsor drush lmi video import ``` You may also notice that within that bash script, we are installing Drupal from scratch with a custom install profile called 'lagra'. This installation profile is using [Profiler](http://drupal.org/project/profiler/) and does some simple enabling of core modules, contrib modules, custom features, and sets a few initial variables within the variables table. If you haven't looked into using Profiler on your projects, you should. It allows you to do some pretty complex operations with very simple syntax right in your profile's `.info` file. You can find the code for this custom install profile in [this gist](https://gist.github.com/17ff5c4216367abaf26f). What this all leads to is a relatively simple and quite an efficient way to completely reinstall the entire project from scratch at any given point. All of the features of the site are in custom [Features](http://drupal.org/project/features) modules, and the migration scripts run automatically, so a fresh install of the project is as easy as running `$ ./docroot/scripts/reset.sh` at any given time. We found this led to a very rewarding workflow during the entire duration of the project. ## Translation Another huge requirement for the site (in fact, one we underestimated) was the use of three different languages on the site: English, Spanish, and Portuguese. For this we had to add a whole slew of `i18n_*` modules. Here's the list: - Internationalization (i18n) - Block languages (i18n\_block) - Menu translation (i18n\_menu) - Multilingual content (i18n\_node) - Path translation (i18n\_path) - String translation (i18n\_string) - Taxonomy translation (i18n\_taxonomy) - Translation sets (i18n\_translation) - Variable translation (i18n\_variable) - Views translation (i18nviews) - I18n page views (i18n\_page\_views) ### Challenges The old Ruby system had a very competent system for managing translations of content. However, not all content in the system was translated, and so there were some challenges in making sure that we could switch to a different language and not have the page show up empty simply because the content was not translated for that particular views listing. The listing being empty was not ideal from a user's standpoint, nor from a content editor's standpoint. So we opted to do a little extra work in the migration scripts for the particular content types that did not have translations. We simply created the English version of the nodes, and at the same time used the same copy for the translations. This resulted in populated views for each language, and an ideal way for content editors to browse the listings and quickly know what content needed to be translated, hit an edit button, and then translate. Another challenge we didn't account for was all of the strings that are not translated automatically. The String translation module was easy enough to use for most cases, but for the times we found it lacking, we ended up using the [String Overrides](http://drupal.org/project/stringoverrides) module to fill in the gaps. We also found that keeping string translations in code was problematic. We opted for using the `$conf` array like so: ```php global $conf; $conf['locale_custom_strings_es'][''] = array( "You must be logged in to access this page." => "Debes estar registrado para acceder a esta página.", ); ``` However, this also became an issue once we decided to start using the String Overrides module because the `$conf` array would simply override any translations we would put in the String Overrides UI. So, we opted to use this `$conf` array method to get the values into the database initially (hitting save in the String Overrides UI) and then just remove the `$conf` array. Then, we could use the UI to translate the few strings that remained. This is still something we haven't solved, so if you have any suggestions on easily keeping string translations in code (à la Features), we would love to know about it in the comments! ## Involvement The following were involved in this project at various stages and in various roles: *In order of code contributions.* ![brockboland](https://www.lullabot.com/sites/default/files/imagecache/93x93/images/entry/smile-fence.JPG) **Brock Boland: *Developer at Lullabot*** --- ![sirkitree](https://www.lullabot.com/sites/default/files/imagecache/93x93/picture-22_0.jpg) **Jerad Bitner: *Senior Developer & Technical Project Organizer at Lullabot*** --- ![davexoxide](https://www.lullabot.com/sites/default/files/imagecache/93x93/picture-21_4.jpg) **David Burns: *Senior Developer at Lullabot*** --- ![makangus](https://www.lullabot.com/sites/default/files/imagecache/93x93/images/entry/mugshot.jpg) **Angus Mak: *Developer at Lullabot*** --- ![blc](https://www.lullabot.com/sites/default/files/imagecache/93x93/images/entry/IMG_1780-cropped.JPG) **Ben Chavet: *Systems Administrator at Lullabot*** --- ![Bao_Truong](https://secure.gravatar.com/avatar/47720ff0bfbb1275ff536bb16cf08bc6?d=http%3A%2F%2Frapportive-proxy.heroku.com%2Fproxy%3Furl%3Dhttp%253A%252F%252Fmedia.linkedin.com%252Fmpr%252Fmpr%252Fp%252F1%252F000%252F07d%252F0cc%252F29d7fcc.jpg&s=80) **Bao Truong: *Software/Web Developer at The Recording Academy*** 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: "Announcing The Lullabot Learning Series Early Release Program" url: "/articles/announcing-the-lullabot-learning-series-early-release-program" type: article date: 2009-09-08 updated: 2014-05-12 --- # Announcing The Lullabot Learning Series Early Release Program # Announcing The Lullabot Learning Series Early Release Program By [ Jeff Robbins ](/about/jeff-robbins) September 8, 2009 ### Get new Lullabot videos early and get 'em cheap! After spending a while trying to figure out the best way to offer discounts to our past customers on new video releases, I came to a realization: why not just give a discount to everyone who buys the videos early? Lullabot fans can more easily complete their collection, and new customers will have an incentive to find out what all the hubbub is about. Each video in the [Lullabot Learning Series](http://store.lullabot.com/collections/lullabot-learning-series) is offered in two formats: as a downloadable video file, and as a DVD. Once we're finished with the production and editing process, the video download is pretty much ready to go. However, the DVD version usually takes a few weeks at the pressing plant and then ship to our warehouse. We've opted to sell the download-only versions of new videos while we wait for DVD versions to come in. However, since these releases aren't available yet in all formats, we're now calling them "[early releases](http://store.lullabot.com/collections/early-release-deals)" and offering them at a big discount. The end result: **purchase new Lullabot videos within the first few weeks and you'll save yourself some cash**. For instance, right now, the [Administering Drupal](http://store.lullabot.com/products/administering-drupal) video is marked down to $55 from the normal price of $75. That's more than 25% off. But how will you know when new Lullabot videos are released? By all means, sign up for [the mailing list](https://www.lullabot.com/mailinglist). Not only will you stay updated on the latest stuff from Lullabot and save some money on new releases, you'll also be just a little bit cooler than you already are. 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: "Lullabot Online Training Goes Global" url: "/articles/lullabot-online-training-goes-global" type: article date: 2009-10-07 updated: 2014-05-12 --- # Lullabot Online Training Goes Global # Lullabot Online Training Goes Global By [ Jeff Robbins ](/about/jeff-robbins) October 7, 2009 Next week, we will be starting the second 6-week-long [Drupal Fundamentals](https://www.lullabot.com/workshop/drupal-fundamentals-online-workshop-octnov/online-2009) online workshop. We launched our new training series, Lullabot Online Workshops, in September and we're really excited about how these classes have been going. Some call them "web conferences", "webcasts", or "webinars", but we're sticking with "online workshops" because we see them as an online version of our in-person workshops. ![Lullabot Online Workshop](/sites/default/files/styles/wide_xs/public/u2/OnlineWorkshop-1.gif.webp?itok=9UemFBT_ "OnlineWorkshop-1.gif") James Walker teaching an online workshop Lullabot Online Workshops combine live streaming video of our instructor, along with live presentations and screen sharing, and a sidebar chat room for students to ask questions and give feedback. I've been really impressed with how engaging, interactive, and fun the workshops have been. It really seems to capture the feeling of sitting in the room at a workshop. And the sidebar discussion provides for even more questions and interactivity than we often see at our conventional workshops. Each student also gets his/her own sandbox Drupal site where they can experiment with each week's lesson. They also have access to the workshop intranet site where they have access to the video archive of the live lessons and message boards for discussion and help from the Lullabot team. Another great thing about these online workshops is they're world-wide. The last workshop was scheduled at a time when most of the Western Hemisphere was awake. In addition to the US and Canada, we had attendees from the UK, France, Holland, Spain and Chile, and one dedicated student actually attended from Australia where he got up at 3am for the live sessions. The next workshop will be at a time more convenient for Australians and those in the Eastern Hemisphere – Monday evening in the Americas is Tuesday morning in Australia, New Zealand, Japan and Indonesia. So not only can people in the Americas attend after work, but people on the other side of the globe can attend during daylight hours. [James Walker](https://www.lullabot.com/about/james-walker) has been doing an amazing job running these workshops. I still don't quite know how he's able to seamlessly watch the chat discussion and present the curriculum, or how he's able to sit in a room by himself in front of the camera and keep it fun, but he does it... for 2 hours at a time... for 6 weeks straight. We still have spots available for next week's workshop, so if you're looking to get started with Drupal or just fill-out your knowledge of the Drupal basics, here is your opportunity. [Register today](https://www.lullabot.com/workshop/drupal-fundamentals-online-workshop-octnov/online-2009). To keep up with Lullabot ongoing events and video releases, please sign up for the [mailing list](https://www.lullabot.com/mailinglist). 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.org's search index and Zipf's Law" url: "/articles/drupalorgs-search-index-and-zipfs-law" type: article date: 2007-03-17 updated: 2016-04-07 --- # Drupal.org's search index and Zipf's Law # Drupal.org's search index and Zipf's Law "the frequency of any word is roughly inversely proportional to its rank in the frequency table" By [ Robert Douglass ](/about/robert-douglass) March 17, 2007 In preparing for [my talk about Drupal's search capabilities](http://2007.oscms-summit.org/node/9) I've been looking at all sorts of interesting things. One of them is [Zipf's Law](https://en.wikipedia.org/wiki/Zipf's_law), which predicts that "the frequency of any word is roughly inversely proportional to its rank in the frequency table". Since Drupal's HTML indexer normalizes the word scores on the assumption that this is true, I thought it would be interesting to see whether the words in Drupal.org's index actually fall into a Zipf-like distribution. For example, in the Drupal.org search index, the word *the* is the most common (as it is in the English language in general), occurring 120,306 times out of 11,182,265 (1%)\*. Zipf's law predicts that the second most common word (*and* in the case of Drupal.org) will occur 1/2 as frequently. As the data below shows, this is not the case. \* By default, the HTML indexer drops words shorter than 3 letters which means common English words such as *a*, *it* and *an* will not be indexed. Furthermore, the Drupal.org search index is full of terms (such as chx, killes and rtfm) which are not English words at all. Both of these factors influence the distribution. To generate the frequency data, I obtained a copy of Drupal.org's search\_index table and ran the following queries to calculate the count and rank of the words: ``` SELECT @m:=0; SELECT @m:=@m+1 AS rank, count(word) AS count FROM search_index GROUP BY word ORDER BY count DESC; ``` I then used the application [Grapher.app](http://www.macresearch.org/tigers_scientific_gem_grapher_app) to generate a pure Zipf curve as well as map the points from Drupal.org's index. Here is the result: ![Zipf's law and Drupal.org search index](/sites/default/files/styles/wide_xs/public/assets/2016-04/zipf-drupalorg_0.png.webp?itok=bqDkkksg "zipf-drupalorg_0.png") *Zipf's law and the words (frequency/rank) of the Drupal.org search index.* The X and Y axes are both logarithmic which makes both curves appear more or less as straight lines. The slope of the Drupal.org line is not -1, and in fact, it changes along the way. Nonetheless, it shows that the search index follows some sort of inverse rank distribution, and Zipf is likely a close fit. The graph could be made to be much nicer (for example by actually interpolating the Zipf equation on top of the Drupal.org data), but this was my first use of the Grapher.app, and I still have many things to learn about it. ## Top words on Drupal.org Below is a list of the top 20 words (out of 276046) in the search index on Drupal.org and their frequency. RankCountWord1120306the2100101and391714for487627drupal584658thi673740that773558with872421modul967653not1067338have1165356but1258015you1357437can1452618page1549019from1648498work1747842how1847406user1947394site2046835there Some of the words show evidence of stemming (via the [Porter Stemmer module](http://drupal.org/project/porterstemmer)), which is the practice of taking words like *module* and *modules* and reducing both of them to *modul*, which is the common stem that they both share. ## Calling all math-heads! Here is [a zip file with the raw data](https://www.lullabot.com/files/zipf.zip) and a basis Grapher.app file with Zipf's law. If you have skills doing scientific analysis of data, can make a better graph than I have done, or want to explore Zipf's law further, please take the data and work your magic. Please report back here with whatever you find. If you want to get started with Zipf's law in Grapher.app, here is the text that you can paste in order to get the basis equation: f({k;N,s})=|\_frac\_{{|\_frac\_{{1};{k^{s}}}};{|\_sum\_{{n=1};{N};{|\_frac\_{{1};{n^{s}}}}}}} Here is a LaTeX expression of the same: f\\left( k;N,s \\right)=\\frac{\\frac{1}{k^{s}}}{\\sum\_{n=1}^{N}{\\frac{1}{n^{s}}}} 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: "DareConf Mini Lessons" url: "/articles/dareconf-mini-lessons" type: article date: 2014-02-12 updated: 2014-05-12 --- # DareConf Mini Lessons # DareConf Mini Lessons Working on People Skills in a Digital World By [ Addison Berry ](/about/addison-berry) February 12, 2014 A few weeks ago I was in London for a few days to attend [DareConf Mini](http://2014.dareconf.com/mini/london), a conference about people skills for the digital workspace. It brings together people from the technology space, the web in particular, but the sessions and lessons really apply to human beings generally. I ended up hearing about DareConf because my friend Karen McGrane spoke at the first DareConf last fall, and it sounded like an amazing experience. When I saw they had a "mini" (one-day) version happening in January I jumped at the chance to check it out. They also had two one-day workshops to choose from, and I decided to add that to the mix since I would already be there. It was well worth the time and expense. This is one of the best conferences I have ever attended, and the experience was perfect for my current personal and professional goals. I've already signed up for the next full, [two-day DareConf](http://2014.dareconf.com/), which is happening in September (and it's half price right now, until February 17th). I've grown from a government paper-pusher to Drupal hacker to consultant and teacher, and am now the Director of Education at Lullabot, overseeing the Drupalize.Me product and managing a team of seven people. Each step up in my journey has always been about communication in many ways, but managing a team is a specific skill set and one that I've stumbled into but haven't stepped back to make a concerted effort on. I think this is true for most people who end up in management. This year I am determined to put full effort into digging in to *understanding* my role as a people manager, instead of relying on my natural communication skills to see me through. I think I'm a good manager and director. I want to be great at it. I went to DareConf looking for guidance and resources for me to become a great manager and inspire the Drupalize.Me team. I also want to provide the same for my colleagues in the Lullabot team of directors and Lullabot as a whole. They are all awesome people and I want to make sure I'm figuring out how to help them express their individual awesome, while also leading our company as a place where the sum is greater than its parts. So, with that heady goal, here is what DareConf was about and why it was the perfect place for me to land. ## The Conference Day The conference itself was only one day, but it was wall-to-wall goodness. Each session was nice and short at 25 minutes, which is a pattern I'm seeing at more conferences, and I really hope it becomes the de facto standard at some point. It's so much more digestible and the speaker has to get to the point quickly. Each speaker related the topic they were tackling through a personal story related to it, and they all wrapped up with practical advice and potential next steps we could take to explore the topic. Topics ranged from conflict resolution, coaching and mentoring, commitment, and being human. I paid attention through the whole day, and wasn't burning time working on email during sessions. It was great. The best part of the sessions for me though was the question and reflection period after each one. In the schedule booklet that describes the sessions, there were four questions for each session, and plenty of space for writing. At the end of each session we had five minutes to reflect on the session, ponder the questions, and jot down our thoughts. It was brilliant. The questions made you think of the real application to your life, and gave me protected space to write down my thoughts, which are wonderful to have now, well after the conference is over. I'm not good at taking notes at conferences, but the prompting questions and built-in space for it meant that I took better, and more applicable, notes than ever. I ended the day with a booklet of pertinent notes, my list of next steps that I was interested in applying when I got home, and a whole lot of ideas to digest and incorporate into my life. My short list of personal takeaways were focused on: - Getting a 360 degree view of how people think of me versus the mask I try to project. (From Tim Chilvers session "Taking off the mask: how to lead when life feels out of control.") - Working on communication skills for conflict resolution like "listen, reflect, clarify" and looking at positions, interests, and needs (PIN). (From Penny Walker's session "Stop assuming, start asking questions: how to turn conflict into collaboration.") - The challenge and goal of actively working to like people you find hard to like. "I'm going to like you, even if you don't want me to." (From Chris Atherton's session "Improving your UX: how to stop being angry and start empathising.") All of the day's sessions were live-streamed and you can [watch the videos](http://2014.dareconf.com/mini/london#donate) of all of these sessions on the DareConf Mini site. ## Workshop Day The great thing about having the workshop day after the conference, instead of before, is that it was the perfect opportunity to actually begin implementing some of the ideas from the sessions. We had a very small group of folks in my workshop, and we essentially got to define our own workshop based on the needs and interests of the people there. We started with a short intro game to get more comfortable with each other, then listed out the various topics and exercises we could potentially tackle for the day. We each ranked the topics and adding it all together defined our actual agenda for the day. We decided on the three following major topics for the day: - Making meeting spaces safe - Getting Things Done (GTD) and Personal Kanban - Conflict resolution and compassionate communication It was a packed day with so much practical, hands-on work that I ended the day full of ideas, confidence, and new hope. ### Making Meeting Spaces Safe Regarding making meeting spaces safe, we discussed "status" within a group, and the fluidity of status that can be used as a tool. Throughout the day we did a lot of exercises using improv techniques to feel the perspectives and interaction norms we need to identify, both in ourselves and others. We got to really feel the energy changes, both positive and negative based on very real human ways of interaction. We also discussed ways of disrupting the normal, assumed status of people in a group to encourage collaboration. This led us to talking about a range of "games" to be used to accomplish this, largely based on the book *[Gamestorming](https://www.amazon.com/dp/0596804172?tag=httpdavegraco-20&camp=14573&creative=327641&linkCode=as1&creativeASIN=0596804172&adid=09F8QZVKJ2TTF6NKWY51&&ref-refURL=http%3A%2F%2Fwww.gogamestorm.com%2F)*. I have a visceral, negative reaction to playing games, but the motivation and implementation we were addressing was pretty non-threatening to me and got me excited about the possibilities there. Through this we also talked about handling people with high status who end up dominating a space and not leaving room for everyone to collaborate. One last big piece of this for me was the general discussion of making real, human connections with people and linking this together with Chris Atherton's session on actively working to like people. Having the fully human context of your work interactions with people makes a world of difference. It's something I know from working in the Drupal community for so long, but bringing the importance of this into the daily workspace was a very good reminder for me. It's an easy thing to let slide or not even be aware of, but it changes everything. ### GTD and Kanban I was curious about the GTD and Kanban overview because I had a grasp of the basic concepts for these, but I've never actually read the books, or taken the time to understand how they both work, or how they would work together. I feel like I'm pretty good at keeping my tasks on track, though I perennially feel like I don't have enough time to do everything on my list. We covered the basic ideas, and then true to the whole day we applied this by writing down some of our own real tasks and working through GTD and Kanban with them to see how it works, as well as raise questions and concerns about them that we discussed as a group. I've been inspired from the workshop to explore GTD and Kanban more on my own, and it's definitely helped me be more focused and organized with my tasks, work as well as personal. I'm still not a 100% convert to these methodologies, but I feel like the overview and practicing with these has let me tease out some more concepts and tools that work really well for me. For example, the default GTD contexts has always confused me a bit, but I've now sorted out what kinds of contexts work for me, and find that it is already helping me. I'm even writing this post based on my GTD lessons, with a context that gives me a handy list of things to do when I'm offline with my laptop (like being on airplanes). ### Conflict Resolution The last major theme of the workshop had us turning our attention to conflict. This is an area that I strongly shy away from so I was glad to be "forced" to really dig into it. We went back to more role-playing and improv work to understand perspectives, and see the impact of the way we use language to either encourage or discourage understanding and collaboration. We then each picked a real conflict in our work lives to role-play with the other attendees, and we talked it out. It was the hardest part of the day, by far, but also very enlightening. It made me realize how much hard work I need to do here, as well as gave me some confidence that I can tackle these issues with humanity and honesty. It gave me a lot to think about and work on. ### Closing the Day We wrapped up the workshop by having everyone share three next actions we would take starting the next day. One was something we will start doing, one was something we would stop doing, and the third was something to continue doing. After having spent a full day diving into the practicalities of being human with this group, I had no reservations about sharing where I was and the things weighing on my mind. In case you are interested, here are my three next actions: - Start using "listen, reflect, clarify" which is a way to slow down conversation and work towards understanding. As Penny mentioned in her session where she raised this, many people spend the time someone else is speaking thinking of what you want to say, or "re-loading" and not actually listening. This is a simple method (though harder to actually apply regularly) to listen to what someone says, reflect what they have said back to them to confirm you got it all, and then ask a genuine question to clarify their point further. - Stop using "yes, but." This came from an exercise where we made plans with a partner and used three different sentence structures. The first was to simply say "no" to all of their suggestions and explain why. The second was to say "yes, but" to indicate we'd go along with it, but we weren't thrilled about it. The last was to say "yes, and" which was a fun way to get the idea juices rolling. Afterwards we discussed that "yes, but" is actually the most depressing and dangerous of the three reactions, as it most erodes the trust in the interaction. - Continue the connection with my team. I feel like I have a good rapport with my team, and generally at Lullabot we work hard to communicate well. One thing attending this conference taught me was how much we do right at Lullabot. I plan to keep that good energy and effort going, and make it even better over time. Overall I felt inspired, and I realized how much we do right at Lullabot and Drupalize.Me. There is always room for improvement, and I have plenty of work to do, but there was also a lot of affirmation of what is going right to be had as well. Such a great conference, with really wonderful people, all muddling through this together. I can not WAIT to go again in September. 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: "WWE & Lullabot Tag Team A Drupal Relaunch " url: "/articles/wwe-lullabot-tag-team-a-drupal-relaunch" type: article date: 2011-07-21 updated: 2014-05-12 --- # WWE & Lullabot Tag Team A Drupal Relaunch # WWE & Lullabot Tag Team A Drupal Relaunch By [ Jeff Eaton ](/about/jeff-eaton) July 21, 2011 We're proud to announce that [World Wrestling Entertainment, Inc.](https://www.wwe.com/) (WWE) is now running on Drupal! For the past year, WWE and Lullabot have worked closely to design and build a flexible, scalable platform for the company's site and its fans. With 200 million page views and more than six million unique visitors each month, WWE.com's peak traffic is higher than ABC.com, CBS.com, NBC.com, NASCAR.com, PerezHilton.com, NHL.com, and UFC.com -- *combined!* ![homepage.jpg](/sites/default/files/styles/wide_xs/public/homepage.jpg.webp?itok=auxRnT6q "homepage.jpg") Lullabot's work on the site was led by Jeff Eaton, project managed by Rachel Scott, and executed by developers David Burns, Matt Kleve, and Blake Hall. Beginning in early 2010, the team worked with WWE to assess the state of their existing platform, their future plans, and the critically important production needs of the WWE in-house content team. Over the following month, a discovery audit was created that served as the blueprint for the migration project to follow. WWE.com's previous incarnation was built on a closed-source CMS that served the company well for several years, but posed problems when more flexibility was needed. As the complexity of the site grew, content editors were forced to spend their time managing the details of each page's layout, rather than producing new material for eager fans. The new Drupal-powered design emphasizes channels of content related to WWE Superstars and their fans, upcoming events, and special editorial features like a leaderboard of rising stars. Rich metadata connects articles to each other without the need for explicit editorial management, while Drupal's Panels and Views modules handle the display of related information. ![power25.jpg](/sites/default/files/styles/max_900/public/power25.jpg.webp?itok=THUg5PIO "power25.jpg") One of the most important parts of the new WWE.com platform is invisible to visitors: heavily customized production tools for the site's content editors. Along with Karen McGrane of Bond Art + Science, Blake Hall worked closely with the WWE.com team to dissect their legacy CMS's user interface, map the critical aspects of their nightly production process, and discover key pain points for their daily work. The result is a highly optimized editorial process that bears little resemblance to Drupal's default interface and workflow. While tools like CCK and Views handle the underlying data, custom administrative screens allow the WWE team to manage the complex connections between "anchor" content like television shows and their related articles, photos, events, and more. Node editing screens have been rearranged and streamlined to focus on the common tasks that team members perform rather than the underlying data they're creating; and a suite of bulk management tools built with Views and Views Bulk Operations allows the team to quickly slice and dice the site's huge library of content. ![raw.jpg](/sites/default/files/styles/max_900/public/raw.jpg.webp?itok=1__498aG "raw.jpg") With more than ten million existing pieces of legacy content dating back to the 1980s, and up-to-the-minute coverage of current WWE events, the migration work began even before development had officially started. A continuous migration process, managed by the WWE.com development team, allowed the site's front-end design, back-end editorial tools, and historical content archive to be built in parallel. On the presentation front, Matt Kleve spearheaded the recreation of legacy Flash-based assets like promotional rotators and photo galleries. The new WWE.com uses standards-compliant HTML, CSS, and JavaScript with tools like jQuery to deliver more flexible versions of these features. Visitors can access more of the site's rich media content on mobile browsers and Apple's iPad, and the content is much easier to adapt to future design changes. We're really proud of the work we did to help the WWE site run smoother for its fans and its staff -- and excited to see where their team takes it! 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 Slides: Contributing to Drupal" url: "/articles/drupalcon-slides-contributing-to-drupal" type: article date: 2008-03-11 updated: 2014-05-12 --- # DrupalCon Slides: Contributing to Drupal # DrupalCon Slides: Contributing to Drupal By [ Addison Berry ](/about/addison-berry) March 11, 2008 Now that the gluttony of last week's DrupalCon in Boston is starting to settle down I realized that I hadn't gotten around to posting the slides from my presentation on [Contributing to Drupal: A guide for everyone](http://boston2008.drupalcon.org/session/contributing-drupal-guide-everyone). The session was a lot of fun and the audience had good questions. That session combined with the code/docs sprint the next day totally revved me up! It is really exciting to see folks diving in. Big thanks to everyone that came to my session and if you couldn't make it or just want to review, here are the slides in PDF, the original Keynote format and Open Office's .odp. (Note that the conversion to .odp ends up losing some of the effects and such.) 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: "Fourth GRAMMY for Drupal and Lullabot" url: "/articles/fourth-grammy-for-drupal-and-lullabot" type: article date: 2013-02-07 updated: 2016-04-07 --- # Fourth GRAMMY for Drupal and Lullabot # Fourth GRAMMY for Drupal and Lullabot Lullabot Re-Launches a Responsive GRAMMY.com for Music’s Biggest Night By [ Lullabot ](/about/lullabot) February 7, 2013 [Lullabot](https://www.lullabot.com) is excited to announce its fourth annual redesign and relaunch of [GRAMMY.com](https://www.grammy.com/)! Just in time for the 55th Grammy Awards this Sunday, February 10th, this year's site features a complete redesign, as well as an upgrade to Drupal 7 leveraging Panels and Views 3.x, some cool, fullscreen, swipeable photo galleries and other mobile-web bells and whistles. The site is also fully responsive, allowing the 40 million+ expected viewers to stream, share, and interact across any web or mobile device. Special congratulations to [Acquia](https://www.acquia.com/) for hosting GRAMMY.com for the first time, as well as to our good friends over at [Ooyala](http://www.ooyala.com) for once again delivering the site’s archive video content, and to [AEG](http://aegdigitalmedia.com/) for delivering the live stream video via YouTube. Simultaneously tune in to every aspect of music’s biggest night so you won’t miss a thing: [awards news](https://www.grammy.com/awards/), [video](https://www.grammy.com/videos/), [pictures](https://www.grammy.com/photos), [artist info](https://www.grammy.com/nominees), [Liveblog posts](http://liveblog.grammy.com/), thousands of tweets-per-minute and behind-the-scenes action. The [GRAMMY Live](https://live.grammy.com/) stream starts tomorrow, Friday February 8th at 5pm ET / 2pm PT, so you can spend all weekend with the website as you gear up for the CBS telecast on Sunday night. Don't miss any of the GRAMMY action this weekend! We'll be watching and tweeting along from [@lullabot](http://twitter.com/lullabot). Follow us on Twitter and let us know what you think. Published in: - [ UX & Design ](/topics/design-and-ux) - [ 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: "CCK & Views Videos: Get 'Em While They're Cheap!" url: "/articles/cck-views-videos-get-em-while-theyre-cheap" type: article date: 2009-03-10 updated: 2014-05-12 --- # CCK & Views Videos: Get 'Em While They're Cheap! # CCK & Views Videos: Get 'Em While They're Cheap! By [ Jeff Robbins ](/about/jeff-robbins) March 10, 2009 *Response to the new Lullabot videos has been outstanding! This is a reminder that the introductory price for these videos (33% off) ends Friday.* We've recently expanded the Lullabot Learning Series with two new tutorial videos: "[Learning CCK](http://store.lullabot.com/products/learning-cck)" and "[Learning Views](http://store.lullabot.com/products/learning-views)". Each of these new videos contains close to two hours of in-depth hands-on demonstrations and examples. These densely packed videos are broken down into digestible lesson-based chapters covering everything from enabling the modules and basic features down to database storage and theming. To order DVDs or HD downloads and complete your CCK and Views knowledge, [visit the Lullabot store today](http://store.lullabot.com/). These videos will sell for $75/ea, however through March 13th, we're offering a Drupalcon special of only $50 per video, so [download your hi-res digital copy today](http://store.lullabot.com/)! > The Lullabot training videos brought clarity to two of the most important and confusing Drupal add-ons. They're excellent videos. --[Ben Finklea](https://www.volacci.com/ben-finklea), CEO, Volacci > got the @lullabot videos for CCK/Views and let me say that they're worth every penny - could do a whole series on that topic alone -- Chrys Rynearson via [Twitter](http://twitter.com/exposur3/status/1317744718) 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 Theming: The Code Behind the Videos" url: "/articles/drupal-theming-the-code-behind-the-videos" type: article date: 2009-06-03 updated: 2014-05-12 --- # Drupal Theming: The Code Behind the Videos # Drupal Theming: The Code Behind the Videos By [ Jeff Robbins ](/about/jeff-robbins) June 3, 2009 One of the best ways that we've found to learn Drupal is to look at a finished site. Drupal's out-of-the-box experience doesn't really show its full potential. In our latest [Drupal theming videos](http://store.lullabot.com), we needed to build a complete website in order to provide content to "fill out" the theme being built. Once we finished that site, we thought that video viewers and others might like to be able get access and try it out for themselves. So we've done two things with the code: 1. We've uploaded the code and database to http://960robots.lullabot.com to act as a demonstration of the [960 Robots theme](http://drupal.org/project/ninesixtyrobots) the we built in the videos. This is more for people to look at the front end and HTML output. We've disabled commenting and account registrations in order to keep the site in tact. 2. We've also bundled up all of the code (modules, core files, etc) along with the database dump and created a installation profile which you can download [here](https://www.lullabot.com/files/lullabot-theming.zip) and install just like you would install Drupal core. 3. **UPDATE:** We've also made available the [original HTML template files](https://www.lullabot.com/files/960-Robots-template.zip) with associated javascript & images so that you can follow along with the Theming Basics video. ### Is This The Long-Awaited Lullabot Drupal Distribution? No. While this *is* a Drupal distribution, it's just a companion for our [theming videos](http://store.lullabot.com). Many new Drupal users will also enjoy being able to install a fully-functioning Drupal site complete with content, custom content-types, views, and custom blocks. It's a great way to get inside of a site and see what makes it work. This code is provided as-is and we don't have plans to keep it updated or supported. ### Download it! Just click the link below, unzip, and install. Having trouble installing? [This video](https://www.lullabot.com/node/315/play) should get you going. Enjoy! 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: "Lullabot Launches Tech Guy Labs" url: "/articles/lullabot-launches-tech-guy-labs" type: article date: 2012-08-22 updated: 2016-04-07 --- # Lullabot Launches Tech Guy Labs # Lullabot Launches Tech Guy Labs A case study By [ Lullabot ](/about/lullabot) August 22, 2012 Lullabot is pleased to announce the Drupal launch of the [Tech Guy Labs](http://techguylabs.com) web site! The Tech Guy, [Leo Laporte's](http://leoville.com/) radio call-in show and live podcast, draws millions of listeners around the globe each week. The new site combines a mobile-friendly responsive layout with an easy-to-search archive of technical questions and answers from the show's callers. The site's previous incarnation was Wiki-based: it allowed the show's staff to quickly set up new pages for episodes of the show, but prevented them from repurposing existing content or integrating social features like commenting and sharing. The new site breaks each episode of the show down into individual segments like Call-In Questions and News Stories. ![Episode Page of Tech Guy Labs](https://www.lullabot.com/sites/default/files/in-this-episode.png) This approach allows the staff to include more detailed transcripts, lets listeners visit the site and add their own input on thorny questions, and makes presenting an attractive overview of each episode much easier. In addition, geocoding allows the Tech Guy Labs team to easily build interactive maps of caller questions and stations that air the show. Next week, we'll be posting an in-depth case study of the tools and techniques that [Jared Ponchot](https://www.lullabot.com/about/jared-ponchot), [Jeff Eaton](https://www.lullabot.com/about/jeff-eaton) and [Angus Mak](https://www.lullabot.com/about/angus-mak) used to implement the redesign. For now, check out the new [Tech Guy Labs!](http://techguylabs.com) 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: "Introduction to the Drupal.org issue queue" url: "/articles/introduction-to-the-drupalorg-issue-queue" type: article date: 2008-06-22 updated: 2014-05-13 --- # Introduction to the Drupal.org issue queue # Introduction to the Drupal.org issue queue By [ Addison Berry ](/about/addison-berry) June 22, 2008 **NOTE: This video is no longer available as it contains outdated content. There is a newer version of this video, [Getting Started in the Issue Queue](https://drupalize.me/videos/getting-started-issue-queue) available on [Drupalize.Me](https://drupalize.me/)** Almost all community work really "happens" in the [Drupal.org issue queue](http://drupal.org/project/issues). This is where the community can track all of the todos for all of our projects. Every project has a queue. This means Drupal core, contributed modules and themes and even the drupal.org website and documentation have queues. This video covers how to find the queue you need, gives a little orientation, then shows how to search for existing issues and create a new issue. 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: "Thoughts About A Drupal Drill Down System" url: "/articles/thoughts-about-a-drupal-drill-down-system" type: article date: 2006-03-18 updated: 2014-05-13 --- # Thoughts About A Drupal Drill Down System # Thoughts About A Drupal Drill Down System By [ Jeff Robbins ](/about/jeff-robbins) March 18, 2006 Drupal's taxonomy system is certainly one if its greatest strengths. The ability to categorize, classify, and/or tag nodes is very powerful and theoretically should make it easy to find similar content quickly. But the "finding stuff" part has been a weakness for Drupal. There have been some recent improvements in the core search functionality and Earl Miles' excellent [Views](http://drupal.org/project/views) module provides a nice user interface to a complex database query engine that can be used in many different ways. A client recently had me thinking about how to easily "drill down" in taxonomy to find nodes. [Taxonomy browser](http://drupal.org/project/taxonomy_browser) has the right idea, but this module lists all taxonomy terms whether or not any content has been tagged with this term. Views module's "exposed" filters adds the ability to filter by any views-aware criteria such as node-type, author role, and a bunch of other good stuff, but again shows everything whether a filter item "contains" content or not. Let's use songs as an example -- you're not going to find any results when narrowing down to "Techno" songs by "the Beatles" on the album "Nevermind". So when you select "Techno" you should only see artists and albums with songs in this category. Of course, Apple has come up with an elegant user interface for this very example: ![itunesbrowser.png](/sites/default/files/styles/wide_xs/public/itunesbrowser.png.webp?itok=BPfCYa2n "itunesbrowser.png") But how can we adapt this to Drupal? Again, Views' interface comes close and probably makes a great starting spot for non-AJAX backwards compatibility. By setting filters to "exposed" you get a display like this at the top of your page: ![viewsexposed.png](/sites/default/files/styles/wide_xs/public/viewsexposed.png.webp?itok=zuczSLAM "viewsexposed.png") And what's more, by combining this with non-exposed filters, you can provide an interface to filter within "song" content items so that you do not need to burden the user with confusing selections like "node type". But Views suffers from the same problem as Taxonomy Browser in that there is no live feedback to let you know that your filtering will yield results. My thinking is that a little bit of AJAX could really benefit this system. Imagine that as you click on a selection in the left column, the items in the right columns update to show only those containing content. Each column contains an "all" item at the top and multiple selections would be allowed. Well don't just imagine it. Fire up iTunes and use it for yourself. It's so intuitive that it's transparent. It just works. In terms of the database query logic, each column represent an "AND", while multiple selections in each column represent an "OR". So if your columns are "Genre", "Artist", and "Album", then you would search could look a bit like this: (Genre = "ALL") AND (Artist = "Rolling Stones" OR Artist = "Clap Your Hands Say Yeah") AND (Album = "Their Satanic Majesty's Request" OR "Clap Your Hands Say Yeah"). Of course that's not proper SQL, but the logic is there. It's also important to note that the logic flows from left to right with each column narrowing what shows up in the columns to its right - hence the "drill down" concept. Also note that the logic for hierarchical taxonomy vocabularies would probably need to be handled slightly differently. Views' exposed filters would need to be prettied up a little bit, but I think most of this is doable using Drupal's core JavaScript functions. It would mostly be a matter of repopulating each select with only items that have nodes assigned to them. If written correctly, this type of search widget could be a HUGE benefit to Drupal. Add iTunes' real-time search box into the equation and we could really get some respect. But let's just take it one step at a time. *The disclaimer:* I do NOT see any time on my schedule to code this. However I'd be happy to help work out the logic (and perhaps the JavaScript) for anyone that does want to code it. I would also encourage anyone interested in funding this development to post comments here. Perhaps we can organize this as a fundable project and match up some folks willing to pay for this functionality with some people willing to code it. And all will be right with the world! 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: "DrupalCon Portland 2013 Wrap-up" url: "/articles/drupalcon-portland-2013-wrapup" type: article date: 2013-06-12 updated: 2014-05-13 --- # DrupalCon Portland 2013 Wrap-up # DrupalCon Portland 2013 Wrap-up Thanks for another great North American DrupalCon By [ Jeff Robbins ](/about/jeff-robbins) June 12, 2013 By now everyone hopefully made it safely home from DrupalCon (if you're still there, it ended more than two weeks ago so you might want to head home), and while the dust settles we wanted to say thanks to all who helped make DrupalCon Portland a success, and also share some highlights from Lullabot. ## The Lullabot Party ![The Lullabot Party at DrupalCon Portland 2013](/sites/default/files/styles/wide_xs/public/field_regular_upload/8878370098_568417aa15_c.jpg.webp?itok=XOLUv6FC "8878370098_568417aa15_c.jpg") Thanks so much to all who braved the early rain (it did clear as the evening went on) and joined a packed house at the Wonder Ballroom for the Lullabot DrupalConPDX Party. A special thanks is in order for [Orbit](https://www.facebook.com/orbitband) for coming all the way out to Portland to rock the Lullabot party. ## Lullabot talks and presentations Thanks so much to all of you who attended the many talks and presentations that bots were giving at this year's DrupalCon. The feedback has been great and we really appreciate the full rooms of people that attended our sessions. Lullabot was honored to have Bots be a part of eleven different sessions at DrupalCon Portland, and since they've now got audio and slides up for all of them we thought it might be helpful to have a list of links to each. We've provided a full listing of each below, organized by day and time for your convenience. If you missed any of these sessions and wanted to see them, go checkout the videos. ![DrupalCon presentation](/sites/default/files/styles/wide_xs/public/field_regular_upload/talk-view.jpg.webp?itok=Ovtffiws "talk-view.jpg") #### Tuesday - 10:15 a.m. - Greg Dunlap spoke about [Making Core Development Sustainable](http://portland2013.drupal.org/node/3863) - 10:15 a.m. - Jared Ponchot will spoke about [Designing on Purpose: Design Process & Deliverables in the Responsive Age](http://portland2013.drupal.org/session/designing-purpose-design-process-deliverables-responsive-age) - 2:00 p.m. - Nate Haug spoke about [WYSIWYG in Drupal 8](http://portland2013.drupal.org/node/2878) - 3:15 p.m. - Jeff Eaton spoke about [Building for a Post-Mobile World](http://portland2013.drupal.org/session/building-post-mobile-world) - 3:15 p.m. Addison Berry spoke as part of the core mentoring team for [Running Coaches Wanted! Contribution Sprints and Trainings](http://portland2013.drupal.org/node/2433) #### Wednesday - 10:45 a.m. - Emma Jane Westby gave her talk titled [Was It Something I Said? The Art of Giving (and Getting) A Critique](http://portland2013.drupal.org/session/was-it-something-i-said-art-giving-and-getting-critique) - 1 p.m. - Seth Brown spoke about the [Mistakes Agencies Make](http://portland2013.drupal.org/node/3083) - 5 p.m. - Josh Riggs spoke about [The Zen of HTML Prototyping & Designing in the Browser](http://portland2013.drupal.org/session/zen-html-prototyping-designing-browser) #### Thursday - 1 p.m. - Karen Stevenson spoke about being [Remotely Virtual](http://portland2013.drupal.org/node/428) - 1 p.m. Greg Dunlap spoke about [Using The Drupal 8 Configuration Management System](http://portland2013.drupal.org/node/743) - 2:15 p.m. Greg Dunlap was a part of the [Dries & Company QA](http://portland2013.drupal.org/node/3883) ## To Austin! Also, for those who missed it, it's official now that DrupalCon 2014 in North America will happen in Austin, Texas. We look forward to seeing you there next year. 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: "Heading Off To Mexico With Lullabot's New Hires" url: "/articles/heading-off-to-mexico-with-lullabots-new-hires" type: article date: 2013-11-08 updated: 2019-02-02 --- # Heading Off To Mexico With Lullabot's New Hires # Heading Off To Mexico With Lullabot's New Hires El ir a México con los nuevos empleados de Lullabot By [ Jeff Robbins ](/about/jeff-robbins) November 8, 2013 ## Retreating As a distributed company, our annual Team Retreat has become a very important part of what makes Lullabot work. We all get together in one location to hang out and gain a better understanding of one another. We do a great job of tele-connecting on a daily basis, however this chance to meet in-person fills in all the gaps lost over the internet - sense of humor, personality quirks, and all the peripheral stuff that we might miss during our work-focused communication. So we get together to talk about the future of the company, to eat good food, and mostly just to hang out. This year, the team will be oceanside near Cancun, Mexico soaking in the sunshine and celebrating a banner year for Lullabot. Since the Team Retreat is such a good energizer for the company, we often find ourselves trying to squeeze in new hires so that these new people won't need to wait to get to meet the rest of the team in person. This year is no exception. So in addition to the great people we've added to the team in the past few months, we'll have several brand-spankin'-new people at the retreat. Toes in the sand on a Caribbean beach – nice way to start a new job! ## The New Guys & Gals Jeff Eaton posted [our first-quarter hires](https://www.lullabot.com/blog/article/welcoming-new-lullabots) back in April. Since then we've been steadily growing Lullabot's talent pool. It's hard not to gush when I look at the amazing people who have joined our team. Let's list 'em off: ### Emma Jane Westby ![" style=](/sites/default/files/styles/max_900/public/bio_image/ejw-lullabot-headshot.jpg.webp?itok=qD8NeYWy "ejw-lullabot-headshot.jpg") Emma Jane Westby (née Hogbin) has been a well-known figure in both the Drupal and the larger Open Source communities for some time. She has received many accolades and awards including being recognized in 2010 by The Google Diversity Programme for her efforts in increasing female participation in software development. Emma joins the [Drupalize.Me](https://drupalize.me/) team as its Education Development Coordinator. This basically means that she's thinking about training, curriculum, marketing, and just about everything else that Drupalize.Me needs to keep kicking ass and knocking out great content. ### Adrian Young ![" style=](/sites/default/files/styles/max_900/public/bio_image/adrian_young.jpg.webp?itok=iDnRZbSL "adrian_young.jpg") [Adrian Young](https://www.lullabot.com/about/adrian-young) is a talented Drupal developer who lives near Minneapolis, Minnesota. Adrian spent his formative years pursuing a master’s degree in architecture. He’s always loved the combination of art and science, creativity and precision engendered by the discipline. Somehow, along the way he discovered Drupal and web design and realized it was an even better fit for combining his left-and-right-brainedness (yes that’s a word). Adrian brings his considerable enterprise experience with both front-end and back-end Drupal to Lullabot’s client services team. ### Justin Harrell ![" style=](/sites/default/files/styles/max_900/public/bio_image/justin_harrell_headshot1.jpg.webp?itok=AizBBUyj "justin_harrell_headshot1.jpg") Justin Harrell is the new superstar designer on our [Drupalize.Me](https://drupalize.me/) team. We had a lot of really great designers apply for this position and we were honored when the best of them agreed to join us. Justin lives in Cleveland where he cranks out amazing design work, awesome illustrations, and superlative HTML and CSS. Justin has already overhauled Drupalize.Me's social media design. Drupalize.Me subscribers will soon have their eyes blown out with more visual awesomeness courtesy of Justin. ### John Albin Wilkins ![" style=](/sites/default/files/styles/max_900/public/bio_image/john_wilkins.jpg.webp?itok=FXmos5eD "john_wilkins.jpg") I first became aware of John Wilkins (known to Drupal fans as *JohnAlbin*) when he started working on the Zen theme with me. He soon outpaced me doing things like casually creating his own CSS layout system and other magical feats which left me in awe of his front-end development skills. I happily handed over the Zen theme to him back in 2008 and he turned it into Drupal's most popular base theme, a position it has maintained for the past 5 years. John wields his ninja-level front end development skills from Taipei, Taiwan, where he works quietly at night while his wife and children sleep. ### Dave Reid ![" style=](/sites/default/files/styles/max_900/public/bio_image/dave_reid.jpg.webp?itok=ynXbAOC4 "dave_reid.jpg") I believe that at one time or another [Dave Reid](https://www.lullabot.com/about/dave-reid) has been the maintainer of just about every module on Drupal.org. That may be an exaggeration, but he has certainly made a huge impact on both the Drupal module repository and core Drupal where he has been the maintainer of the path system, contact module, and the token system. Needless to say, Dave is a rockstar developer with some deep Drupal mojo. ### Jesse Mathewson ![" style=](/sites/default/files/styles/max_900/public/bio_image/jesse_0.jpg.webp?itok=P2GrGJYC "jesse_0.jpg") Jesse Mathewson is our new Executive Assistant. If you ever stop by the Lullabot Activity Center in Providence, she'll probably be the one to greet you. Jesse spends her days keeping lists, shipping things, scheduling, booking, researching, and generally making sure that everything is where it needs to be to keep Lullabot running smoothly. ### Kris Konrady ![" style=](/sites/default/files/styles/max_900/public/bio_image/kris_konrady.jpg.webp?itok=XfGHmPbC "kris_konrady.jpg") [Kris Konrady](https://www.lullabot.com/about/kris-konrady) joins Lullabot to help with HR and Bookkeping. It turns out that as the team gets bigger, this stuff gets more complicated! She helps keep us organized and on track. When there's a form to be filled out, check to be sent or received, or employment laws to be considered, Kris is probably involved in one way or another. ### Amber Himes ![](https://www.lullabot.com/sites/default/files/styles/medium/public/bio_image/amber-headshot.jpg?itok=5UlBR6yt) Amber Himes joins the Drupalize.Me team as its latest trainer. Amber lives in that hotbed of Lullabot employees – Portland, OR, where she helps in a variety of ways at Portland’s four Drupal-related meetups. She has also hosts a regular Google Hangout called DevMunch, which takes a look at cool developers tools for people of all experience levels. She brings 2 years of Drupal development and training experience. Look for Amber in upcoming videos from [Drupalize.Me](https://drupalize.me/). ### Lisa Kastner ![" style=](/sites/default/files/styles/max_900/public/bio_image/lisa_kastner.jpg.webp?itok=G_QEPFiQ "lisa_kastner.jpg") Lisa Kastner is Lullabot's new Marketing Manager. Her experience includes marketing positions at Canon, Billboard Magazine, and most recently the Specialty Food Association. Lisa lives near the sandy beaches of Long Beach, NY. Sometimes we can get so caught in our work that we forget to tell people what we're doing. She's going to make sure that we remember to share our great work on our projects, sponsor events, and generally interact with the world more! ### Joe Fender ![](https://www.lullabot.com/sites/default/files/styles/medium/public/bio_image/joe.jpeg?itok=t0bnBMZW) Joe Fender joins the Drupalize.Me team as its second "Joe", but primary developer. He will make sure that Drupalize.Me's technical needs get full-time attention. When we met Joe, he was living in Japan, however he is British citizen and now lives in London. Like so many great British world explorers before him, we don't expect Joe to stay put long. He can continue to work from Lullabot wherever he goes. And we look forward to following his travels as he rolls out new features for Drupalize.Me from around the globe. 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: "Lullabot Sponsoring DrupalCamp Ohio" url: "/articles/lullabot-sponsoring-drupalcamp-ohio" type: article date: 2012-10-18 updated: 2014-05-13 --- # Lullabot Sponsoring DrupalCamp Ohio # Lullabot Sponsoring DrupalCamp Ohio And Jeff Robbins is Keynoting! By [ Haley Scarpino ](/about/haley-scarpino) October 18, 2012 Lullabot is excited to announce will be sponsoring the second annual [DrupalCamp Ohio](http://drupalcampohio.org), November 30th and December 1st at Ohio State University. After the success of last year, they have decided to expand to a two day event. Lullabot’s one and only Jeff Robbins will be keynoting the event. In addition to the exciting keynote, there will be sessions, bof’s and code sprints. Session submissions will be open soon on the website. DrupalCamp Ohio is a volunteer run event. If you are interested in getting involved, [please contact them](http://drupalcampohio.org/contact). Lullabot will be there and we’d love to see you there, too! 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: "Designers, Drupal, and the Future" url: "/articles/designers-drupal-and-the-future" type: article date: 2009-01-26 updated: 2014-05-13 --- # Designers, Drupal, and the Future # Designers, Drupal, and the Future By [ Jeff Eaton ](/about/jeff-eaton) January 26, 2009 First, a disclaimer. It's been a long, long time since I've done real design work for the web. ([One of my earliest sites](https://www.roguefishmedia.com/) is still standing, a monument to the days of hand-optimized GIFs and table-based layouts...) I'm not a CSS guru, and I tend to get frustrated quickly when I leave the world of *code* and muddle into the cobwebbed *design* parts of my brain. I do my share of converting existing themes and CSS/HTML templates to Drupal, though, and I chat with enough Joomla!, Wordpress, Movable Type, and ExpressionEngine folks to have a few thoughts on what's up in Drupal Theme Land. ### The Past Drupal has long had a reputation for baffling designers, and for years it was deserved. First, Drupal's approach to generating an HTML page is a bit different than the standard Wordpress/Movable Type approach. Rather than asking the CMS for a piece of data and doing things with it, a Drupal theme is handed small pre-rendered chunks of text (like a title, an author's name, etc.) and is responsible for 'wrapping' them in the appropriate HTML markup. It's a subtle difference, but it requires a shift in thinking. In addition, making a custom theme for Drupal that deviated from the normal markup in a significant way used to require nontrivial PHP skills. While the big pieces (like the page-level markup and individual pieces of content) could be tweaked using editable template files, overriding smaller bits like navigation menus and the details of a post's attribution line required wading into the depths of Drupal's theming APIs and writing code. If you remember the days of the \_phptemplate\_variables() function, you know what I'm talking about. In addition, changing markup created by third-party modules required digging through the source code, finding where they generated their HTML, and implementing your own override functions in PHP. It worked, and it didn't require hacking third-party code, but it was hell for folks whose comfort zone was pure HTML and CSS. ### The Present Thanks to some awesome work in Drupal 6, a lot of those hassles are in the past. More markup now lives in editable template files instead of PHP functions, and third-party modules can expose their own markup as editable template files. Developers on large projects coordinating with designers can use hardcore PHP code to finesse data before it makes it to the template files, but the templates still say clean and easy to edit. It's also possible to build "pure" CSS themes that rely on Drupal's default markup. The down side, though, is that not many people outside of the Drupal *development* community know about these changes. The information is there in Drupal 5 to 6 changelogs, and there are bits and pieces of knowledge scattered in blog posts and articles. The little-known but information-rich [Drupal 6 theming guide](http://drupal.org/theme-guide/6) also helps. However, the fundamental shift in Drupal design that took place hasn't resulted in an new wave of non-programmers designing for Drupal. Some of that is inertia -- it's "well known" that Drupal is hard for designers, the same way that it's "well known" that it's impossible to make accessible Joomla! themes. (Both have been true in the past, but that's changed -- witness [Beez](http://www.joomla-beez.com/).) Part of the problem is also cultural. Drupal requires that designers learn to use CVS for version control before contributing their work back to the community. Addons and themes the live outside of the Drupal.org downloads section tend to languish in obscurity, like books that can't be found on Amazon.com. The biggest issue, though (at least in my opinion) is the lack of good theming examples *in the Drupal core download*. Developers looking to learn best practices can pop open any core module, poking around to see how things are done. While few are perfect, they demonstrate lots of useful techniques and can be used as starting points for almost any project. Drupal's bundled themes fail on that count, in a big way. The default Garland theme, while attractive and customizable via the admin interface, is notoriously confusing for newcomers to use as a starting point in their own custom designs. Older themes, like Bluemarine and Pushbutton, are holdovers from the days of tabled layout and 2004 era design. *None* of the core themes demonstrate the capabilities in Drupal 6: CSS-only themes that use the 'default' markup, easy creation of new content regions without custom code, and the use of template files to override lesser known HTML like the poll module's graph of survey results. When Lullabot teaches its workshops on Drupal theming, we've found that taking people through those steps is one of the best recipes for success. Start with nothing but an .info file, to define a theme that uses the default markup and no CSS. Start adding your own CSS files and images to customize the layout, and then begin pulling over templates like page.tpl.php when you find that the markup needs tweaking. Eventually, we can lead them through the complexities of template.php code overrides, but this progression makes for an actual learning *curve* rather than a wall that needs scaling. ### The Future? Drupal 7 is still under development, and a lot of energy is crackling around how to improve the bundled themes that come with it. Several people have proposed using a grid system like [960](https://960.gs/) to enable rapid theme development. While I'd love to see some cutting-edge CSS coolness make it into core, I think that we need to ensure that the basics are there, too. What would I love to see included in Drupal 7? 1. **Make sure all of Drupal's tpl.php template files contain usable, standards-compliant markup.** This came a long way in Drupal 6, but there are still a couple of rough edges. Content-first ordering in the default page.tpl, for example, would be great. 2. **Include a layout.css file in Drupal's System module that does *nothing* but position the header, sidebars, and content correctly.** Overriding these core CSS files is easy; just add a CSS file with the same name into your theme. For newcomers, though, it would help clarify how the default template files work in a minimalist layout. 3. **Provide a core theme that is *nothing* but an .info file.** With default markup and a layout.css file provided by core, this theme would serve as an example of what "stripped down Drupal" gives a designer to work with. 4. **Provide one or two core themes that are *only* CSS and images decorating the standard markup.** This has been done in contrib for Drupal 6 (See dvessel's [Skyliner](http://colorovfire.com/) theme), but very few realize it's possible. If we discover that Drupal's default markup isn't flexible or clean enough to theme with pure CSS, we should fix it. 5. **Provide a theme that adds one or two additional content regions** to implement the newsier appearance that's common in Joomla! and advanced Wordpress themes. This theme should also override one or two of the less common templates from a core Drupal module: user-profile.tpl.php is one possibility, as few realize it can be tweaked so easily. 6. **Provide a theme that uses template.php code overrides and/or jQuery to implement exotic functionality.** This doesn't have to be insane; it could be as simple as turning one region into a slide-out panel using jQuery, adding daytime and nighttime CSS, or adding extra "template suggestions" so comments by the author of a post show up differently. The important part is to point people in the direction of advanced techniques. 7. **README.txt files inside each of these themes' directories should explain what they do and how.** (Regions are added by putting a line of text in a theme's info file, module templates are overridden by copying a tpl.php file into the theme's directory and editing, etc.) ### Can it be done? This is a big set of wishes: it implies at least two *completely new* themes for core, with specific requirements. If we were to hit those goals, though, I think we'd have a much better collection of examples to point designers to when they learn the Drupal ropes. In addition, designers exploring a new Drupal install for clues would have something to go on *before* they start posting confused questions on the support forums. So, what do you think, fellow Drupal developers? Can we make this happen? Even more important... should we? 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: "User Management for Real World Groups" url: "/articles/user-management-for-real-world-groups" type: article date: 2009-12-20 updated: 2014-05-13 --- # User Management for Real World Groups # User Management for Real World Groups By [ Karen Stevenson ](/about/karen-stevenson) December 20, 2009 Drupal's default methods of handling user names, emails, and registration processing work pretty well out of the box for many web sites. Drupal assumes your users are online, have unique email addresses, and that you want to create a site that grows organically as users find it and register themselves. Drupal out of the box may not work so well for real-world groups of people where the group already exists and consists of specific people who may or may not be online, may or may not have unique email addresses, and may or may not be able or willing to register themselves on your site. Some good examples of the problems I have run into are creating web sites for families, clubs, and churches, but the same problems exist for any other real world organization or group. They have established groups of members, some of whom may share email addresses or have no known email address. And in these cases the administrator will probably create user accounts for everyone rather than waiting for users to self-register. On top of that, we may want some control over usernames so that users can recognize each other once they do get online, by forcing the username to be FIRSTNAME LASTNAME. This creates several problems that have to be overcome creatively. After several years of trying various approaches, here is a summary of the problems I ran into and the ways I eventually solved them. I have included links to the ones I discuss, with the number of downloads in the week of November 22 as a measure of how commonly used they are. ### Some emails are shared Drupal expects each user to have a unique email address, but many people in these groups share a single email address for a whole family. The parents may share an address because they don't know how to set up another email account and consolidate it with the one they are used to using. If they set up another one just to gain access to a new site, it is likely they won't ever actually check that address anyway, so they might miss important messages. Plus if there are children in the family, children and parents probably share an email address so the parents can stay on top of the messages the child is sending and receiving, and in that case they won't want the child's email messages going someplace else. So sometimes it is not a good idea to require users to create a new, unique, address. There is a Drupal module called [Shared Email](http://drupal.org/project/sharedemail) (280 downloads) that allows more than one user to register with the same email address. That's one way to solve the problem, but I ended up with a different solution that doesn't require any extra modules. Users can share an email address and still create different accounts if they use 'Sub-addressing' (see [https://en.wikipedia.org/wiki/Email\_address](https://en.wikipedia.org/wiki/Email_address) for more about that topic.) Basically, each user of a shared address can add '+tag' or '-tag' to the name part of the address and it will still go through as a valid address that will end up in their shared email account. For instance, if Bob and Cindy share the email address 'smiths@yahoo.com', Bob can create an account with the email address of 'smiths+bob@yahoo.com' and Cindy can create a different account with the email address of 'smiths+cindy@yahoo.com'. The email addresses are unique, so Drupal will let them create different accounts, and messages to either address will land in their shared email account. They can even use this feature in their email software to send messages for Bob and Cindy into different folders. With this solution, you just need to explain this option to users, perhaps by overriding the theme for the user registration form with a better description. ### Some people don't have email addresses This problem was a bit tougher to overcome. You can't leave the email address empty, and you have to be careful about using made-up email addresses like 'user@test.com' because you will be sending out email to another domain that may actually exist. I found a module that looked like it might work, the [Local Email module](http://drupal.org/project/localemail) (45 downloads). This module creates a dummy email address for these users using the system domain, and then provides a way for them to log in using pre-created questions and answers. I was concerned about security with this system and the module seems neglected, plus it solved the wrong problem for me. My users without email addresses were not web users and would not be logging in. If they actually want to use the system and log in and are knowledgeable enough to do that, they can get an email address. In my case, I just needed a way to create a user record so they will show up in some directories and lists, these users would not need a way to log in nor would they need any way to register themselves. And the solution for that was to use sub-addressing again. I created email addresses like 'nomail-1@mydomain.com', 'nomail-2@mydomain.com', etc. It is a valid email address and email will actually go there, but I can use a garbage account for that purpose. I can also use that value in Views as a filter to find users that do or do not have valid email addresses. ### We want to force FIRSTNAME LASTNAME We can set this up intially the way we want for any user accounts we create, but we have to keep them current if names change or there are duplicates, we have to notify users about what name we set up for them and educate them about using it, and users can change their usernames and break everything if they want to. A better solution would be to create separate fields for the first and last name that the administrator or end user can create and keep current, then automatically use those names wherever the username would show up. I spent time exploring ways to auto-create the username out of the field values, and there are patches here and there to do that in various ways, but all of them seemed to have reports of problems. I finally realized a better solution was to ignore the username value and enable the [Real Name module](http://drupal.org/project/realname) (4,442 downloads) to just display the names the way I wanted them. We can set up fields for First Name and Last Name in the system, either by turning on the core Profile module and creating profile fields, or by enabling the [Content Profile module](http://drupal.org/project/content_profile) (12,287 downloads), and setting those fields up in the Profile node type. Once the fields exist, the administrator can visit the Real Name settings page (in the User Administration area) and identify which fields to use for the user's 'Real Name'. Once I had this working well, the actual username isn't really shown anywhere, it is only needed as a way to login. And for login, it is actually easier to just let people login with their email address. The [Logintoboggan Module](http://drupal.org/project/logintoboggan) (14,257 downloads) allows users to login with either their username or email address, so that was my first solution. But then I realized that if I want users to login with their email address and have the system automatically display their 'Real Name' instead, that the username ends up just being an annoying, and confusing, extra field to fill out. What I really wanted was to force everyone to login with their email address and get rid of username altogether. I finally found the [Email Registration Module](http://drupal.org/project/email_registration) (1,484 downloads) and turned that on instead of Logintobbogan. That module hides the username on the login and registration forms and changes the form description to eliminate any reference to 'username', telling users to use their email address to login. ### The administrator will be creating the account, not the end-user If the administrator creates the account, the administrator will have to create both usernames and passwords for each, possibly before the site is live. That means we need an easy way to auto-create a password. There are two modules that help with auto-creating passwords, [Generate Password](http://drupal.org/project/genpass) (569 downloads) and [Password Quick Set](http://drupal.org/project/passquickset) (46 downloads). Generate Password works only when a user is created, and has some settings where the admin can choose whether the user will have a choice to create their own password or if the password will be created for them. Password Quick Set does nothing when a user is created, but adds a button to the user edit form for administrators to allow them to create a new random password for an existing user. So they fill different needs and don't seem to step onto each other, and you can use either or both. If the administrator creates the username, we need a way to let the users know how to login. If the site is already live, you can opt to send email messages to the users as their accounts are created to tell them what username and password to use, but if you are creating users before the site is live that won't work. For that situation we can create random passwords using one of the above methods, and then send an email out to all users when the site goes live telling them that they can override our temporary generated password with the actual password they wanted to use by using the Drupal feature to reset your password. ### The administrator may need to mass-create users Another issue when the administrator creates accounts is that we want to have some easier ways for them to do so. There are a couple modules that can be helpful here. I have used the [User Import module](http://drupal.org/project/user_import) (2,737 downloads) to import a list of user data from a comma separated file created from a spreadsheet. Also handy is the [User Plus module](http://drupal.org/project/userplus) (1,764 downloads), which creates a table where you can input many usernames in a single submission. The only downside of these methods is that they don't use the standard Drupal user forms, which means that some other modules won't integrate with them. For instance, the modules to auto-generate passwords and change usernames to email addresses won't do anything if you use User Plus to create users. But they have numerous handy features of their own. User Import has its own method of auto-generating passwords, and both allow you to assign roles and required Content Profile fields (like FIRSTNAME and LASTNAME) to the users as they are created. Another method of importing user data is to use the Feeds or Feed API modules, there is some how-to information from [Development Seed](https://developmentseed.org/blog/2009/dec/15/importing-and-aggregating-stuff-feeds). ### Non-administrative users may need to be able to create other users This is a less common situation, but it does come up. For instance, you may have a family site where you want a parent to create accounts for their children or you want Organic Groups group administrators to be able to create accounts for group members, but you don't want them able to touch any other user accounts. I have found two modules that help here, but both have limitations. One is the [Subuser module](http://drupal.org/project/subuser) (48 downloads) and the other is [U Create module](http://drupal.org/project/ucreate) (1,447 downloads). The Subuser module allows you to define a role that can create subusers and maintains a relationship between the original user and the subuser, allowing you to name the relationship. So you can call the creator a 'parent' and the target a 'child' and the parent will see links to the children's accounts on his account page. The module doesn't use the standard Drupal user input forms, so it you can't create or see profile fields, and it has a buggy method of letting the 'parent' see a user administration page of only their users which could be fixed by using Views and Views Bulk Operations to create a custom user administration page instead of the system page. The U Create module lets and user with the right permissions create a user, and it integrates into Organic Groups so that the created user can be assigned to specific groups. But, again, it doesn't use the standard Drupal user input forms so there is no way to create profile fields. U Create does not create any relationship between the creator and the user or provide any way for the creator to edit the user account, only a way to create a new user. The main problem with both solutions is that Drupal has lots of ways of allowing you to control which nodes you can edit, but fewer ways to control which users you can edit, so all the methods of trying to do this end up being less than ideal. Maybe this problem will be solved in better ways in future versions of Drupal. 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: "Doing It (With Drupal) Again!" url: "/articles/doing-it-with-drupal-again" type: article date: 2009-08-23 updated: 2014-05-13 --- # Doing It (With Drupal) Again! # Doing It (With Drupal) Again! By [ Jeff Robbins ](/about/jeff-robbins) August 23, 2009 [ ](http://www.doitwithdrupal.com) ![Do It With Drupal](/sites/default/files/styles/wide_xs/public/u2/sub-do-it-with-drupal.png.webp?itok=J2U0OxK4 "sub-do-it-with-drupal.png") In case you're not on the Lullabot [mailing list](https://www.lullabot.com/mailinglist) and you haven't seen the post on [DoItWithDrupal.com](http://www.doitwithdrupal.com/blog/were-doing-it-again), I just wanted to post a quick note to let everyone know that based on the popularity of last year's [Do It With Drupal Seminar](http://www.doitwithdrupal.com), we've decided to Do It again! [Do It With Drupal 2009](http://www.doitwithdrupal.com/) will be held December 9th, 10th, and 11th at the [New Orleans Marriot](http://www.doitwithdrupal.com/location-and-accommodations) at the edge of French Quarter in New Orleans, Louisiana. Registrations open on Monday at 12:00pm Eastern and the first 25 registrations will get a special deal. Find out more information about Do It With Drupal 2009 at [DoItWithDrupal.com](http://www.doitwithdrupal.com). And if you'd like to get a taste of last year's event, you can still purchase access to the [2008 video archive](http://www.doitwithdrupal.com/2008), 34 sessions and over 40 hours of Drupal and site-building videos, right at your fingertips. 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: "WYSIWYG as a Feature" url: "/articles/wysiwyg-as-a-feature" type: article date: 2010-06-28 updated: 2014-05-13 --- # WYSIWYG as a Feature # WYSIWYG as a Feature By [ Karen Stevenson ](/about/karen-stevenson) June 28, 2010 One of the things Drupal core does not do well at all is provide an easy way to switch on a WYSIWYG editor. There is no editor out of the box and setting it up requires some custom configuration of several Drupal core settings, installing and configuring several contributed modules, and also installing one or more external libraries, like the TinyMCE library. The Lullabot book, [Using Drupal](https://www.amazon.com/gp/product/0596515804?tag=lullabot-20) (O'Reilly), devotes a whole section to describing one way to configure a WYSIWYG editor. ## The Problems It's complicated because there are lots of inter-related parts: 1. You need to set up one or more roles that can edit content. 2. You need to create and configure 'Input formats' to create at least one format that allows users to add the html and css needed to create rich text content. 3. You need to select and install a WYSIWYG editor: install the editor, download an appropriate javascript library and move it to the right location (which varies depending on which editor you are using), and then configure the editor to use the library. And all of that gets you only to the point where you can edit text. If you want to allow users to upload images and insert them into their text, you also need to identify which of several possible methods of image handling you want to use, and install and configure the modules needed to manage that. If you've never set a rich text editor up in Drupal before, it can take quite a long time to figure out what to do and do it. Even if you've done it before it typically involves lots of time to navigate to all the needed configuration pages and set each module up appropriately. And once you've done it on one site, there's no easy way to replicate the setup on another site. So there is a real need for a way to pre-configure as much of this as possible to make it easier to set up and deploy this to other sites, something we can do using the Features module. ## WYSIWYG Options There are lots of possible ways to set something like this up. For the editor you could use a module like [FCKEditor](http://drupal.org/project/fckeditor) or [TinyMCE](http://drupal.org/project/tinymce), or do this the way many of us are doing it now, using the [WYSIWYG](http://drupal.org/project/wysiwyg) module and configuring it to use the library of our choice. For image handling there are numerous options: you could use the [Image](http://drupal.org/project/image) module to create nodes for each of the uploaded images, or you could use the [Imagefield](http://drupal.org/project/imagefield) module along with helpers like the [Insert](http://drupal.org/project/insert) module and [Filefield Sources](http://drupal.org/project/filefield_sources) to attach images to nodes as CCK fields, or you could use the core Upload module to attach images to nodes without making them into fields or nodes, or use [IMCE](http://drupal.org/project/imce) or [Image Browser](http://drupal.org/project/imagebrowser) to create 'loose' images that are not attached to nodes at all. Most of these methods work nicely with the [Imagecache](http://drupal.org/project/imagecache) module, which makes it possible to create a collection of preset sizes for your images. ## Gathering the Modules and Files An extensive discussion of all the possible kinds of image handling and all the other things you can do with Imagecache is an article (or two) in itself. So without delving into all the reasons for choosing one of these methods over another we're going to build a feature that uses the WYSIWYG module and the TinyMCE library as an editor, and the Image Browser and Imagecache for image handling. And we're going to add a couple other handy additions: [Image Resize Filter](http://drupal.org/project/image_resize_filter) so we can grab the image that has been dropped into our text area to make it bigger or smaller, and [Transliteration](http://drupal.org/project/transliteration), a helper module that ensures that the names of the files that we create don't have spaces or other invalid characters in them. And Image Browser requires that we have [Views](http://drupal.org/project/views) and [jQuery UI](http://drupal.org/project/jqueryui) installed. Note that we want version 2 of Image Browser, not version 1. We also need the modules that make Features work, [Features](http://drupal.org/project/features), [Strongarm](http://drupal.org/project/strongarm), and [CTools](http://drupal.org/project/ctools), along with a new module called [Input Formats](http://drupal.org/project/input_formats) that lets us import and export input formats. Finally we need the [WYSIWYG](http://drupal.org/project/wysiwyg) module with a patch at http://drupal.org/node/624018 that makes it possible to import and export WYSIWYG configuration, and the TinyMCE library. If we express this in a make file, it looks like this: ``` ; DRUPAL VERSION core = 6.x ; CORE MODULES projects[] = drupal projects[views][subdir] = "contrib" ; FILE HANDLING projects[imagecache][subdir] = "contrib" projects[imageapi][subdir] = "contrib" projects[transliteration][subdir] = "contrib" projects[image_resize_filter][subdir] = "contrib" projects[imagebrowser][subdir] = "contrib" projects[imagebrowser][version] = '2.x-dev' ; FEATURES projects[features][subdir] = "contrib" projects[strongarm][subdir] = "contrib" projects[ctools][subdir] = "contrib" projects[input_formats][subdir] = "contrib" ; WYSIWYG projects[wysiwyg][subdir] = "contrib" ; Add a patch to make wysiwyg exportable. projects[wysiwyg][patch][] = "http://drupal.org/files/issues/wysiwyg-624018-ctools-export-input-formats-2.patch" ; LIBRARIES projects[libraries][subdir] = "contrib" projects[jquery_ui][subdir] = "contrib" ; TinyMCE libraries[tinymce][download][type] = "get" libraries[tinymce][download][url] = "http://downloads.sourceforge.net/project/tinymce/TinyMCE/3.2.7/tinymce_3_2_7.zip" libraries[tinymce][directory_name] = "tinymce" ; jQuery UI libraries[jquery_ui][download][type] = "get" ;libraries[jquery_ui][download][url] = "http://jquery-ui.googlecode.com/files/jquery-ui-1.7.3.zip" libraries[jquery_ui][download][url] = "http://jquery-ui.googlecode.com/files/jquery.ui-1.6.zip" libraries[jquery_ui][directory_name] = "jquery.ui" libraries[jquery_ui][destination] = "modules/contrib/jquery_ui" ``` ## Creating a Feature After this we can set up a site the old-fashioned way, by installing all the necessary modules and libraries, creating the imagecache presets, creating the input filters, setting up user permissions (including permissions to view all the imagecache presets and to use them in the Image Browser), setting up the WYSIWYG editor and choosing all the buttons and options you want it to use. Now the magic happens. We go to the Features page and choose the option to create a new feature. Into the feature we add the WYSIWYG and input filter settings, the Imagecache presets, and all the related permissions and variables. Then we download the feature and add it to another site. ![WYSIWYG w_Image Browser | Gallery2.png](/sites/default/files/styles/wide_xs/public/WYSIWYG%20w_Image%20Browser%20%7C%20Gallery2.png.webp?itok=4V7BS8no "WYSIWYG w_Image Browser | Gallery2.png") ## Viewing the Results I have attached a copy of the feature this created. You should be able to try it out by creating a new Drupal installation that has all the required modules and libraries (you can create one with the make file and Drush Make). Then add the WYSIWYG Feature code to the site, go to the Features page, and enable it. You will need to clear the caches after enabling it and then you should be able to see that WYSIWYG and the input filters and the Imagecache presets and the permissions are all set up correctly. Try to create a new story and the WYSIWYG editor should appear. Keep in mind that the WYSIWYG patch is brand new, as is Version 2 of Image Browser, so there are probably still a few hiccups in this process. But the take-away is that it is quickly becoming possible to lock WYSIWYG functionality in code and deploy it to other sites. 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 Strategy Forum 2013" url: "/articles/content-strategy-forum-2013" type: article date: 2013-09-19 updated: 2014-05-13 --- # Content Strategy Forum 2013 # Content Strategy Forum 2013 Recapping this year's gathering of strategist and CMS professionals By [ Jeff Eaton ](/about/jeff-eaton) September 19, 2013 [Insert Content Here](https://insertcontenthere.com/) listeners may have spotted last week's firestorm of blog posts and tweets about the [Content Strategy Forum](https://www.frontlinein.com/about) in Helsinki, Finland. Held once a year, it draws hundreds of CMS, content strategy, and marketing folks: this year's event was no slouch. I attended the event, and also had a chance to present about the common pitfalls many of Lullabot's clients have dealt with while transitioning to structured content models and multi-channel publishing. Some of the material built on my previous article, *[When Editors Design](https://www.smashingmagazine.com/2013/06/26/controlling-presentation-in-structured-content/),* with extra emphasis on the challenges news and media organizations face when their dreams hit the wall of legacy content and Photoshop-driven-design. Slides are up on [Slideshare](https://www.slideshare.net/slideshow/deblobbing-in-the-real-world/26152687), and Lullabot.com should be seeing a few spin-off articles in the near future. ### Highlights CS Forum was jam-packed with high quality speakers: there were difficult choices in every schedule slot. There *were* a few standouts: Margot Bloomstein's *[Content Strategy for Slow Experiences](https://www.slideshare.net/slideshow/content-strategy-for-slow-experiences-cs-forum-2013/26163960)* made the case for more thoughtful, slower-paced content that helps visitors and customers relax in a chaotic world. She cited research showing that customers call frustrating online checkout experiences "slow," but call engaging and enjoyable ones "fast." Even when the two checkout experiences took the same amount of time, their experience of the process was more important than their watches. Online community was the focus for Misty Weaver. Her *[Closing the Community Gap](https://www.slideshare.net/slideshow/closing-the-community-gap-csforum13/26160674)* talk explored the difference between managing closed communities like support forums, and open communities like social networks where a company's clients talk and connect. As the Drupal community has grown beyond the borders of Drupal.org, it's experienced many of the hiccups she describes: her advice on building healthy interaction between the various "Drupal Islands" is particularly relevant… Native Advertising has been creeping into more news and entertainment web sites; Razorfish's [Hawk Thompson](https://www.slideshare.net/slideshow/breaking-how-native-advertising-is-making-headlines/26164897) gave an excellent overview of the highlights and lowlights. As more advertisers buy space in print and online publications, blending their content with "native" content, [the risk of embarrassing Scientology-level disasters](http://www.washingtonpost.com/blogs/erik-wemple/wp/2013/01/15/the-atlantics-scientology-problem-start-to-finish/) looms large. Hawk emphasized the difference between *blending in to fool an audience,* and *talking about the topics they care about in ways that feel natural.* Finally, Richard Ingram offered a deeply philosophical look at how humans make sense of the world in *[The Importance of Visualization: Mapping the Way Forward.](https://www.slideshare.net/slideshow/the-importance-of-visualisation-mapping-the-way-forward-26169463/26169463)* His talk explored the history of maps and how we've used them to understand chaotic systems -- then explained how the principles of map-making can be used to untangle complex content and business problems. ### Drupal in the Content Strategy World One of the interesting recurring themes throughout the event was the difficulty of getting different disciplines -- content managers, developers, designers, and so on -- on the same page. Everyone understands that it's critical, but many large projects run aground when those teams work in silos, only meeting when they hand off deliverables to each other. As our community heads into DrupalCon Prague next week, it should be interesting to watch how these cross-disciplinary issues play out. Published in: - [ Digital & Content Strategy ](/topics/content-strategy) 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: "FAPI/CCK Confusion: 'value' vs '#value'" url: "/articles/fapicck-confusion-value-vs-value" type: article date: 2010-07-12 updated: 2019-01-11 --- # FAPI/CCK Confusion: 'value' vs '#value' # FAPI/CCK Confusion: 'value' vs '#value' How CCK uses lots of advanced Form API (FAPI) voodoo to build out the details of its fields during the #process part of FAPI processing. By [ Karen Stevenson ](/about/karen-stevenson) July 12, 2010 [Angie Byron](https://www.lullabot.com/about/team/angie-byron) wrote [a nice article](http://drupal.org/node/726282) on Drupal.org that explains something about how to form\_alter CCK fields. At the very end it says: > No, you didn't read that wrong. Sometimes you need to set both \['value'\]\['#value'\] and \['#value'\]\['value'\]. And other times you need to change the field value in $form\_state\['values'\]. It seems to be that one controls the value displayed on the form, and the other affects the value sent to the database. You need both to avoid NULL values and "Value is required for field blah blah blah" errors. If anyone can shed some light on what the heck is going on here, that would be awesome. ;P The short take-away is that CCK uses lots of advanced Form API (FAPI) voodoo to build out the details of its fields during the #process part of FAPI processing. If you want to see what CCK has added to the node form and alter it, you will need to jump in using the #after\_build step, which will bring you into the form after #process has finished. But #after\_build contains a additional information you won't see in hook\_form\_alter(). The form looks different at that point because it has now been processed. You will see some parts that look the same as you would expect to see in a hook\_form\_alter(): ``` ... $form['field_text'][0] = array( 'value' => array( '#type' => 'textfield', '#default_value' => 'President', '#title' => 'My Title', ), ); $form['field_number'][0] = array( 'value' => array( '#type' => 'textfield', '#default_value' => 30, '#title' => 'Years of service', ), ); .... ``` The `value` item here is the name of the field itself. Many CCK fields store something in a column called `value`': text fields, number fields, email fields, etc. Other CCK fields use different column names, nodereference fields store their values in a column called `nid` and userreference fields store values in a column called `uid`. As used in the snippet above, `value` is the name of the column that a CCK textfield is stored in. If you were processing a form that looks like this in hook\_form\_alter(), before #process has been invoked, you would set a value for the field in the item called `#default_value`. In this example we have a field that was set to have a default value of `President`. But during the #process stage of FAPI processing a final value for the field will be set, and it will be transferred to an array called `#value`. So the form has picked up additional information by the time we get to #after\_build: ``` ... $form['#value']['field_text'][0]['value'] = 'President'; $form['#value']['field_number'][0]['value'] = 30; ``` In other words, FAPI has added an array called `#value` that contains the resolved values for each item. The important thing to note is that if you want to alter the final value for these fields at this point, making changes to `#default_value` will have no effect at all, the values have already been transferred to the #value array, which is what FAPI considers the final value of these elements. To change these values in #after\_build, you need to do something like this: ``` $form['#value']['field_text'][0]['value'] = 'Secretary'; $form['#value']['field_number'][0]['value'] = 15; ?> ``` That will set the right values in the form state for FAPI. But even with that the values that appear in the form that the user sees may still look wrong. Depending on the specific field you are trying alter, you also may need to do the following to make the form look right: ``` $form['field_text'][0]['value']['#value'] = 'Secretary'; $form['field_number'][0]['value']['#value'] = 15; ``` Confusing? You bet! As if that wasn't confusing enough, there is one more FAPI `value`/`#value` confusion, one that has nothing to do with CCK. FAPI provides a field type called `value`, and its value is set by `#value`. This is an alternative to a element type of `textfield` or `select` or `hidden`, it's an element that contains a value that the end user will not see and cannot alter. It's a very handy alternative to a hidden field that can contain anything, even an object or an array (hidden fields can only contain strings). It looks like this: ``` $form['secret_unchangeable_value'] = array( '#type' => 'value', '#value' => 'Hidden value that no one can see.', ); ``` If you happened to have something like this in your CCK form, along with all the other `value`'s and `#value`'s.... well, you get the idea. It would be nice if `value` didn't have all these different meanings to CCK and FAPI. But CCK and FAPI emerged at about the same time and it just happened that both of them used that term for their own purposes. 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: "Thank You, Angie" url: "/articles/thank-you-angie" type: article date: 2011-04-27 updated: 2014-05-13 --- # Thank You, Angie # Thank You, Angie By [ Jeff Robbins ](/about/jeff-robbins) April 27, 2011 Angela Byron is moving on. Known as "webchick" to most in the Drupal community, she came to us as a shy, wide-eyed, and timid programmer and over the past 5 years, we've watched her grow into a self-confident decision maker capable of leading an entire open source project. Angie joined Lullabot in 2006 and for her first project, we zoomed her off to London to help us build one of Drupal's first "big projects" – [MTV.co.uk](https://www.mtv.co.uk), the website for MTV in the UK. We all had steep learning curves on that project, but we learned a lot, and launched a great Drupal site. By the next summer, we'd rented an apartment on Manhattan's Upper West Side and Angie, along with Addi Berry, and Nate Haug spent 3 intensive months architecting and building [MyPlay](http://myplay.com/videos), a video website for Sony Music. It was during this time that Addi empowered Angie to swear - a helpful skill on some high-pressure projects. Over the years, Angie has led [Using Drupal](https://www.amazon.com/exec/obidos/ASIN/0596515804/orbit0b-20) (O'Reilly), a book co-authored by several on the Lullabot team. She's also led consulting relationships with Anderson Merchandisers, The New America Foundation, The George Lucas Educational Foundation, and several others. More recently she led development and site architecture for [Grammy365](https://www.grammy.com/recording-academy/membership/recording-academy/about/chapters/recording-academy), an extranet site for members of the Recording Academy. She's also been an empathetic and knowledgeable trainer, participating in many Lullabot [workshops](https://www.lullabot.com/events), [videos](https://drupalize.me/), and the [Do It With Drupal](http://doitwithdrupal.com) conference. But all of that is eclipsed by her biggest project to date: Drupal 7. For the past 3 years, Angie, along with project founder Dries Buytaert, have led the development of the latest version of our beloved CMS. One of only 2 code committers, Angie spent thousands of hours reading and rereading every line of code which was submitted by thousands of developers in the Drupal community; she diplomatically participated in hundreds, if not thousands, of discussions in the Drupal issue queue; and she made decisions – lots of them – which shaped the most revolutionary release of Drupal to date. Simply put, you will not find another person on this planet who cares more about Drupal or is willing to work harder for Drupal than Angela Byron. She's been recognized for her achievements too. Prior to being chosen for the gargantuan task of leading Drupal 7, Angie won the [Google-O'Reilly 2008 Open Source Award for Best Contributor](https://opensource.googleblog.com/2008/07/and-winners-of-2008-google-oreilly-open.html) across all open source projects. More recently, she was the first woman to appear on the cover of [the Linux Journal](https://www.linuxjournal.com/content/interview-angie-byron-drupal). In January and Febrary of this year, Lullabot put her on tour teaching people about Drupal 7 and we printed up (now coveted) t-shirts with WEBCHICK printed across the front in an homage to Metallica. Angie has become a well respected leader in the Drupal community and people are often shocked that she still remains so friendly, approachable, and willing to help out new Drupalers. We've been proud to support Angie as she's worked so tirelessly on all of these projects over the years. We're sorry to see her go, yet we want to be sure to publicly thank her for everything she's done not only for and with Lullabot, but for everything she's done for the people of the Drupal community. Angie has accepted a job at Acquia which will allow her to work full time on Drupal core. We wish her well. We hope that whenever Angie hears herself drop an [f-bomb](https://www.urbandictionary.com:443/define.php?term=F%20-%20Bomb), she'll have fond memories of her time with Lullabot. 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 Open Security Model, Drupal and ExpressionEngine on Security" url: "/articles/the-open-security-model-drupal-and-expressionengine-on-security" type: article date: 2008-06-04 updated: 2014-05-13 --- # The Open Security Model, Drupal and ExpressionEngine on Security # The Open Security Model, Drupal and ExpressionEngine on Security By [ Nate Lampton ](/about/nate-lampton) June 4, 2008 I recently evaluated [ExpressionEngine](https://expressionengine.com/) for viability as a CMS replacement for [Drupal](http://drupal.org). ExpressionEngine (EE) is a commercial CMS tool built on PHP and mySQL by EllisLab. A client of ours was attracted to its clean interface, built-in features, and expandability. I was impressed by the well-designed UI and the flexibility of EE's templating engine. There are definitely lessons the Drupal community can learn from their attention to detail. In fact, as I explored the EE support forums, I discovered a great deal of antagonism towards Drupal -- to my surprise, it wasn't based on features or learning curve, but on the idea that Drupal is *insecure*. In the forums, [comments such as this](https://expressionengine.com/forums/topic/43473) were common whenever Drupal or other CMS systems were brought up for comparison: > Some reasons why one might *not* want to use Drupal? > > [\[2/5\] Drupal Acidfree Module “node titles” SQL Injection Vulnerability](http://secunia.com/advisories/23895/) > [\[2/5\] Drupal Unspecified Spoofing Weakness and Cross-Site Scripting](http://secunia.com/advisories/23586/) > [\[2/5\] Drupal Project Issue Tracking Module Multiple Vulnerabilities](http://secunia.com/advisories/23887/) > [\[2/5\] Drupal Project Module Script Insertion Vulnerability](http://secunia.com/advisories/23908/) > [\[4/5\] Drupal Comment Preview Arbitrary Code Execution](http://secunia.com/advisories/23960/) > [\[1/5\] Drupal Textimage Module Security Bypass](http://secunia.com/advisories/23985/) > [\[1/5\] Drupal Captcha Module Security Bypass](http://secunia.com/advisories/23983/) > > And that’s just for the month of January. RonnieMc has pointed out before, however, that this might actually be a factor to choose Drupal as it presents job security--constantly keeping up with vulnerabilities and updates to all of your components can be a full time job. All the links above reference [Secunia](http://secunia.com/) a reporting service that aggregates reported issues on security. By doing a few searches, you can view the Secunia security issues reported for [Drupal](http://secunia.com/search/?search=drupal) and [ExpressionEngine](http://secunia.com/search/?search=expressionengine). Performing the above searches, you can see that ExpressionEngine has had only one security advisory over the course of 3 years. Over that same time period, Drupal has had over 80. It's easy to draw the incorrect conclusion there that ExpressionEngine therefore is more secure. Secunia [specifically states](http://secunia.com/product/17839/?task=statistics) "**The statistics provided should NOT be used to compare the overall security of products against one another**." And for good reason, what we're experiencing here is a difference in security practices. In this particular difference, Drupal reports its security vulnerabilities, while ExpressionEngine does not. At the time of initial research however, it was not clear that this was the case. With the primary developers touting the security of ExpressionEngine over Drupal, I thought perhaps EE does have much better security. So I set out to see what happens when security issues are reported. ## Finding ExpressionEngine Exploits In one day of evaluation, I found **3 security vulnerabilities** in version 1.6.2 of the core ExpressionEngine software (at that time, the latest version). That didn't bode well for my expectations of EllisLabs' reporting of issues. The vulnerabilities included a [simple XSS attack](https://expressionengine.com/bug_tracker/bug/4285/), [unauthorized deletion of private message files](https://expressionengine.com/bug_tracker/bug/4286/), and allowing of arbitrary code execution (dangerous enough that I e-mailed the developers directly). Unsurprisingly, none of these issues were reported to Secunia. [ ![Executing PHP on EE](/sites/default/files/styles/wide_xs/public/u10/ee-php-small.png.webp?itok=JxQK6xZb "ee-php-small.png")](https://www.lullabot.com/files/u10/ee-php-full.png)Running PHP on the http://demo.expressionengine.com demo installation. ExpressionEngine allows and executes image files that contain PHP code. The code review of ExpressionEngine revealed a very high awareness of secure exploits, and took steps to avoid many forms of attacks. There are several functions built-in that scour uploaded files for potential embedded code, and inline documentation that mentions why certain precautions are taken. ExpressionEngine in itself is a fairly securely written piece of software. What's different between Drupal and ExpressionEngine is how each responds to security vulnerabilities. Despite efforts to write secure code, programmers invariably make mistakes. How those mistakes are handled determines a large part of how secure the software is overall, but more importantly how secure the sites are that run the software. ## Drupal Approach - Announced release Drupal believes in an open security policy. Because all the source code is available at all times, attempting to cover-up security problems by discreetly slipping it into an update isn't viable, because all the changes between versions can be tracked. The fixing of the bug usually happens through a special security process, where the security team is notified via e-mail. The problem is fixed in the source code, then an announcement is made as quickly as possible that an update needs to be applied. This explains the 80+ vulnerabilities listed by Secunia on Drupal, because every security problem is handled in a public manner. Websites running insecure versions of modules are notified via the Update Status module, strongly encouraging administrators to update the module as soon as possible. ## ExpressionEngine Approach - Quiet release EllisLab takes the opposite approach, instead attempting to correct security problems without publicizing their errors. In the case of the 3 vulnerabilities reported from the security review, no warning was ever issued to their clients of potential vulnerabilities. A new release was created and posted to the download area, and sites that are running the previous version of the software receive a notice that a new version has been released. After the new version was released, I finally was able to see exactly how a serious vulnerability was handled. In the ExpressionEngine changelog, the vulnerability that potentially allows total control of a site was summarized in a bullet point: > Increased security with uploaded file names to prevent Apache from overzealously parsing a file as a script. It would stand to reason then, that the phrases for "increased security" or "add additional security" truly mean "fixing a security problem". The [publicly available changelog](https://expressionengine.com/docs/changelog.html) lists at least 14 enhancements of security, each one likely fixing an actual security problem. In these cases, ExpressionEngine is leading their customers into a false sense of security. Claiming a high level of security publicly while quietly fixing easily exploited bugs during development. ## Open Source, Open Security There's nothing wrong with discreetly fixing bugs. Doing so can make it more difficult to find the exact exploit. However, in the case where users can easily compare one version against another, the manner in which the bug is fixed is a small barrier for hackers. Because most web applications are delivered in a parsable scripting language (such as Perl, Python, PHP, JavaScript, ASP, etc.) the full source code of these applications is available to anyone that has ever installed the software. It only takes a single, well-informed user (hacker) to read through the code, and expose potential threats. The "security by obscurity" approach, while always dangerous, has even less effectiveness when the source code is available. So to the folks at ExpressionEngine, yes, keeping your software secure can indeed be a full-time job. Because mistakes are inevitable in coding, security problems will exist in all software. Simply not telling the software's users about insecurities won't keep hackers from exploiting them. It's a matter of educating, that the best way to have a secure website is to have an up-to-date website. ExpressionEngine is serious about fixing problems, so long as they don't have to tell anyone. With their current marketing spin on security, the most difficult task will be honesty with those users that have been mislead. 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: "Lullabot is now Bluemarine Synergistics" url: "/articles/lullabot-is-now-bluemarine-synergistics" type: article date: 2012-04-01 updated: 2014-05-13 --- # Lullabot is now Bluemarine Synergistics # Lullabot is now Bluemarine Synergistics The merger to end all mergers! By [ Jeff Robbins ](/about/jeff-robbins) April 1, 2012 Today starts a new month and a fresh beginning for Drupal. After many discussions over many drinks at DrupalCon Denver, we’re proud to announce a strategic inflection point for the Drupal community. As of today, the following companies will be merging into one company - a new formidable force in the Drupal community: - [Lullabot](https://www.lullabot.com/) - [Node One](http://nodeone.se) - [Phase2 Technology](https://phase2.io/) (formerly merged with [Treehouse](http://www.treehouseagency.com)) - [Chapter Three](http://www.chapterthree.com) - [Four Kitchens](https://www.fourkitchens.com/) - [The Fifth Element](http://fifthelement.com) - [ImageX Media](https://imagexmedia.com/) - [Palantir](https://www.palantir.net/) - [Gorton Studios](https://www.gortonstudios.com/) - [Volacci](https://www.volacci.com/) - [Pantheon](https://pantheon.io/) - [Aten Design Group](https://atendesigngroup.com/) - [Advantage Labs](https://www.advantagelabs.com/) These 13-ish independent companies will be joining forces to form a new paradigm-shifting meta-company: **Bluemarine Synergistics**. Employing over 850 individuals spread out across eight continents, the company’s name combines the popular and much-loved Drupal theme with the word “synergistic,” implying strategic partnership, enterprise-empowered logistics, and e-business efficiencies. Finally, Drupal will have a single dominant company for enterprise customers to be their long term partner. By combining so many Drupal companies into a scalable, virtualized talent cloud, we can make it easier for customers to find us – they won’t need to shop around. And we can have a single, unified, sponsorship presence at DrupalCon and Drupal camps. It’s not only good for Drupal, it’s good for the Drupal community! Just think of the parties a company of this size can throw. It will be a lot of free beer – free, as in free beer. With estimated combined revenues of $1.21 billion, Bluemarine Synergistics will be expanding over the coming year, hiring a 200-person sales force to create a giant “Drupal tuna net” to capture companies in new verticals such as cosmetics, agriculture, Amish social organizations, multilevel marketing, “waste management,” adult entertainment, online file transfer services, and even the hunting and fishing industries. It turns out that there are many companies, even entire industries, not yet using Drupal. For instance, the aging baby boomer population represents a huge market not yet tapped by Drupal. Bluemarine Synergistics will attack the baby boomers. > “We believe that there’s a great deal of money to be made here,” said former Lullabot CEO Jeff Robbins. “It turns out that everyone wants a website. Drupal makes websites! Bluemarine Synergistics can make your website for you, even if you’re old or Amish or whatever.” > “It’s amazing how quickly it all came together,” exclaimed Ben Finklea, former CEO of Volacci who will hold the title of Social Media Intern for the combined company. “Almost all 13 or whatever CEOs happened to be at Rock Bottom Brewery one night during Drupalcon Denver. We started doing tequila shots and yada, yada, yada – we merged! My title has changed but my job is basically the same as it always was.” > “Twice in one month... WE LOVE MERGING!” said Michael Caccavano, former President of Phase2, former CEO of Treehouse. > “Mergers are our way of showing we care about each other. We could just hang out at DrupalCon twice a year, but this way we are together all the time.” said former Phase2 Technology CEO, Jeff Walpole. > “This company has a lot of promise. It has a bright future, but I’m also proud that we’re looking back and rediscovering some of the best elements of our past. We always felt removing Bluemarine from core was a step backwards. It’s just so affirming to be a part of this giant leap forward,” said former Gorton Studios CEO Drew Gorton. > Glenn Hilton, the former CEO of ImageX Media added, "It's a great move for ImageX. All the clients we lost to our competitors, we've now gained back." > “We used to lose sleep at night, praying that potential clients would fax us their Drupal project RFPs. Now, with our powers combined, none of us will ever go hungry again!” – Aaron Stanush, former partner at Four Kitchens and newly appointed Strategy Strategist at Bluemarine Synergistics. > “Originally, our promise to the Drupal community was that we would exclusively focus on professional services. In only a couple weeks’ time, we realized that customers would appreciate a comprehensive offering that includes infrastructure. Pantheon is proud to consider itself a cornerstone in fulfilling the Bluemarine Synergistics promise. Can you put ‘cornerstone’ in italics for the press release? Thanks.” – David Strauss, former CTO at Pantheon Systems. > "All of the former Aten Design Group team members are ecstatic about becoming a part of Bluemarine Synergistics. The BS product line is so bleeding-edge. Who can say no to the 24-hour-a-day website package? Just think about it – websites, any time of the day." – Jon Clark, former Business Development Director at Aten Design Group, now Assistant to the Director of Execution, BS Products Division. Bluemarine Synergistics finally solves the “coopetition” problem once and for all. We’re all friends. We’re all working on the same software. Why do we need to compete for employees? Why do we need to compete for clients? Heck, why do we all need to decide what to charge? Think of all the good we can do if we just band together! One big company running Drupal for the good of everyone – that’s Bluemarine Synergistics. Company headquarters are currently being built on property located approximately two miles west of New Amsterdam, Indiana – the geographic center of all the previous headquarters when weighted by employees, revenue, and contributions to Drupal core. Please share your excitement about Bluemarine Synergistics on your favorite social media outlets. Use the hashtag [\#bs4drupal](https://twitter.com/search/bs4drupal) because Bluemarine Synergistics *is* B.S. For more information, please visit . Thanks! 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: "Bringing Drupal to the U.S. Government" url: "/articles/bringing-drupal-to-the-us-government" type: article date: 2009-10-06 updated: 2014-05-13 --- # Bringing Drupal to the U.S. Government # Bringing Drupal to the U.S. Government By [ Jeff Robbins ](/about/jeff-robbins) October 6, 2009 [ ](https://www.flickr.com/photos/jeffeaton/3978615797) ![Jeff Robbins in Washington, DC](/sites/default/files/styles/wide_xs/public/u2/JeffRobbinsInDC.jpg.webp?itok=EutDRYgS "JeffRobbinsInDC.jpg") When I saw Tim O'Reilly last year, he asked how much Drupal is being used in government agencies. And other than its popularity for political campaigns and NGOs, my answer was "Not too much". But since that time we've seen quite a boom. In addition to these federal sites, we've been seeing Drupal at the state level with exemplary sites like [New York State Senate](https://www.nysenate.gov/) which leverages email, mobile, and social networking to keep its citizens connected. (We'll have the New York Senate team presenting about this site at the [Do It With Drupal Seminar](http://www.doitwithdrupal.com) in December.) We're also starting to see Drupal becoming a flexible and inexpensive platform for local city and town governments to create sites with much more interactive and rich content than they could afford in the past. It's exciting to see Drupal bringing a new level of transparency and openness to government -- allowing for more understanding and involvement by citizens. While most of this government information and data was theoretically open in the past, the truth was that you had to find the right people to open the right file cabinets in the right storage rooms to get what you needed. Now, thanks to the web, the barrier of involvement has gotten a lot lower. Whether it's finding out the wait time at the nearest Registry of Motor Vehicles, downloading the application for a dog license, or keeping track of how your state representative is voting on the issues, the Internet is making it easier. Drupal can provide a quick flexible way to build these feature rich sites. And for all those other government agencies looking to get up to speed quickly with Drupal, [we'll be happy to help](https://www.lullabot.com/on-site-drupal-training)! 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: "Forms API 2.0 Cheat Sheet" url: "/articles/forms-api-20-cheat-sheet" type: article date: 2006-11-01 updated: 2014-05-13 --- # Forms API 2.0 Cheat Sheet # Forms API 2.0 Cheat Sheet By [ Jeff Eaton ](/about/jeff-eaton) November 1, 2006 With the first beta release of Drupal 5 [now in the news](http://drupal.org/drupal-5.0-beta1), it's time for module developers to start upgrading their code to work with the new changes. The means -- of course! -- diving into the Forms API. While Drupal.org has piles of excellent documentation on the API, sometimes it's useful to keep some of the trickier bits handy for reference. We've put together [this one-page cheat sheet](https://www.lullabot.com/files/formapi.pdf) for Drupal 5's FormAPI: download it, print it, love it! 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: "Do you wanna be Lullabot Drupal Certified?" url: "/articles/do-you-wanna-be-lullabot-drupal-certified" type: article date: 2009-08-11 updated: 2016-04-07 --- # Do you wanna be Lullabot Drupal Certified? # Do you wanna be Lullabot Drupal Certified? On the subject of Drupal Certification in the community By [ James Walker ](/about/james-walker) August 11, 2009 When the subject of Drupal Certification comes up within the community as it has done recently (see [Dries' blog post](https://dri.es/on-drupal-certification-programs) and the [mailing list thread](http://lists.drupal.org/pipermail/consulting/2009-August/003512.html)), we here at Lullabot take notice. While we don't generally take an active role in the conversation, our name invariably gets thrown into the mix. That's flattering. We work hard to be the top Drupal training company. We have put over a thousand people through intense, hands-on workshops and taught thousands more through books, DVDs, etc. We love empowering people to make cool stuff with Drupal and have done our best to make a sustainable business doing so. But... **We have no current plans to offer certification.** That is certainly not because we haven't discussed it. We have had countless hours of internal discussions about it. We have even discussed it with potential partners, existing clients, and others but we always come to the same conclusions: it's not time yet. ## Is the investment worth it? Our focus is to arm people with the knowledge they need to make awesome Drupal sites, modules and themes. This is (in part) how we help to grow the community and further the platform. Our training offerings have been very succesful towards this goal. Putting together certification exams, adminstering them and providing certification is an ongoing, significant investment that doesn't directly advance our mission. Lullabot is not a big company. Anytime we consider a new offering, we have to be sure that it makes sense for us. To do it *right*, we would put tens (if not hundreds) of thousands of dollars into developing the program. That kind of investment needs to be worth it. ## The drop is always moving ... Probably too fast, or at least too far. A huge challenge we face with our training curriculum is keeping it up to date. Each new Drupal version brings vast, sweeping changes to APIs and interface which means that annually (at least), we revamp our training materials. To further complicate matters, any Drupal site of significance uses Drupal Core plus 25 - 50 contributed modules. Any meaningful certification would need to include enough of these to ensure that the certificate holder was capable of delivering quality work. However, the rate of change within contrib modules is even faster than core. Drupal developers, themers and implementers currently face an enormous task of staying relevant. A certification program, therefore, needs to not only follow the same level of work, but should reflect that competence in version 6 does not necessarily translate to version 7. "Lullabot Drupal 6 Core, CCK 2.x and Views 2.x Development Certified" doesn't have quite the same ring. ## Certification is a reputation business And, therefore, delicate. We take extreme pride in our work. To be "Lullabot Drupal Certified" means that we are - in some way - vouching for a person's ability to deliver. A few bad apples could quickly begin to devalue what it means to be "Lullabot Drupal Certified". Worse, to someone with no involvement in Drupal "Lullabot Drupal Certified" looks no different from "Company B Drupal Certified". If "Company B" certification is cheaper and easier to complete (and results in less skilled Drupal talent), then the whole "Drupal Certification" moniker degrades towards meaninglessness. ## Wherefore art thou, Association? To combat the reputation issue, one can look to the Drupal Association to either provide training material, or "certify the certifiers". As a founding member of the Association, I've seen this discussion from that angle every time it's come up. Chances of the Association stepping in here are minimal. Dries (also President of the Association) has stated that he's against it publicly. It's a free market, open to *anyone* who wants to participate. ## So, what now? Lullabot loves you. Many of the things we do (like [Online Workshops](https://www.lullabot.com/workshop/drupal-fundamentals-online-workshop/online-2009) and [DVDs](http://store.lullabot.com/)) are direct responses to feedback and requests from people like you. While people have often suggested that Lullabot could or should offer certification, few people have approached us and asked if we would certify them. If you're out there, now is your chance: Do you wanna be Lullabot Drupal Certified? 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 Usability Testing Day 1" url: "/articles/drupal-usability-testing-day-1" type: article date: 2009-02-25 updated: 2014-05-13 --- # Drupal Usability Testing Day 1 # Drupal Usability Testing Day 1 By [ Addison Berry ](/about/addison-berry) February 25, 2009 This morning I was up early, along with seven other folks, to dig into the first day of [Drupal usability testing](http://drupal.org/node/365191) at the University of Baltimore. Our community has been able to take part in some other usability tests in the past, but the timing of this one is particularly exciting because we will be testing Drupal core while it is still in code thaw. This means that we can try things out and get valuable feedback while we can still make changes to the code. We all met at the lab in the morning and then spent time double-checking the equipment and getting coffee and tea into our team. After a few technical checks and getting settled, we reviewed the plan for the day, and walked through the task lists that we would have the subject's run through. We are all ready with text editors and sticky notes for writing out specific issues. We also papered the room with some traveling whiteboard sheets so we can freely write and organize our issues. This is my first time in a testing lab and it is pretty darned cool. We have a room split with a one-way mirror and we can see the test subject's computer screen as well as watch their eye-tracking. The eye-tracking is fascinating stuff but the real good stuff comes from having the subject talk through the things they are doing, what they are looking for and why they are approaching things the way they are. The folks we had in today had both built Drupal sites before and so we set them up with a range of tasks, from basic, like adding content, to the "not so straight-forward as they sound," like setting up search or sending emails when new content is created. There was an interesting mix of surprise and frustration at times, along with places where things just worked and made sense. We spent time after each test talking through our observations, writing and slapping sticky notes all over the walls. The testing days are long days, this one wrapped up after 10 p.m., and we are all pretty tired, but also energized with thoughts and ideas generated from the first two tests. Looking forward to continuing tomorrow. 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 ahah.js in Core, more than just form elements" url: "/articles/drupal-ahahjs-in-core-more-than-just-form-elements" type: article date: 2007-10-08 updated: 2014-05-14 --- # Drupal ahah.js in Core, more than just form elements # Drupal ahah.js in Core, more than just form elements By [ Nate Lampton ](/about/nate-lampton) October 8, 2007 I was working on a patch for poll module today when I made a wonderful discovery.The AHAH (similar to AJAX) enhancements for Drupal core work with all DOM elements, not just form elements (like buttons and select lists). All the code necessary is already in Drupal core, though the following implementation with poll module is still in progress. The thing I love about Drupal's new AHAH implementation is making javascript available to PHP developers. Here's the extent of code necessary to add this behavior to poll module: ```php $form['choice_wrapper']['poll_more'] = array( '#type' => 'submit', '#value' => t('Add another choice'), '#description' => t("If the amount of boxes above isn't enough, click here to add more choices."), '#weight' => 1, '#submit' => array('poll_more_choices_submit'), // If no javascript action. '#ahah' => array( 'path' => 'poll/js', 'wrapper' => 'poll-choices', 'method' => 'append', 'effect' => 'slide', ), ); ``` This is the normal FormAPI implementation of ahah.js. All necessary javascript is included on the page as necessary, not a line of javascript needed by the PHP developer. If we wanted to do the same thing with a link (or DIV, TABLE, or whatever HTML tag you wanted), we could add the javascript to the page manually: ```php $ahah_binding = array( 'url' => url('poll/js'), 'event' => 'click', 'wrapper' => 'poll-choices', 'selector' => '#edit-poll-more', 'effect' => 'slide', 'method' => 'append', 'progress' => array('type' => 'throbber'); ); drupal_add_js('misc/jquery.form.js'); drupal_add_js('misc/ahah.js'); drupal_add_js(array('ahah' => array($element['#id'] => $ahah_binding)), 'setting'); return 'Click here for more choices'; ``` This is a totally untested example, but it shows the necessary code to add AHAH to any element on the page, not just form items. It's pretty cool, and I hope that AHAH will aid in the battle for better usability in Drupal. 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: "An Update on the Art of Estimation" url: "/articles/an-update-on-the-art-of-estimation" type: article date: 2012-10-31 updated: 2015-07-27 --- # An Update on the Art of Estimation # An Update on the Art of Estimation Estimating is an arcane art. See how we get as close to accurate as possible with our take on poker planning. By [ Jerad Bitner ](/about/jerad-bitner) October 31, 2012 If you haven't read it already, there is a lot of really good meat in the [first article on this topic](https://www.lullabot.com/articles/the-art-of-estimation) by [Seth Brown](https://www.lullabot.com/about/seth-brown). Seth does an excellent job of digesting our process of working with our clients during the decomposition of a project to figure out just how much this baby is gonna cost. ### Estimating One thing that has evolved since the writing of Seth's article, is [the spreadsheet](https://docs.google.com/spreadsheets/d/1i9e3f6Yta1P5LPhdHU05KVN-rhimmtEGXZBI3rvLa0g/edit) that we use for our work breakdown, and the exact method in which we go about estimating the various deliverables. The spreadsheet is full of notes on how we go about our various estimates, and the things what we calculate automatically. ![new-spreadsheet-img](http://monosnap.com/image/yEHYSOCmInQAMRrmVxHrnXzrD.png) First we have at least three developers who are going to be working on the project conduct a blind estimation line by line with white text on a white background and then do a reveal all at once. During the reveal, if we find that there ends up being large discrepancies in the estimates, we talk about the assumptions each developer made in order to make sure we're all on the same page with what is expected for said deliverable and give them a chance to adjust their estimates given any new information. We then average these as the total estimated developer hours. There is also an added "pm factor" for our project manager which is a .25 multiplier. This multiplier is based on Lullabot's historic data for our projects where we've found that project managers end up spending about one quarter of the time developers spend on a project overall. Our updated spreadsheet also introduces a concept of a risk or uncertainty factor which is helpful in calling out the unknown aspects of a project. The higher the risk, the more unknown that element of the project is. Measuring risk also helps measure the difference between a fixed-bid VS a time and materials approach to the project. Risk is measured on a scale from 0-5 and we calculate additional developer hours by taking the sum of the developer average plus the pm factor and multiply by the risk rating. Finally we multiply again by point one (`=sum(Average+PM Factor)URating.1`). We're basically asking our developers how uncertain they are about their estimates and why. It's usually because of some unknown factors, and we try to determine if these factors are something that the client can help us aleviate. If so we go back to make sure that expectations are clearer and give our developers the opportunity to reduce the risk and adjust their estimates. But sometimes it's a factor that is simply not controllable and if the risk remains high it increases the overall estimated hours. ### Resourcing We've also added a new sheet which helps us to figure out what resources we need on the project in order to accomplish the estimated deliverables and to help us to align our resources with the timeline of the project and the estimated amount of work. This helps our project mangers to get an idea of which deliverables they should schedule for each sprint. For instance, if they know that they have 80 man hours during Sprint 1, they also know that they can schedule X amount of deliverables for that first sprint based on how many hours were estimated. ![resource-img](http://monosnap.com/image/HMdRyIEhepatZoKmyGFdBBbfC.png) This also gives us a chance to think about dates going forward and account for any scheduled vacation times for the developers who are on the project and holidays can also be factored into the timeline. In the case of a fixed bid project, we can either add resources or if we have no more available resources, advise the client that we'll need to cut scope in order to meet their budget. We continue to revise our approach to this process as we find better ways of trying to come up with more accurate estimates in order to meet our deadlines, budgets and to set client expectations appropriately. So far our latest version feels like the most accurate we've gotten to date, but we'd love to hear what you think about it! How has project estimation changed for you? 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: "When Regular Expressions Go Too Far" url: "/articles/when-regular-expressions-go-too-far" type: article date: 2013-08-14 updated: 2014-05-14 --- # When Regular Expressions Go Too Far # When Regular Expressions Go Too Far Taming Filthy, Greedy Regular Expressionses With the Ungreedy Flag By [ Brock Boland ](/about/brock-boland) August 14, 2013 This week, I finally learned how to fix a regular expression problem that has long vexed me and stumped my Googling skills. The details are tricky to explain, but the simplest example is easy to describe. In many cases, a regular expression would get the *last* instance of a match in a string instead of the *first* instance. This has caused me all sorts of headaches when using `preg_replace()` to touch up some HTML in Drupal. Of course, it's rarely necessary (or even a good idea) to try [parsing HTML with regular expressions](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454), and [QueryPath](https://drupal.org/project/querypath) is often a better solution. That said, I ignored my own advice and decided to use a regular expression in an input filter anyway. Let's say I want to wrap the text of any header tag (`h1` through `h6`) in a `span` tag, so that I can better style it with CSS. Here's a basic `preg_replace()` call that you might expect would do the trick: ```php $html = "

First Header

Some text.

Second Header

"; $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. ![Kyle in boxers at the Drupalize.me booth](/sites/default/files/styles/max_900/public/assets/2016-04/kyle_dmebooth_2012.jpg.webp?itok=WkHWEML_ "kyle_dmebooth_2012.jpg") 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. ![EatonCon Invite](/sites/default/files/styles/max_900/public/assets/2016-04/eatoncon_diskette_0.jpg.webp?itok=zk9XOhEy "eatoncon_diskette_0.jpg") 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. ![move-these.png](/sites/default/files/styles/wide_xs/public/move-these.png.webp?itok=LSkz9dmd "move-these.png") 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: ![cck-links-edit.png](/sites/default/files/styles/wide_xs/public/cck-links-edit.png.webp?itok=jeNYQXIA "cck-links-edit.png") 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. ![rendered-links.png](/sites/default/files/styles/wide_xs/public/rendered-links.png.webp?itok=iO8xuGHS "rendered-links.png") 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 * ![Ed Sussman](/sites/default/files/styles/wide_xs/public/u2/edsussman-resize2.jpg.webp?itok=PPPPRIEa "edsussman-resize2.jpg") 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!!) ![wwwdrupalorg-thumb.png](/sites/default/files/styles/wide_xs/public/wwwdrupalorg-thumb.png.webp?itok=hISLr5bJ "wwwdrupalorg-thumb.png") 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

All%20of%20the%20various%20phones%20have%20various%20ways%20of%20uploading/downloading%20the%20ringtones,%20so%20I%20won't%20attempt%20to%20give%20instructions%20here,%20but%20if%20anyone%20has%20any%20tips%20and%20info%20about%20what%20worked%20for%20them,%20please%20post%20comments.

%0A

Here's%20a%20loop%20of%20the%20song%20in%20various%20formats:

%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 ![views1-annotated.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/views1-annotated.png.webp?itok=WwMJFufK "views1-annotated.png") *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. ![views2-annotated.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/views2-annotated.png.webp?itok=mhJnyWiS "views2-annotated.png") *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. ![views3.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/views3.png.webp?itok=mAgn4U58 "views3.png") *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!) ![views0-annotated.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/views0-annotated.png.webp?itok=aP241obw "views0-annotated.png") *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! ![views6.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/views6.png.webp?itok=yTUK3PUY "views6.png") *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. ![searchresults-annotated.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/searchresults-annotated.png.webp?itok=_1jNH35R "searchresults-annotated.png") *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. ![views4-annotated.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/views4-annotated.png.webp?itok=xVcgLZyr "views4-annotated.png") *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. ![views5-annotated.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/views5-annotated.png.webp?itok=I7i7lsqq "views5-annotated.png") *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. ![search1.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/search1.png.webp?itok=GNrSKknB "search1.png") *Figure IX: All content in the themes category* ![search2.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/search2.png.webp?itok=3Izhk2k7 "search2.png") *Figure X: All content in the themes category that matches the search term "deco"* ![search3.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/search3.png.webp?itok=ACKH8-RB "search3.png") *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. ![config.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/config.png.webp?itok=ePM6rXc4 "config.png") *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/) ![An image of the High Moon webcomic on Zuda.com.](/sites/default/files/styles/wide_xs/public/high-moon.png.webp?itok=dJDwcKH5 "high-moon.png") 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 ''. $site_title .''; } 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. ![Reroute email configuration](/sites/default/files/styles/wide_xs/public/u35/Reroute%20Email%20%20JUNO%202013-02-14%2015-50-56.jpg.webp?itok=keza45Ws "Reroute Email JUNO 2013-02-14 15-50-56.jpg") ### 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. ![Screenshot of boring, repetitive code](/sites/default/files/styles/wide_xs/public/u12/scaffolding-menu-hook-shadow.png.webp?itok=heJLdyQ_ "scaffolding-menu-hook-shadow.png") 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) ![Screenshot of Scaffolding Module's administrative overview page](/sites/default/files/styles/wide_xs/public/u12/scaffolding-admin-shadow.png.webp?itok=KdoVO7Ns "scaffolding-admin-shadow.png") ### 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. ![Screenshot of Scaffolding Example's edit and confirmation pages](/sites/default/files/styles/wide_xs/public/u12/scaffolding-edit-and-confirm-shadow.png.webp?itok=Vk2SmvNR "scaffolding-edit-and-confirm-shadow.png") ### 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/) ![Drupal UE](/sites/default/files/styles/wide_xs/public/u2/DrupalUEshot.png.webp?itok=nJKg-Hxx "DrupalUEshot.png") 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. ![screen_shot_2013-09-05_at_11.55.27_am.png](/sites/default/files/styles/wide_xs/public/field_regular_upload/screen_shot_2013-09-05_at_11.55.27_am.png.webp?itok=FM4hPysc "screen_shot_2013-09-05_at_11.55.27_am.png") ## 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. ![screenshot](https://www.lullabot.com/sites/default/files/field_regular_upload/views-pager.png) 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: ![Image sizes settings](/sites/default/files/styles/wide_xs/public/assets/2016-04/image-size-settings.png.webp?itok=e5sJLRir "image-size-settings.png") 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: ![Image exact settings](/sites/default/files/styles/wide_xs/public/assets/2016-04/image-exact-settings.png.webp?itok=vMg_Ej5P "image-exact-settings.png") 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: ![Content settings](/sites/default/files/styles/wide_xs/public/assets/2016-04/image-attach-content-settings.png.webp?itok=VvjQ3Bdp "image-attach-content-settings.png") When enabled, this causes an upload field to appear when you create a node: ![Image attach field](/sites/default/files/styles/wide_xs/public/assets/2016-04/image-attach-field.png.webp?itok=vGR7mZJO "image-attach-field.png") 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: ![Image View options](/sites/default/files/styles/wide_xs/public/assets/2016-04/image-view.png.webp?itok=mLThAWRe "image-view.png") ### imagefield and imagecache imagefield module is different, in that it allows you to add an "image" type field to any CCK node type: ![imagefield add field](/sites/default/files/styles/wide_xs/public/assets/2016-04/imagefield-add-field.png.webp?itok=NTanzK5H "imagefield-add-field.png") 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: ![imagecache settings](/sites/default/files/styles/wide_xs/public/assets/2016-04/imagecache-settings.png.webp?itok=kFALpK-T "imagecache-settings.png") And here's a screenshot of the node/add/content\_product page: ![imagefield preview](/sites/default/files/styles/wide_xs/public/assets/2016-04/imagefield-preview.png.webp?itok=q-uz7U2w "imagefield-preview.png") 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. ![A screenshot of graphs representing page views and bounce rates of a webpage](/sites/default/files/styles/max_900/public/2023-11/2010-01.jpg.webp?itok=tCiyqYM-) 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. ![google-query-explorer_0.jpg](/sites/default/files/styles/wide_xs/public/google-query-explorer_0.jpg.webp?itok=AB_x94Y0 "google-query-explorer_0.jpg") 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) ![Drupal's search administration interface has controls for the scoring factors](/sites/default/files/styles/wide_xs/public/assets/2016-04/content-ranking_0.png.webp?itok=yKQpNvDg "content-ranking_0.png") *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 Drupal statistics module can influence search results](/sites/default/files/styles/wide_xs/public/assets/2016-04/statistics-admin-oval_0.png.webp?itok=R0Amlpmp "statistics-admin-oval_0.png") *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. ![Drupal search results with default scoring factors](/sites/default/files/styles/wide_xs/public/assets/2016-04/initial-results-annotated_0.png.webp?itok=4Qx2TNty "initial-results-annotated_0.png") *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; } ![Code added to theme_search_item to show score](/sites/default/files/styles/wide_xs/public/assets/2016-04/phptemplate_search_item.png.webp?itok=hOq9FWnT "phptemplate_search_item.png") *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. ![Search results that show the ranking score](/sites/default/files/styles/wide_xs/public/assets/2016-04/search-initial-with-score-annotated.png.webp?itok=t9_Rc72r "search-initial-with-score-annotated.png") *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. ![Search results with the keyword scoring factor increased](/sites/default/files/styles/wide_xs/public/assets/2016-04/boost-keyword-results-annotated.png.webp?itok=Ie62YfXo "boost-keyword-results-annotated.png") *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 Drupal advanced search form with scoring factor widgets](/sites/default/files/styles/wide_xs/public/assets/2016-04/advanced-search-factors-annotated.png.webp?itok=rEt7Zb5j "advanced-search-factors-annotated.png") *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) ![Forum access use case.](/sites/default/files/styles/wide_xs/public/assets/2016-04/forum-access-use-case.png.webp?itok=zrfu_SxA "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): ![Forum Access on containers.](/sites/default/files/styles/wide_xs/public/assets/2016-04/forum-access-container.png.webp?itok=E39XOteC "forum-access-container.png") 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 on forums.](/sites/default/files/styles/wide_xs/public/assets/2016-04/forum-access-forum.png.webp?itok=KfDNXbwR "forum-access-forum.png") Forum Access restricts the list of forums to only those the user has access to: ![Forum Access while posting.](/sites/default/files/styles/wide_xs/public/assets/2016-04/forum-access-post.png.webp?itok=VvDNJtyR "forum-access-post.png") **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) ![Taxonomy Access permissions](/sites/default/files/styles/wide_xs/public/assets/2016-04/taxonomy-access-permissions.png.webp?itok=nnNbMo2i "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: ![Taxonomy Access posting](/sites/default/files/styles/wide_xs/public/assets/2016-04/tac-post.png.webp?itok=k0Mye-fk "tac-post.png") **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: ![TAC Lite permissions](/sites/default/files/styles/wide_xs/public/assets/2016-04/tac_lite-permissions.png.webp?itok=KbZd2dRa "tac_lite-permissions.png") 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) ![A list of all hooks executed on the given page.](/sites/default/files/styles/wide_xs/public/assets/2016-04/hooks-exposed.png.webp?itok=_VXviNrd "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) ![A list of all hooks executed on the given page.](/sites/default/files/styles/wide_xs/public/assets/2016-04/template-files-exposed.png.webp?itok=Xhq67sRL "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) ![A list of all hooks executed on the given page.](/sites/default/files/styles/wide_xs/public/assets/2016-04/theme-functions-exposed.png.webp?itok=Cdwu7c6A "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. ![coffee - Google Search.jpg](/sites/default/files/styles/wide_xs/public/coffee%20-%20Google%20Search.jpg.webp?itok=-B-gFb4I "coffee - Google Search.jpg") ## 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=kml kml http://www.example.com/download?format=georss georss ``` 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. ![coffee hound - Google Maps.jpg](/sites/default/files/styles/wide_xs/public/coffee%20hound%20-%20Google%20Maps.jpg.webp?itok=qagVaoha "coffee hound - Google Maps.jpg") 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. ![Coffee Hound.jpg](/sites/default/files/styles/wide_xs/public/Coffee%20Hound.jpg.webp?itok=NJT_NmIb "Coffee Hound.jpg") 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." ![Starbucks-1.jpg](/sites/default/files/styles/wide_xs/public/Starbucks-1.jpg.webp?itok=-2aGkG1o "Starbucks-1.jpg") 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. ![bot-retreat.jpeg](/sites/default/files/styles/wide_xs/public/bot-retreat.jpeg.webp?itok=bP-0YBZn "bot-retreat.jpeg") 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. ![sketch.jpg](/sites/default/files/styles/wide_xs/public/sketch.jpg.webp?itok=Q57l-fqS "sketch.jpg") 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) ![dashboard.jpeg](/sites/default/files/styles/wide_xs/public/dashboard.jpeg.webp?itok=5vnfmnsf "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'. ![Views | drupal.org_.jpg](/sites/default/files/styles/wide_xs/public/Views%20%7C%20drupal.org_.jpg.webp?itok=NKjDuE5C "Views | drupal.org_.jpg") 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: ![Views | drupal.org-1.jpg](/sites/default/files/styles/wide_xs/public/Views%20%7C%20drupal.org-1.jpg.webp?itok=7EMOguJZ "Views | drupal.org-1.jpg") 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. ![pr](/sites/default/files/styles/wide_xs/public/assets/2015-06/lullabot.com_2013-07-13_10-01-13.jpg.webp?itok=0ajBm8Gm "lullabot.com_2013-07-13_10-01-13.jpg") 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. ![pr](/sites/default/files/styles/wide_xs/public/assets/2015-06/lullabot.com_2013-07-13_10-02-14.jpg.webp?itok=HxzovYho "lullabot.com_2013-07-13_10-02-14.jpg") 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. ![pr](/sites/default/files/styles/wide_xs/public/assets/2015-06/lullabot.com_2013-07-13_10-03-12.jpg.webp?itok=dmBxpKVJ "lullabot.com_2013-07-13_10-03-12.jpg") 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): ![ahoy-desktop.png](/sites/default/files/styles/wide_xs/public/u51/ahoy-desktop.png.webp?itok=er5xadZI "ahoy-desktop.png") ### My MAMP Pro setup: ![ 2013-03-07 11-37-16.png](/sites/default/files/styles/wide_xs/public/u51/%202013-03-07%2011-37-16.png.webp?itok=PwaKO-X8 " 2013-03-07 11-37-16.png") ### The ip address for my local machine: ![localipmachine.png](/sites/default/files/styles/wide_xs/public/u51/localipmachine.png.webp?itok=qHIYatg0 "localipmachine.png") ### An additional MAMP Pro alias for this site: This maps my local ip address, and the xip.io naming convention. ![aliasadd.png](/sites/default/files/styles/wide_xs/public/u51/aliasadd.png.webp?itok=4Nkpa9_C "aliasadd.png") 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. ![CCK HOWTO: two date fields with different widgets](/sites/default/files/styles/wide_xs/public/assets/2016-04/cck-howto-date-widgets.png.webp?itok=HqCxnmXk "cck-howto-date-widgets.png") 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: ![CCK HOWTO: The three parts of a field](/sites/default/files/styles/wide_xs/public/assets/2016-04/cck-howto-three-parts-of-a-field.png.webp?itok=3bAdy2Lm "cck-howto-three-parts-of-a-field.png") ## 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). ![Adding a CCK text field](/sites/default/files/styles/wide_xs/public/assets/2016-04/add-text-field_0.png.webp?itok=q6pLC8W3 "add-text-field_0.png") 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. ![Adding a CCK number field](/sites/default/files/styles/wide_xs/public/assets/2016-04/cck-howto-add-integer.png.webp?itok=h6mdlvBl "cck-howto-add-integer.png") 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. ![Comparing text and integer field configuration options](/sites/default/files/styles/wide_xs/public/assets/2016-04/cck-howto-compare-integer-date-settings-640.png.webp?itok=a7M1RC6j "cck-howto-compare-integer-date-settings-640.png") 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". ![CCK HOWTO: Turning on multiple field values](/sites/default/files/styles/wide_xs/public/assets/2016-04/cck-howto-multiple_0.png.webp?itok=Ig45W30O "cck-howto-multiple_0.png") 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. ![Multiple fields, multiple input formats](/sites/default/files/styles/wide_xs/public/assets/2016-04/cck-howto-multiple-input-formats.png.webp?itok=HH5tHpNM "cck-howto-multiple-input-formats.png") ![Multiple fields, multiple input formats; the results](/sites/default/files/styles/wide_xs/public/assets/2016-04/cck-howto-multiple-input-formats-result.png.webp?itok=ob0FGRI0 "cck-howto-multiple-input-formats-result.png") 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. ![Fields retain semantic value across content types](/sites/default/files/styles/wide_xs/public/assets/2016-04/cck-howto-field-semantic.png.webp?itok=OQgtaEx7 "cck-howto-field-semantic.png") 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 <?php print $head_title ?> >

``` 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: **&lt;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: ![Drupal Filtered HTML filters default order](/sites/default/files/styles/wide_xs/public/assets/2016-04/drupal-filters-default-order.png.webp?itok=4PsKNRFg "drupal-filters-default-order.png") 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.

http://drupal.org

``` 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. ![Input filters order changed](/sites/default/files/styles/wide_xs/public/assets/2016-04/drupal-filtered-html-input-format-order.png.webp?itok=j0-XCEI4 "drupal-filtered-html-input-format-order.png") 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) ```

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.

http://drupal.org

``` 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. ![Drupal default input format cannot be deleted](/sites/default/files/styles/wide_xs/public/assets/2016-04/drupal-input-formats-default-delete_0.png.webp?itok=TtDjdo5o "drupal-input-formats-default-delete_0.png") 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: ![google_bar.jpg](/sites/default/files/styles/wide_xs/public/u20/google_bar.jpg.webp?itok=AU9B0Vfi "google_bar.jpg") ![google_pie.jpg](/sites/default/files/styles/wide_xs/public/u20/google_pie.jpg.webp?itok=JKl1-RwI "google_pie.jpg") In Open Flash Charts, similar data looked like the following (with neat flash animation that you can't see here): ![open_flash_chart_bar_0.jpg](/sites/default/files/styles/wide_xs/public/u20/open_flash_chart_bar_0.jpg.webp?itok=MqsnaNbJ "open_flash_chart_bar_0.jpg") ![open_flash_chart_pie.jpg](/sites/default/files/styles/wide_xs/public/u20/open_flash_chart_pie.jpg.webp?itok=_f9quCtY "open_flash_chart_pie.jpg") 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: ![chart_pie.jpg](/sites/default/files/styles/wide_xs/public/u20/chart_pie.jpg.webp?itok=757QOC-M "chart_pie.jpg") ## 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: ![fusion_charts.jpg](/sites/default/files/styles/wide_xs/public/u20/fusion_charts.jpg.webp?itok=G0V9VHVi "fusion_charts.jpg") 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. ![fusion_charts_settings.jpg](/sites/default/files/styles/wide_xs/public/u20/fusion_charts_settings.jpg.webp?itok=uMJIZA9U "fusion_charts_settings.jpg") 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. ![statistics_pro.jpg](/sites/default/files/styles/wide_xs/public/u20/statistics_pro.jpg.webp?itok=r4foYyNr "statistics_pro.jpg") ## 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. [ ![The interface for Form Builder.](/sites/default/files/styles/wide_xs/public/u10/form-builder.png.webp?itok=T8_rq-In "form-builder.png")](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. ![Pantheon Control Panel-2.jpg](/sites/default/files/styles/wide_xs/public/Pantheon%20Control%20Panel-2.jpg.webp?itok=vT3Kh9nE "Pantheon Control Panel-2.jpg") 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. ![Spin Up a Site | Pantheon Control Panel-2.jpg](/sites/default/files/styles/wide_xs/public/Spin%20Up%20a%20Site%20%7C%20Pantheon%20Control%20Panel-2.jpg.webp?itok=MClH_iHA "Spin Up a Site | Pantheon Control Panel-2.jpg") 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. ![Create or Import a Site | Pantheon Control Panel-6.jpg](/sites/default/files/styles/wide_xs/public/Create%20or%20Import%20a%20Site%20%7C%20Pantheon%20Control%20Panel-6.jpg.webp?itok=kvuTVmLj "Create or Import a Site | Pantheon Control Panel-6.jpg") 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. ![Create or Import a Site | Pantheon Control Panel-10.jpg](/sites/default/files/styles/wide_xs/public/Create%20or%20Import%20a%20Site%20%7C%20Pantheon%20Control%20Panel-10.jpg.webp?itok=b0rq-PiV "Create or Import a Site | Pantheon Control Panel-10.jpg") 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. ![Midwest Developer Summit Dashboard | Pantheon Control Panel_0.jpg](/sites/default/files/styles/wide_xs/public/Midwest%20Developer%20Summit%20Dashboard%20%7C%20Pantheon%20Control%20Panel_0.jpg.webp?itok=6jbgpce4 "Midwest Developer Summit Dashboard | Pantheon Control Panel_0.jpg") 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. ![Midwest Developer Summit Dashboard | Pantheon Control Panel-1_0.jpg](/sites/default/files/styles/wide_xs/public/Midwest%20Developer%20Summit%20Dashboard%20%7C%20Pantheon%20Control%20Panel-1_0.jpg.webp?itok=kW_Bg-PU "Midwest Developer Summit Dashboard | Pantheon Control Panel-1_0.jpg") 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. ![Midwest Developer Summit Dashboard | Pantheon Control Panel-1.jpg](/sites/default/files/styles/wide_xs/public/Midwest%20Developer%20Summit%20Dashboard%20%7C%20Pantheon%20Control%20Panel-1.jpg.webp?itok=yxMXlOlg "Midwest Developer Summit Dashboard | Pantheon Control Panel-1.jpg") ## 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. [ ![BeautyTips Example 1](/sites/default/files/styles/wide_xs/public/u2/diwd-schedule-1.jpg.webp?itok=QU6UqYF8 "diwd-schedule-1.jpg")](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. [ ![BeautyTips Example 2](/sites/default/files/styles/wide_xs/public/u2/diwd-schedule-2.jpg.webp?itok=jFuqunxM "diwd-schedule-2.jpg")](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" ![Start Listen PHP Debug Connections](/sites/default/files/styles/wide_xs/public/field_regular_upload/drush_xdebug1.jpg.webp?itok=WQjvXKIZ "drush_xdebug1.jpg") 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. ![Breakpoint](/sites/default/files/styles/wide_xs/public/field_regular_upload/drush_xdebug2.jpg.webp?itok=rS9Csfpv "drush_xdebug2.jpg") 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. ![Force break at the first line when a script is outside the project](/sites/default/files/styles/wide_xs/public/field_regular_upload/drush_xdebug3.jpg.webp?itok=mlev8Cu1 "drush_xdebug3.jpg") 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. ![placing code into mamp](/sites/default/files/styles/wide_xs/public/assets/2015-09/rewrite.png.webp?itok=b4rYw1Dw "rewrite.png") 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. ![group_square.jpg](/sites/default/files/styles/wide_xs/public/group_square.jpg.webp?itok=aH_4pInO "group_square.jpg") 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. ![group_teaser.jpg](/sites/default/files/styles/wide_xs/public/group_teaser.jpg.webp?itok=a3L-Mjyu "group_teaser.jpg") 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. ![carousel.jpg](/sites/default/files/styles/wide_xs/public/carousel.jpg.webp?itok=69_4kcML "carousel.jpg") And there is yet another iteration of the Meeting 'teaser' used in views of upcoming meetings: ![meeting_teaser.jpg](/sites/default/files/styles/wide_xs/public/meeting_teaser.jpg.webp?itok=sY_2A45F "meeting_teaser.jpg") ## 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: ![ds_configuration.jpg](/sites/default/files/styles/wide_xs/public/ds_configuration.jpg.webp?itok=S0LzmZR- "ds_configuration.jpg") 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'] = '
' . t("@count members", array('@count' => count($memberships))) . '
'; // 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.') . '
'; } elseif (!empty($next_meeting)) { $vars['next_meeting_view'] = '
' . drupal_render(node_view($next_meeting, 'square')) . '
'; } else { $vars['next_meeting_view'] = '
' . 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. ![Add_field.jpg](/sites/default/files/styles/wide_xs/public/Add_field.jpg.webp?itok=s6_1ZtIH "Add_field.jpg") For each one, I create a field that has a machine name matching the name of the variable from the preprocess function above. ![preprocess_field.jpg](/sites/default/files/styles/wide_xs/public/preprocess_field.jpg.webp?itok=XZIEsH7J "preprocess_field.jpg") 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. ![display_fields.jpg](/sites/default/files/styles/wide_xs/public/display_fields.jpg.webp?itok=QT1qDi15 "display_fields.jpg") 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). ![views-pieces_0.png](/sites/default/files/styles/wide_xs/public/views-pieces_0.png.webp?itok=hb7iNhei "views-pieces_0.png") ### 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. ![views-query_0.png](/sites/default/files/styles/wide_xs/public/views-query_0.png.webp?itok=It24Dga5 "views-query_0.png") 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. ![views-output_1.png](/sites/default/files/styles/wide_xs/public/views-output_1.png.webp?itok=9WzBYIuw "views-output_1.png") ### 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. ![Bots hard at work](/sites/default/files/styles/wide_xs/public/videola_team.jpg.webp?itok=l-KnKvZZ "videola_team.jpg") 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. ![Videola video browse view](/sites/default/files/styles/max_900/public/videola_browse_screen.jpg.webp?itok=4Zc_HhfU "videola_browse_screen.jpg") 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. ![Membership subscription node](/sites/default/files/styles/max_900/public/videola_membership_product.jpg.webp?itok=QlW8aZFg "videola_membership_product.jpg") An extra configuration screen during the install process allows a custom price to be set for monthly, bi-annual, and annual subscriptions. ![Install profile configuration screen](/sites/default/files/styles/max_900/public/videola_install_configuration.jpg.webp?itok=hdce1UR3 "videola_install_configuration.jpg") ## 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 selection of available tables for adding.](/sites/default/files/styles/wide_xs/public/assets/2016-04/tw-add-tables.png.webp?itok=bBPMcE6j "tw-add-tables.png") 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 added.](/sites/default/files/styles/wide_xs/public/assets/2016-04/tw-overview.png.webp?itok=DDkyKpmU "tw-overview.png") 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 analysis screen](/sites/default/files/styles/wide_xs/public/assets/2016-04/tw-analyze-thumb.png.webp?itok=xKgOBRye "tw-analyze-thumb.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 of legacy product data](/sites/default/files/styles/wide_xs/public/assets/2016-04/tw-view.png.webp?itok=SdKvkSgj "tw-view.png") 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](/sites/default/files/styles/wide_xs/public/assets/2016-04/migrate-content-set.png.webp?itok=dqPQyS1N "migrate-content-set.png") 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 ![Field mapping](/sites/default/files/styles/wide_xs/public/assets/2016-04/migrate-field-map.png.webp?itok=ZAgS5djI "migrate-field-map.png") 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). ![Dashboard](/sites/default/files/styles/wide_xs/public/assets/2016-04/migrate-dashboard.png.webp?itok=7V4lG9a7 "migrate-dashboard.png") 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: ![Migration results](/sites/default/files/styles/wide_xs/public/assets/2016-04/migrate-import.png.webp?itok=uIuQXtaG "migrate-import.png") 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](/sites/default/files/styles/wide_xs/public/assets/2016-04/migrate-content-admin.png.webp?itok=0RTevSXq "migrate-content-admin.png") 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. ![Sample node](/sites/default/files/styles/wide_xs/public/assets/2016-04/migrate-imported-node.png.webp?itok=IdDtr8br "migrate-imported-node.png") 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. ![rest (Content) | Highlights Hub.jpg](/sites/default/files/styles/wide_xs/public/rest%20%28Content%29%20%7C%20Highlights%20Hub.jpg.webp?itok=_-5DX5pv "rest (Content) | 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. ![rest (Content) | Highlights Hub-1.jpg](/sites/default/files/styles/wide_xs/public/rest%20%28Content%29%20%7C%20Highlights%20Hub-1.jpg.webp?itok=PgtuheVy "rest (Content) | Highlights Hub-1.jpg") Give this view a path. This will be the services path for this view. ![rest (Content) | Highlights Hub-2.jpg](/sites/default/files/styles/wide_xs/public/rest%20%28Content%29%20%7C%20Highlights%20Hub-2.jpg.webp?itok=rf5S4Lg_ "rest (Content) | 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. ![Services | Highlights Hub.jpg](/sites/default/files/styles/wide_xs/public/Services%20%7C%20Highlights%20Hub.jpg.webp?itok=Kp-lgJ5_ "Services | Highlights Hub.jpg") 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: ![Mozilla Firefox.jpg](/sites/default/files/styles/wide_xs/public/Mozilla%20Firefox.jpg.webp?itok=iFYTbFHt "Mozilla Firefox.jpg") 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. ![Feeds importers | Highlights Spoke.jpg](/sites/default/files/styles/wide_xs/public/Feeds%20importers%20%7C%20Highlights%20Spoke.jpg.webp?itok=xBMlDIM0 "Feeds importers | Highlights Spoke.jpg") 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. ![Hub | Highlights Spoke.jpg](/sites/default/files/styles/wide_xs/public/Hub%20%7C%20Highlights%20Spoke.jpg.webp?itok=xNSSqj49 "Hub | Highlights Spoke.jpg") 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: ![Hub | Highlights Spoke-3.jpg](/sites/default/files/styles/wide_xs/public/Hub%20%7C%20Highlights%20Spoke-3.jpg.webp?itok=MI-A9taG "Hub | Highlights Spoke-3.jpg") 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: ![Hub | Highlights Spoke-1.jpg](/sites/default/files/styles/wide_xs/public/Hub%20%7C%20Highlights%20Spoke-1.jpg.webp?itok=BQnK0bUz "Hub | Highlights Spoke-1.jpg") ## 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. ![Hub | Highlights Spoke-5.jpg](/sites/default/files/styles/wide_xs/public/Hub%20%7C%20Highlights%20Spoke-5.jpg.webp?itok=AYYwinnX "Hub | Highlights Spoke-5.jpg") ## 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: ![Edit_ | Highlights Spoke.jpg](/sites/default/files/styles/wide_xs/public/Edit_%20%7C%20Highlights%20Spoke.jpg.webp?itok=ykzguPJ_ "Edit_ | Highlights Spoke.jpg") 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. ![Picture of the module being set up](/sites/default/files/styles/wide_xs/public/field_regular_upload/imagefield_focus_ui.png.webp?itok=lDUpRAhF "imagefield_focus_ui.png") 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. ![Picture of the module in action](/sites/default/files/styles/wide_xs/public/field_regular_upload/imagefield_focus_results.png.webp?itok=Mphw72sK "imagefield_focus_results.png") 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'] = '

©2010 All Rights Reserved
';$conf['site_footer'] .= shell_exec("git log -1 --pretty=format:'%h - %s (%ci)' --abbrev-commit");$conf['site_footer'] .= '

'; ` This code gives us the following footer: ![Site footer showing last git commit](/sites/default/files/styles/wide_xs/public/u35/git-commit.jpg.webp?itok=IEHZJyP2 "git-commit.jpg") 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'] = '

©2010 All Rights Reserved
'; $conf['site_footer'] .= shell_exec("git log -1 --pretty=format:'%h - %s (%ci)' --abbrev-commit `git merge-base local-dev dev`"); $conf['site_footer'] .= '

'; ``` What other settings.php hacks do you find useful? Share them with us in the comments below! 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: "Drupal Commerce Basics" url: "/articles/drupal-commerce-basics" type: article date: 2012-03-01 updated: 2014-05-15 --- # Drupal Commerce Basics # Drupal Commerce Basics Get started with e-commerce in Drupal 7 By [ Addison Berry ](/about/addison-berry) March 1, 2012 Drupal Commerce videos are here! People have been asking for them for a while now, and we've been working hard to get them ready. In this Drupal Commerce series, Ryan Szrama takes you through the process of creating your own Drupal e-commerce site using Drupal Commerce for Drupal 7. The series starts by getting the basics installed with the Commerce Kickstart project, and then works through working with products, taxes, discounts, checkout, and general configuration of our store. We have two of the videos available for free, so you can see what we will be covering and get into the details of how to add product displays to your store, and understand how they are different from the products themselves. Drupal Commerce relies heavily on the Views and Rules modules for many of its features, which allow you a lot of customization. If you need a refresher on these two modules, you can watch these other Drupalize.Me series: [Intro to Views for Drupal 7](https://drupalize.me/tutorial/overview-views) [Learning the Rules framework](https://drupalize.me/tutorial/introduction-rules) 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: Views Dependent Filters" url: "/articles/module-monday-views-dependent-filters" type: article date: 2012-02-20 updated: 2014-05-15 --- # Module Monday: Views Dependent Filters # Module Monday: Views Dependent Filters How to Hide/Show Exposed Filters (and Combinations of Filters) in Drupal 7 By [ Jeff Eaton ](/about/jeff-eaton) February 20, 2012 Since version 2.0 of the Views module shipped back in 2008, site builders have been able to use its *Exposed Filters* feature to create slick user-filterable lists without writing a lick of code. Unfortunately, complex views with lots of exposed filters can easily become cluttered -- some filtering options only make sense when others are also selected, for example. [Views Dependent Filters](http://drupal.org/project/views_dependent_filters) solves that problem, allowing to hide and show exposed filters based on other filters' values. ![Screenshot of dependent field configuration](/sites/default/files/styles/wide_xs/public/dependent-filter-config_0.png.webp?itok=zsv_8TDj "dependent-filter-config_0.png") Configuring the module is a bit opaque: using it requires adding a "Global Dependent Filter" to your existing view, then positioning it *between* the two exposed filters whose behaviors should be linked. For example, you might add an exposed *Content Type* filter, then the *Global Dependent Filter*, then an second exposed filter that's only applicable to one of the content types. The Dependent Filter's configuration options will allow you to choose which values from the first filter should hide or show the second exposed filter. ![Screenshot of resulting change to site](/sites/default/files/styles/wide_xs/public/dependent-filter-in-action_0.png.webp?itok=cQlKWBp2 "dependent-filter-in-action_0.png") Once the filter has been set up, [Views Dependent Filters](http://drupal.org/project/views_dependent_filters) does what it says on the tin. Exposed filters appear and disappear automatically, and your complex view gets simpler. Developers familiar with Drupal's FormAPI and the new States system will recognize what's going on under the hood: the same tricks can be done in a custom module with careful use of hook\_form\_alter(). Using [Views Dependent Filters](http://drupal.org/project/views_dependent_filters), though, means that the visibility tweaks are an inherent part of the view, and can be exported and saved cleanly. If you're looking for a way to simplify complex, user-filterable views, check it 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: "Lullabot’s Hierarchy of Qualification" url: "/articles/lullabots-hierarchy-of-qualification" type: article date: 2013-04-10 updated: 2023-10-26 --- # Lullabot’s Hierarchy of Qualification # Lullabot’s Hierarchy of Qualification How we evaluate potential projects By [ Brian Skowron ](/about/brian-skowron) April 10, 2013 My daily life, like most people’s lives, is filled with countless decisions that I rarely take the time to think about. What I’m choosing to eat for breakfast or where I decide to walk my dog are decisions that I make on the spur of the moment without much insight into the mechanics of how I arrived at them. In each of these decisions, I’m sure there are lots of little details that my brain weighs subconsciously against one another. A lot of times my motivations are clear to the outside observer, but sometimes they’re harder to piece together. I imagine it’s similar when companies start discussing a new project with development or design agencies like Lullabot. Why we do or don’t get excited about a new opportunity might be clear as day, but other times it may seem completely counter-intuitive to you as a potential client. After all, if we’re offering a service and you’re able to pay for it, then it’s time to start working together, right? Well, not exactly. That kind of purchasing dynamic works with products, and as normal everyday consumers we’ve been buying products our entire life. We’re conditioned to think that businesses offer their goods and services at a certain price, and we can partake of those goods and services if we’re simply willing to spend the money for them. Imagine if you went to buy a new television with money in hand and the sales rep told you, “Sorry, you’re just not the right fit.” But in our line of work, it’s a fact of life. Agencies like us need to be extremely selective in order to sustain our business. We almost certainly wouldn’t survive otherwise. When companies look for design and development partners, they may not realize that most of us are selling a scarce commodity—our own talent and expertise. And there’s only so much of it we can sell at any given time. What this means is by saying “yes” to your project, we’re essentially saying “no” to every other project that could possibly use the same resources. That’s a heavy decision, especially when you consider that we’re also shouldering a TON of risk for every new project we take on. The fallout from one bad project might extend way beyond dollars, to our employees, our reputation, maybe even our business entirely. So what makes a good project and how does Lullabot evaluate potential work? Well, like all decisions in life there are a lot of factors influencing our evaluation. Fortunately we examine these factors and motivations much more carefully than our breakfast decisions. ## Maslow’s Hierarchy of Needs Most of us have probably heard of [Maslow’s Hierarchy of Needs](https://en.wikipedia.org/wiki/Maslow's_hierarchy_of_needs) at some point in our life. In case you haven’t, it’s a theory of human motivation which posits that humans seek to satisfy certain basic needs (like food, water, shelter) before ascending to seek more advanced and complex needs (like friendship, self-esteem, and creativity). That’s a very simplified explanation, and I recognize that Maslow’s hierarchy has been applied to so many different things that it’s probably cliché at this point, but despite its critics, it does provide an excellent model for how we qualify a project. In our case, the categories are different, but the concept is essentially the same. There are some basic needs that are a prerequisite for having a viable project, and as you move up the hierarchy we look to satisfy other higher-level needs from the work we do and the clients we engage. I suspect most agencies, consciously or not, evaluate new projects with the same factors in mind. ![Lullabot’s Hierarchy of Needs](/sites/default/files/styles/wide_xs/public/assets/2016-02/lullabot-hierarchy_0.png.webp?itok=XM9oBmEB "lullabot-hierarchy_0.png") ## Quantitative Needs At Lullabot we tend to look at four quantitative aspects of a project - Need, Timeline, Authority, and Budget. These needs are basic across all sales deals, and when I’m talking with a potential client, these are some of the first things I am looking to identify. These are the underlying linchpins of any viable project, regardless of the attractiveness of the work. If you don’t have a quantifiable need, timeline, budget, or the authority to approve the work, there is no potential for a project to get off the ground. I view these needs as analogous to physiological needs. If you don’t have them, none of the other stuff really matters. If you’re looking to hire a design or development agency, I would recommend defining these things clearly before you start bidding out your project. Need is the real reason or “why” behind the project. What is it that’s really driving you to seek an agency? What outcomes are you hoping for, and how do you define success? When I’m looking at someone’s need, I also want to know the impact beyond the superficial details. It’s great to know that you have to launch a new design by Q3, it’s even better to know that you’re hoping to gain a foothold in a new market with this relaunch and change the perceptions of your brand amongst key demographics, and that your CEO is pegging his reputation to the success of this project. Timeline is also less superficial than it may seem. Sure, you probably have an idea of when you’re hoping to launch or complete your project, but what business factors are influencing those timeframes? What happens if those timeframes are missed, and how much time should we budget for your decision-making process, procurement procedures, and legal review. In my experience, clients routinely gloss over these things, so doing a little homework and being open about them upfront goes a long way when we’re qualifying timeline and deciding what’s realistic or what’s not. Authority is simply who amongst your team holds decision-making power. It may be one person, or it may be a group of individuals with different roles. Knowing your authority structure is more important to agencies than you might think. We know that we’re not always talking to the person or people who have the final say; and beyond just knowing who can sign off on the finances, this is really important to the success or failure of a project. If there are unknown stakeholders whose opinion is material to the outcome of the project, it can be disastrous not to have their insight and involvement early. Weeks and months of work can be lost when a critical person’s voice is only heard too late. Identifying all of the stakeholders in a project and their roles is something we’re always looking to do. Budget is unique because it can often be hard to qualify for agencies and clients alike. There’s a natural tendency for people to hide their budget for fear of losing negotiating leverage or to define their budget based solely on the quotes they get back from the agencies they talk to. This is almost always a bad idea. First off, when we at Lullabot are looking to qualify budget, it’s usually just to determine whether something is viable, and if so, how much awesome can we fit into it? I’m sure there might be some unscrupulous firms that would take advantage of a client’s budget, but for reputable agencies, knowing the budget means knowing the breadth and depth of work that can be done. Then we can have a frank and honest conversation about what we can do within that budget and what tradeoffs we should consider. And if you are worried about disclosing an exact number for fear of losing your bargaining leverage, at least provide a range so we know what ballpark we’re in. Anything helps. Oh, and if you don’t even know what your budget should be, worry instead about what your budget actually is. Ask yourself realistically what’s the most amount of money you’d possibly be willing to spend on your project, and then communicate that number. It takes an incredible amount of effort and resources for an agency just to estimate or bid on a project, and most won’t undertake that effort if they don’t know there’s a possibility of securing work. Worst case, if your budget ends up being unrealistic, a reputable agency will let you know up front, saving you a lot of time and effort. ## Risk Needs Once we’ve qualified that the quantitative needs of a project are viable, we then seek to understand how risky the project and engagement will be for us. This isn’t always a simple thing to evaluate, as risk can take many forms with a project. It could be risk that other vendors on the project won’t uphold their responsibilities, risk of undefined scope, risk of onerous contract terms, or even risk that [the entire project gets cancelled](https://www.lullabot.com/articles/mistakes-agencies-make-a-story-in-three-acts). One of the most common risks we encounter is fixed bid contracts with unknown deliverables. Like most agencies, Lullabot would much prefer contracts that pay us for time and materials instead of a fixed bid with fixed deliverables. Even when scope is extremely well-defined (a rarity), software and design estimation is still a very [delicate](https://www.lullabot.com/articles/the-art-of-estimation) [art](https://www.lullabot.com/articles/an-update-on-the-art-of-estimation). Any error in estimation can turn into a huge loss, and fixed bid contracts put that risk squarely on our shoulders. Granted, among many companies fixed bid contracts are a necessity we live with, but we’d almost always prefer time and materials if that possibility exists. In cases where we do agree to fixed bid projects, we commonly engage in a focused discovery phase beforehand in order to give us confidence in the items we’re estimating. Ultimately, however, really attractive commercial terms don’t mean a lick if the risk of a project is too great to bear. Sometimes even a time and materials contract can be too risky if there are fundamental problems like unachievable project goals or lack of access to key stakeholders. These problems won’t change if there’s more money on the table, and in Lullabot’s case, our reputation of launching successful projects is far more important than short-term financial gain. In our 7 years, we’ve walked away from plenty of otherwise attractive opportunities due to their accompanying risk. ## Personality Needs Quantitative and Risk needs essentially answer the questions of whether the project is viable, and whether we’re rolling the dice with Lullabot’s well-being; but Personality needs start to examine whether there’s a harmonious fit between our companies and stakeholders. Often this simply comes down to whether or not you’re a good human being we’d enjoy working with. Because let’s face it, if our respective teams are going to be working intensely alongside one another, possibly for months; we probably want to at least enjoy who we’re in the trenches with. At Lullabot, we take personality needs very seriously, not only when evaluating client work, but also in the way we recruit and hire our talent. And believe it or not, this actually impacts project success. If our employees and client stakeholders are not trustworthy individuals who are equally invested in the success of our relationship, they aren’t going to engage in the type of open communication that is necessary for a successful web launch. It tells us a lot when we’re presented with an RFP or requirements document from someone who doesn’t want to speak with us or have any meaningful interaction around their project. Personality fit is a huge deal, and we always look for like-minded clients wherever that possibility exists. Beyond the individual, personality needs can also apply to companies as well. Like many successful agencies, Lullabot has built a track record in specific verticals where we hold strong domain and subject-matter expertise (like Media, Technology, and Higher Education). A personality need for us is to work with companies that fit our profile and match some of this expertise. Sure, we’ll work outside of this comfort zone from time to time, but our background doesn’t necessarily make us the best fit for medical records processing firms, or commercial shipping operations, for example. There’s an unserved personality need there which could ultimately create friction in our work. And there’s another element to personality needs involving the organizational culture of the companies we work with. In general we like to work with companies that share our drive to create impact with the work they do, and who are respected leaders in their fields. We like cultures that reward innovation and creativity, and feel good when we’re helping organizations that affect positive change in the world. ## Esteem Needs Esteem is a big one, and often overlooked by people shopping their project to agencies. Put it to you this way, remember when you were looking at our client list and reading case studies to get a feel for us? Well guess what? We know that’s how we’re evaluated, and it’s really important to us that we talk about the work we’ve done. It’s how we get new business, how we differentiate ourselves, and how we solidify our identity among other agencies. Future business potential is a tremendous motivator, and esteem is definitely a part of that equation. Esteem isn’t a requirement per se, and lately it seems a lot of Lullabot projects are under tight NDAs, but publicity and reputation are huge incentives for agencies. We get excited about projects that have high visibility where we can earn some recognition for the work we’ve done. We also like to perform work that’s unique and challenging, where we can earn admiration and respect from our colleagues. It’s one thing to take on grunt work that pays the bills and keeps the lights on, it’s another thing entirely to be working on the front lines of the web inventing new techniques people will notice. It’s always easy for legal teams to strike a clause about press releases and publicity in a contract negotiation, but if you can allow your agency to talk about the work they’re going to do and use your name, your project will instantly become more attractive to them. And if you’re unsure you’ll be happy enough with the final product to warrant this publicity, provide some parameters where your agency will be able to earn the right to publicize your relationship. This can be a very compelling carrot, and a very strong negotiating chip in your favor. ## Self-Actualization I define Self-Actualization as the ultimate status agencies seek to achieve with their clients. It’s a point beyond all of the other needs before it, where we identify so strongly with a client or project we’re working on that it becomes a part of our identity at Lullabot. These are the rare circumstances where we’re so culturally and creatively aligned, that we’re uncovering new value in ourselves by working on the project together. Some of our closest clients and longest-standing relationships fall into this category, and it transcends mere deliverables on a statement of work. Self-actualization also encompasses autonomy and the latitude to extend our own boundaries in pursuit of a solution. When we can use new technologies, enrich our own learning and mastery, and grow as professionals within a project, that’s the ultimate business high. We don’t always know right away when we encounter these types of opportunities, but sometimes the possibilities of a project are clear, and they simply make us say “Wow!”. And it goes without saying, if you have a project like this, [I would love to talk](https://www.lullabot.com/contact#your-project). ## So what can you do with this knowledge? One thing to acknowledge within this hierarchy is that it’s rare for a project to hit on all of these needs at once. We recognize that certain characteristics we qualify are completely beyond your control, and that you couldn’t change them even if you wanted to. We also understand that you’re not always going to have the right budget, or time and materials contract terms, or be comfortable with a press release. As with any decision-making process, there are trade-offs and factors to weigh against one another. This isn’t an exact science, and at Lullabot we don’t explicitly quantify or score each attribute I’ve described because there’s always going to be a ‘gut factor’ involved in qualifying new business. But what you can do is look at this hierarchy as a representation of how we think and what motivates us with potential new work. I assume we’re not that different than most agencies in this regard. It can also provide you with some insight on how to change the things you do control to make your project more attractive to agencies in general. If you’re stuck with an onerous timeline, for example, you might be able to apply less risky contract terms or let your agency publicize the project. If you can’t publicize the work, maybe you can give your agency more autonomy in how they approach the project, or a more flexible timeline. Finding good talent is hard these days. Understanding how agencies like Lullabot decide which work to take and which work not to take can help you understand how to prepare your project to be more attractive, and as a side benefit, it’ll increase the chances that the project will be successful for everyone involved. 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: "An Introduction to Unit Testing in Drupal" url: "/articles/an-introduction-to-unit-testing-in-drupal" type: article date: 2007-11-26 updated: 2016-04-07 --- # An Introduction to Unit Testing in Drupal # An Introduction to Unit Testing in Drupal Programmatic tests to prove correctness of code By [ Angie Byron ](/about/angie-byron) November 26, 2007 This article applies to [Drupal 5.3](http://drupal.org/drupal-5.3), the [Simpletest module version 5.x-1.0](http://drupal.org/node/176392), and the [Simpletest project](http://simpletest.org/) from Sourceforge.net, version [simpletest\_1.0.1beta2](https://sourceforge.net/projects/simpletest/files/OldFiles/simpletest_1.0.1beta2.tar.gz/download). ## What is unit testing [Unit testing](https://en.wikipedia.org/wiki/Unit_testing) is the art and practice of taking a small portion of code, a unit, and subjecting it to programmatic tests to prove its correctness. The smallest units in PHP code are usually functions, and the unit tests will run the functions with sets of controlled parameters, and then make assertions about the return values of the function, or about the state of the application before and after the function has run. For example, if the function is designed to validate email addresses, the unit tests would pass in known valid email addresses and assert that they validate correctly. The tests would also pass in a number of invalid email addresses as well and assert that they do not validate. ## Why is unit testing useful? Writing unit tests helps produce higher quality code on many levels. - The availability of tests helps detect the introduction of bugs whenever the programmer adds new features or refactors the code. This is called regression testing. - Tests also serve as a source of documentation about what code is really expected to do. - The act of writing the tests challenges the programmer to consider possible edge cases and their consequences. - Last but not least, the act of writing tests encourages the programmer to write code in small chunks that can be tested independently. The last point is worth diving into. If we are writing code that needs to do three manipulations to an incoming string, the temptation is to write a function where the string comes in as an argument, the three manipulations are executed, and the resulting string is returned. When this function isn't working right it is hard to determine which of the three manipulations are failing. Your test case can only send a string in and compare the result with the expected result. How can you know which of the manipulations isn't working correctly? Breaking the function into smaller pieces, one for each manipulation, results in code that is easier to test. Test cases can then be written for each manipulation, better isolating the source of failure. Unit tests can be often be scripted to run automatically, making them an ideal part of your development, build, and deployment process. You can run tests before committing new code to the repository. You can run the tests after adding new modules and thus ensure that previous functionality hasn't been compromised. You can run tests after deploying changes from a development environment to a staging environment thus cutting down on the dependency of trial and error to detect problems. ## What testing tools does Drupal offer? The principal tool for unit testing in drupal is the [Simpletest module](http://drupal.org/project/simpletest). This module is a Drupal extension to the [Simpletest project](http://simpletest.org/) for general PHP unit testing. The Drupal module adds a wealth of tools and convenience for Drupal specific unit testing. For example, it has the ability to create new users and nodes, set configuration variables, and submit Drupal forms. As an addition to the Simpletest module there is the [Simpletest automation project](http://drupal.org/project/simpletestauto) which provides a system for running test suites automatically and reporting the results. The Simpletest automation is also capable of applying patches to Drupal code, and is thus an essential tool for vetting patches in the issue queue. The similarly named but very different [Simpletest automator](http://drupal.org/project/simpletest_automator) extends the convenience of writing tests even further by allowing you to click through your site as it records your actions as a macro. This macro can then be used as the basis for a unit test that you can run automatically at a later time. ## How to set up the Simpletest module To begin running unit tests on your Drupal installation, download the latest release of the Simpletest module and install it as you would any other module. Please note that you *should not install this module on a production site*. It is only designed for development and staging purposes, and running the tests *will alter the state of your database*. Running the simpletest unit tests on a production site could lead to lost data or unpredictable behavior. The Drupal module depends on the Simpletest library, which you can download from Sourceforge.net. The latest release, as of this writing, is [simpletest\_1.0.1beta2](https://sourceforge.net/projects/simpletest/files/OldFiles/simpletest_1.0.1beta2.tar.gz/download). The download from Sourceforge comes as a tarball which you need to extract into the simpletest module directory. The resulting directory structure looks like this. ![simpletest-directory-structure-graffle.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/simpletest-directory-structure-graffle.png.webp?itok=7eR7-rC2 "simpletest-directory-structure-graffle.png") That concludes the installation of the Simpletest module, and you are now ready to run the existing unit tests. ## How to run the included tests The existing unit tests can be found under `Administer->Site building->Simpletest unit testing` (admin/build/simpletest). This page is a listing of all the test suites that are installed. The bulk of them come from the Simpletest module itself, and are used to test Drupal core functionality. Since it is possible for any module to provide test suites it is entirely possible that some of the contributed modules you have installed will also have test suites. Some modules that include test cases are the [coder](http://drupal.org/project/coder), [organic groups](http://drupal.org/project/og) and [timeline](http://drupal.org/project/timeline) modules. The Simpletest unit testing page allows you to select all of the tests in any group, or, if you expand the *Tests* fieldset in any group, the single tests individually. Select the *Run selected tests* option at the bottom and click *Begin*, and Simpletest will do its work. Depending on how many tests you've chosen, the running time may be anywhere from a couple of seconds to minutes. Here I am about to run all of the tests in the "Node tests" group. ![node-tests-selected.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/node-tests-selected.png.webp?itok=0hWNoz7n "node-tests-selected.png") Here is the output generated by running all of the tests in the Node tests group. ![test-results-graffle.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/test-results-graffle.png.webp?itok=TmE4U0N1 "test-results-graffle.png") ### What to do if you get errors The beauty of having unit tests available is that it makes error reporting much easier. If you run the test suites for Drupal core or a contributed module and get *Fails*, you have a great opportunity to use the [Drupal.org issue queue](http://drupal.org/project/issues) to [file a bug report](http://drupal.org/node/add/project-issue). Paste the output from Simpletest into the issue and the module maintainer will know exactly what it is that went wrong. Be sure to include the ordinary information about your Drupal installation, including what release you are running and what modules you have installed. ## How to write a basic unit test Unit testing is a great productivity enhancer for programmers and I highly recommend using it as a core technique whenever you write code. Your code will come together quicker and will be higher quality for the effort. Adding unit test support to your Drupal module is easy. Simpletest defines `hook_simpletest` which your module implements. ```php /** * Implementation of hook_simpletest(). */ function hook_simpletest() { $module_name = 'mymodule'; // Change this to your module name. $dir = drupal_get_path('module', $module_name). '/tests'; $tests = file_scan_directory($dir, '\.test$'); return array_keys($tests); } ``` The hook returns a list of files that contain test cases. The convention is to make a `tests` directory in your module and put the test cases in there. If you follow the convention then you only need to copy the code above and change the function name and update the `$module_name` variable to be the name of your module. Now you can start to create test cases. These files should have the ending `.test` and reside in the `tests` directory in your module. The stub code for a test case looks like this: ```php class PageViewTest extends DrupalTestCase { function get_info() { return array( 'name' => t('Page node creation'), 'desc' => t('Create a page node and verify its consistency in the database.'), 'group' => t('Node Tests'), ); } function testSomething() { } function testSomethingElse() { } } ``` A test case is a class that extends the `DrupalTestCase` class. You must implement the `get_info()` method which returns a keyed array with `'name'`, `'desc'` and `'group'` strings which are used for display on the Simpletest unit test page. Beyond that, any function that starts with 'test' will be executed whenever the test case is run. The Simpletest framework and the DrupalTestCase offer a lot of handy tools for executing your tests. We'll explore a couple of these by looking at some non-trivial examples taken from the test cases found in existing modules. The first example comes from the `user_validation.test` suite from the Simpletest module. ```php // username validation function testMinLengthName() { $name = ''; $result = user_validate_name($name); $this->assertNotNull($result, 'Excessively short username'); } ``` This test creates a `$name` which is unacceptable as a user name because it is an empty string. It passes the `$name` into Drupal's [user\_validate\_name](http://api.drupal.org/api/function/user_validate_name/5) function and then uses the `assertNotNull()` method to check whether the test passes or fails. The `assertNotNull()` method must be called using the `$this` object, which is an implicit self reference in any PHP class object. The `assertNotNull()` method will check whether `$result` is `NULL`. If it is `NULL`, the test *fails*. If it is not `NULL`, it *passes*. This makes sense because the function is asserting that the object is not `NULL`, which is another way of saying "I expect this to have a value (the error message saying that the validation fails), please fail if not NULL". The `assertNotNull()` method also takes a message parameter. This message gets passed on to the Simpletest framework and is displayed on the test results page. Here is the outcome of the test listed above: ![user-validation-pass.png](/sites/default/files/styles/wide_xs/public/assets/2016-04/user-validation-pass.png.webp?itok=fNZjnGki "user-validation-pass.png") As you can see, the test passed, which means `user_validate_name()` did the expected and returned a non `NULL` value when the `$name` variable was unacceptable. If you refer to the API documentation for [user\_validate\_name](http://api.drupal.org/api/function/user_validate_name/5), you will see that it returns string values whenever validation fails. The next example comes from the tests for the [finduser module](http://drupal.org/project/finduser). The goal of the module is to search for users. In order to test this, the site needs to have users, and the test suite has to know about them, otherwise it wouldn't know what to search for or what to expect. Fortunately the `DrupalTestCase` has many Drupal-specific convenience methods that let us handle this situation. Here's the code: ```php function testSearchByEmail() { // Temporarily set the 'finduser_email' variable to TRUE. It will return // to whatever it's normal state is when the tearDown() method is called. $this->drupalVariableSet('finduser_email', TRUE); /* Prepare a user that we can search for */ $web_user = $this->drupalCreateUserRolePerm(); // Search by email $results = finduser_do_search('email', $web_user->mail); // Assert that only one result is found $this->assertEqual(count($results), 1); // Assert that it is the user we created. $this->assertEqual($web_user->uid, $results[0]->id); // Search for a bogus, non-existent user $bogus_results = finduser_do_search('email', 'xxx'. $web_user->mail); // Assert that zero results are found $this->assertEqual(count($bogus_results), 0); } ``` The first convenience method we see here is `drupalVariableSet()`. Once again, this is a method of the test case object itself, and thus must be called from the `$this` object. What the method does is inspect the current value for a Drupal variable (`finduser_email` in this case), take note of it, and then set it to a new value (`TRUE`). After the tests have run, the variable will be set to its original value, all in the background without you needing to worry about it. In this way you can temporarily change the configuration parameters of your site and not have to worry about cleaning up - the Simpletest module handles it for you. The next convenience method is `drupalCreateUserRolePerm()`. This method takes an array of permissions and uses them to create a new user account. The user account will have a generated name and email address, and will have a special user role with the permissions in the array. You can then use this user account to test the functionality of your site. Just like with `drupalVariableSet()`, the user and the roles will be deleted when the tests are finished running so you needn't worry about filling up your database with extra test users, nor do you need to go and create these users manually. Once the setup steps are all done the test goes on to invoke the actual function that is being tested: the `finduser_do_search()` function. The function is told to search for an email that is equal to the `$web_user`'s email. To determine whether the function behaves as expected, `$this->assertEqual()` is called. `assertEqual()` takes the first two parameters and asserts that they are equal. The results of `assertEqual()` will be saved for reporting on the *Simpletest unit tests* page. The test continues, however, and `$this->assertEqual()` is called again, this time asserting that the `$web_user`'s name is equal to the username that was found by doing the search. It is important to test both success cases and failure cases equally. The first two assertions tested that a user was found when expected. The third assertion in the code above asserts that no user is found when searching for a bogus, non-existent user. ```php // Search for a bogus, non-existent user $bogus_results = finduser_do_search('email', 'xxx'. $web_user->mail); // Assert that zero results are found $this->assertEqual(count($bogus_results), 0); ``` There are many more ways to do assertions, as well as many more Drupal helper functions. The [Simpletest handbook pages](http://drupal.org/simpletest) on Drupal.org are a good reference for the various tools available. ## Functionality testing - a special case The examples we have looked at so far are true unit tests because they inspect one function at a time, throwing various arguments at it and asserting that the results are correct. The Simpletest framework supports an entirely different method of testing that simulates actions done in a web browser and inspects the output that is sent to the browser, making assertions based on the output. Here is an example that creates a new user, logs into the site using the new user, submits the form to create a new *Page* node, asserts that the output that gets sent to the browser has the *"Your %post has been created"* message, and asserts that the node exists in the database. Keep in mind how many clicks it would take you to do all of those steps manually! ```php function testPageCreation() { /* Prepare settings */ $this->drupalVariableSet('node_options_page', array('status', 'promote')); /* Prepare a user to do the stuff */ $web_user = $this->drupalCreateUserRolePerm(array('edit own page content', 'create page content')); $this->drupalLoginUser($web_user); $edit = array(); $edit['title'] = '!SimpleTest test node! ' . $this->randomName(10); $edit['body'] = '!SimpleTest test body! ' . $this->randomName(32) . ' ' . $this->randomName(32); $this->drupalPostRequest('node/add/page', $edit, 'Submit'); $this->assertWantedRaw(t('Your %post has been created.', array ('%post' => 'Page')), 'Page created'); $node = node_load(array('title' => $edit['title'])); $this->assertNotNull($node, 'Node found in database. %s'); } ``` The first new method in this code is `$this->drupalLoginUser()`. Use this function to log in using any `$user` object, such as those returned by `user_load()`. This code logs in using the `$web_user` which was created with the *'edit own page content'* and *'create page content'* permissions. This code submits a form using Http POST. Note that `$edit` contains only the information that the user would be required to enter on the web form. The *title* and *body* fields were generated using the `$this->randomName()` method. *'Submit'* is the name of the button that gets clicked in order to submit the form. The `$this` object stores the HTML output so that you can make assertions against it later. ```php $edit = array(); $edit['title'] = '!SimpleTest test node! ' . $this->randomName(10); $edit['body'] = '!SimpleTest test body! ' . $this->randomName(32) . ' ' . $this->randomName(32); $this->drupalPostRequest('node/add/page', $edit, 'Submit'); ``` This code takes the HTML output that is returned after submitting the form and looks for a specific string within it. The method asserts that the string exists. ```php $this->assertWantedRaw(t('Your %post has been created.', array ('%post' => 'Page')), 'Page created'); ``` Finally, this test asserts that the node is found in the database as well. ```php $node = node_load(array('title' => $edit['title'])); $this->assertNotNull($node, 'Node found in database. %s'); ``` ## More information For more information on the [Simpletest module](http://drupal.org/project/simpletest), please refer to the [handbook pages on Drupal.org](http://drupal.org/simpletest). The source code for the [drupal\_unit\_tests.php](http://cvs.drupal.org/viewvc.py/drupal/contributions/modules/simpletest/drupal_unit_tests.php?revision=1.7&view=markup) and [drupal\_test\_case.php](http://cvs.drupal.org/viewvc.py/drupal/contributions/modules/simpletest/drupal_test_case.php?revision=1.34&view=markup) is also informative as the reference for the available assert and helper methods. The [Simpletest API documentation](http://simpletest.org/api/) is useful for learning about the underlying framework. Any of the methods available in the Simpletest base classes are also available to your test cases through class inheritance. The center of testing activity for Drupal.org is [testing.drupal.org](http://testing.drupal.org/). There are two groups on groups.drupal.org that deal with testing, the [Unit testing group](http://groups.drupal.org/unit-testing) and the [Quality assurance group](http://groups.drupal.org/quality-assurance). 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: "Squeeze! Save disk space with MySQL compression" url: "/articles/squeeze-save-disk-space-with-mysql-compression" type: article date: 2012-04-18 updated: 2021-01-12 --- # Squeeze! Save disk space with MySQL compression # Squeeze! Save disk space with MySQL compression A Quick How-to By [ Andrew Berry ](/about/andrew-berry) April 18, 2012 After a Drupal site launches and starts gathering content, the database can be expected to grow. If a site has commenting or node revisions enabled, the database can grow **very** quickly. It's not uncommon to encounter site databases that are hundreds of megabytes large, if not multiple gigabytes. Developers may need to have multiple sites set up on their local machines for quick debugging and development. With the advent of reasonably priced, but small SSDs (these days, 120GB is the sweet spot for price and size), having 20 GB of MySQL databases isn't always doable, especially when site instances are rarely used. Take a look at the largest database you have on your local machine. Odds are, most of the content is text that is easily compressed. Developers see this every time they gzip or bzip2 a database dump and a 1GB .sql file becomes 50M. With MySQL 5.5, we can take advantage of the fact that most Drupal tables compress with very high efficiency and save previous space on our hard drives. MySQL 5.5 offers [automatic compression for InnoDB tables](https://mariadb.com/docs/server/server-usage/storage-engines/innodb/innodb-row-formats) using the Barracuda table format. There are two requirements to be able to use compressed tables: 1. `innodb_file_per_table` must be enabled in my.cnf 2. The [Barracuda](https://mariadb.com/docs/server/server-usage/storage-engines/innodb/innodb-row-formats) file format must be enabled when MySQL is built. Enabling compression is a per-table setting that requires an ALTER TABLE statement like the following: `ALTER TABLE node ROW_FORMAT=compressed; ` Disabling table compression is equally as simple: `ALTER TABLE node ROW_FORMAT=dynamic; ` Note that if the table format is not Barracuda, the ALTER will still succeed. If the table is exported and imported into a Barracuda table, it will be compressed during the import. Running ALTER TABLE for each table in a database can be time consuming, especially since it would need to be run every time the database is re-imported from production. To simplify managing table compression, I've created bash script that: 1. Can compress or decompress all tables in a given database. 2. Checks to make sure that tables will actually be compressed. 3. Shows the progress of individual queries, as compression and decompression can take significant time. I'm getting in the range of 50% efficiency for larger databases where the content is mostly node revisions. Write speed for compressed tables is significantly slower than uncompressed tables, so consider leaving your most-used databases uncompressed, or only compress tables that are not related to current development. *Download (or fork!) [compress-tables.sh on gist](https://gist.github.com/2369360).* 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: "A beginner's guide to caching data" url: "/articles/a-beginners-guide-to-caching-data" type: article date: 2007-05-18 updated: 2014-05-15 --- # A beginner's guide to caching data # A beginner's guide to caching data By [ Jeff Eaton ](/about/jeff-eaton) May 18, 2007 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')) && !empty($cache->data)) { $my_data = unserialize($cache->data); } else { // Do your expensive calculations here, and populate $my_data // with the correct stuff.. cache_set('my_module_data', 'cache', serialize($my_data)); } } 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/api/5/function/cache_get), [cache\_set()](http://api.drupal.org/api/5/function/cache_set), and [cache\_clear\_all()](http://api.drupal.org/api/5/function/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, and the $cache->data element isn't empty, it unserializes the stored data and sticks it into the $my\_data variable. 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 serializes it, and save 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', 'cache', serialize($my\_data), 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. ### Advanced caching You might have noticed that cache\_set()'s second 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. 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. [File-based caching](http://drupal.org/project/fastpath_fscache), integration with the open source [memcached](http://drupal.org/project/memcache) project, and other approaches are all possible. 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. 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: "Baby Got Backend" url: "/articles/baby-got-backend" type: article date: 2011-03-23 updated: 2014-05-15 --- # Baby Got Backend # Baby Got Backend Spinning up an awesome experience for your site's content editors By [ Jeff Eaton ](/about/jeff-eaton) March 23, 2011 Drupal's flexible theming system has made [](http://mogdesign.eu/blog/70-beautiful-drupal-sites/)lots of amazing site designs possible, from the respectably contemporary [White House web site](https://www.whitehouse.gov/) to [Pete Droge's Drupal-driven Flash masterpiece](http://droge.com/flash). What most Drupal sites don't show off, however, is what content editors work with every day: the notoriously unglamorous content creation, editing, and management screens. Tools for *those* users are often neglected, even though their success is critical for any content-driven site. At this year's [DrupalCon in Chicago](http://chicago2011.drupal.org/sessions/baby-got-backend-content-administrators-are-users-too) (and last year's [Web Content Conference](http://www.webcontent2010.com/videos.html#mcgrane-eaton)), [Karen McGrane](https://karenmcgrane.com/) and I co-presented on content strategy and the importance of the backend experience for successful sites. In both presentations, we focused on techniques for discovering what pain points are most acute for a site's editors, and common patterns for solving those problems. The response we've received at both conferences has been unanimous: "Once we know what kind of tools are needed, *how* can we build them in Drupal?" The great news is that there are quite a few flexible tools to streamline your Drupal site's day-to-day content management workflow *without* writing any custom code. In this article we'll explore a couple of the easy wins, highlight modules that can be used to build out more complex tools, and give you some useful next steps for taming the backend beast. ## Making the the admin section stand out Back in the dark ages, Drupal had a dedicated administration section. It used its own layout, locked out non-administrators, and (like most web apps) it made the split between visitor-facing content and admin-facing management tools very explicit. In 2004, Drupal 4.3 abandoned that approach in favor of "in place" administration. Settings forms, content editing tools, and so on all used the same visual styling as the front end, and user permissions controlled who had access to actions like editing and deleting content. The downside, of course, is that separating the backend is sometimes a *good* thing. Highly customized sites often have themes that are badly suited for heavy duty management screens, and the visual cue that you're "behind the scenes" is useful for many site maintainers. Since Drupal 5, it's been possible to manually choose a separate theme for administration pages, but Drupal hasn't taken advantage of it out of the box. Drupal 7, released this January, brings back dedicated administration tools in a big way. It ships with a new admin theme called "Seven" that sports a clean, neutral appearance and actually makes Drupal tabs and subtabs (the achilles heel of most themes) look nice. If you're using Drupal 6, [the Seven theme is available as a separate download](http://drupal.org/project/seven). A number of other dedicated administration themes are also available if Seven isn't to your liking: [I prefer Rubik](http://drupal.org/project/rubik), a lesser-known theme that's been gaining popularity over the past year. I like Rubik's clean appearance, its slick handling of collapsed fieldsets, and the fact that it hides Drupal's often-verbose field descriptions until the appropriate one has focus. If you need more control over where the administration theme is used, check out the [Administration Theme module](http://drupal.org/project/admin_theme) for Drupal 5, 6, and 7. It lets you specify specific paths that should use the admin theme even if they're located in other sections of the site. The similar Administrative Pages module also lets you change what paths should appear in Drupal 7's "Overlay" popup window. ## Navigation for editors and administrators Most administration themes hide things like Primary Links, sidebars, and so on to make room for edit forms and content lists. That means you'll need to use another tool to handle navigation while you're in the admin area. The [Admin Menu](http://drupal.org/project/admin_menu) module has long been a favorite: it puts a tidy dropdown menu at the top of every page, giving administrators quick access to the full navigation tree, including content creation. Using Drupal's default Menu module, you can also rearrange the options. Drupal 7 includes a customizable administration toolbar as a compliment to the Seven theme: it hovers at the top of the page when browsing the site's normal content, and allows quick access to to the top-level administration links. A similar module, [Toolbar](http://drupal.org/project/toolbar), is available for Drupal 6. Although it isn't a direct port of Drupal 7's toolbar, it offers a similar look and feel. My personal favorite for both versions of Drupal is the [Admin](http://drupal.org/project/admin) module. It's a bit bulkier than Admin Menu, but its navigation panel is tucked behind an inconspicuous tab when it's not in use. Admin also allows admins to stick entire blocks into the flyout panel: the result is a customizable "dashboard" that's accessible on any page. While there are a handful of other modules that provide persistent navigation palettes and menus, [Admin](http://drupal.org/project/admin), [Admin Menu](http://drupal.org/project/admin_menu), and [Toolbar](http://drupal.org/project/toolbar) are all mature and well-maintained; pick the one you like and run with it. ## Taming the node form: The easy stuff With a fresh new look for the administration section and a consistent navigation system in place, it's time to look at Drupal's node form. It packs in a lot of functionality, but this workhorse can be overwhelming once features like revision control, extra input formats, and additional CCK fields have been added to the mix. Your first line of defense is the [Vertical Tabs](http://drupal.org/project/vertical_tabs) module. This quick-and-easy fix turns the node form's multitude of collapsible fieldsets into a single "preferences panel" with a tab for each group of fields. It's a simple fix, but it can cut down quite a bit of clutter quickly. For those using Drupal 7, it's already built in. Next up is the [Node Form Settings](http://drupal.org/project/nodeformsettings) module: it's a fantastic tool that smooths out a number of the node form's common annoyances. On a per-content-type basis, it can hide the "Revision Log" field, hide the "Input format instructions" that appear beneath every textarea, control the size of the node's Body field textarea, hide the Preview button, change the text of the Submit button, hide the confusing "Split summary" link that hovers above every body field, and more. It can also make many of the same tweaks to the comment form, and hide the Input Format fieldset beneath CCK-generated text fields. None of the changes are impressive alone, but together they eliminate a lot of visual clutter. Although it's not yet available for Drupal 7, a port is planned soon. ## Taming the node form: Advanced techniques Once those two modules have taken care of the low hanging fruit, a trickier problem remains: content types with lots of custom fields. Although CCK, Drupal's FieldAPI, and related modules give us a lot of flexibility, they add to the problem of widget overload when it comes time for a writer or editor to pound out new content. Three modules, [Field Group](http://drupal.org/project/field_group), [Auto Node Titles](http://drupal.org/project/auto_nodetitle) and [Conditional Fields](http://drupal.org/project/conditional_fields), allow us to hide fields that aren't needed, populating them automatically or revealing them when appropriate. The [Field Group](http://drupal.org/project/field_group) module allows you to cluster related fields together for each node type, even wrapping them in a collapsible fieldset that tucks them out of the way unless they're needed. Although this seems like a simple thing, properly naming and grouping input fields is one of the easiest ways to help content editors make sense of potentially confusing forms. In Drupal 7, the module also allows you to group fields differently depending on whether you're viewing or editing a node and can take advantage of the Vertical Tabs display style mentioned earlier. Field Group is included with the core CCK downloaded in Drupal 6, but is maintained as a separate project for Drupal 7. [Auto Node Titles](http://drupal.org/project/auto_nodetitle) is straightforward: for each content type, you can hide the built-in Title field on the edit form and specify a pattern-based default that Drupal should use to populate it. This can be useful when you're using several small CCK fields for values like First Name and Last Name, or Event Date and Event Location, but want the visible title of the node to be a combination of several fields. It eliminates clutter *and *makes titles more consistent.** [Conditional Fields](http://drupal.org/project/conditional_fields), meanwhile, is best used when you have large groups of CCK fields that only make sense in the presence of other selections. For example, the Street Address field on an event doesn't make sense if it's taking place online. Using Conditional Fields, you could create a Meeting Type field with several options: Conference Call, IRC Chat, or Physical Meeting. Phone Number, URL, and Street Address fields could be hidden or revealed based on the user's Meeting Type selection. This trick only makes sense when you have relatively complex content types, but it can help reduce confusion for new users quite a bit by hiding options that they don't need to worry about. Although Conditional Fields isn't yet available for Drupal 7, a similar module called [Field Conditional State](http://drupal.org/sandbox/peem/1073388) is currently under development. Keep an eye out for it as it evolves. ## Custom tools for your team Most of the modules mentioned above focus on streamlining existing edit forms inside of Drupal, but on large sites, several other problems arise. Often, different teams or individuals have different pools of content they're responsible for; simply *finding* what you need to work on can be a chore with Drupal's one-size-fits-all content administration screen. In addition, many tasks like publishing lists of approved content are painfully cumbersome with the default admin screens. Other tasks, like editing metadata for multiple articles simultaneously, are impossible. Fortunately, an old standby can come to the rescue. Using the [Views](http://drupal.org/project/views) module, it's easy to to create customized content listings, stick them onto a custom page in the administration section, and restrict access to users with specific roles. Using Views' Exposed Filters, editors and admins can also drill down on these lists to find content that matches just the criteria they care about. With a tool like [Panels](http://drupal.org/project/panels), you can combine multiple Views (as well as other widgets) into highly customized dashboards for your content team, even building separate dashboards with different features for users with different responsibilities. Here on Lullabot.com, we have a simple dashboard with just two views. One lists the latest five articles published on the site, along with statistics like how many page views and comments they've received. The other view lists unpublished articles, sorted by the date they're slated for publication. That view also lists who's responsible for finishing each article, and any revision notes that have been made during the editing process. We've used the [Login Destination](http://drupal.org/project/login_destination) module to ensure that we're taken to the dashboard as soon as we log in. On one screen, we can see what's live, what's coming up, and what steps need to be taken to finish any articles that are in the queue. If you're looking for a solution that's a bit lighter than Panels, or you need to give individual users the ability to customize their own dashboards, check out [HomeBox](http://drupal.org/project/homebox) module. It's used on Drupal.org itself, and allows users to add any blocks to their own dashboards with a drag-and-drop interface. Finally, there's the venerable [Views Bulk Operations](http://drupal.org/project/views_bulk_operations) module. It allows you to turn any view into an administration tool that actually *makes changes* to content en masse. The available actions included publishing and deleting content, changing field values, changing authors, and more: other modules can expose additional actions as well. The available actions can be changed for each View, and dangerous actions can be hidden from users with insufficient permissions. It's an amazing Swiss Army Knife that can be used to build a wide variety of flexible, task-focused content tools. ## A world of possibilities... Amazingly enough, we've only scratched the surface of what can be done to streamline the content management experience. In future articles I'd like to look at how modules like [Flag](http://drupal.org/project/flag) and [Prepopulate](http://drupal.org/project/prepopulate) can be used to smooth out complicated workflows; the use of [Features](http://drupal.org/project/features) to capture these administrative features and share them between sites; and upcoming projects like [Workbench](http://drupal.org/project/workbench) that capture some of the most common solutions in a single package. As Karen and I mentioned in our [DrupalCon session](https://www.slideshare.net/slideshow/baby-got-backend-content-administrators-are-users-too/7261659), the real challenge is *figuring out what your site's content editors want and need.* These modules can eliminate most of the coding, but nothing can replace talking to the people who'll be using the site every day. What kinds of solutions have *you* found work best for the sites you've built? 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 vpnc as a Command Line VPN Client" url: "/articles/using-vpnc-as-a-command-line-vpn-client" type: article date: 2011-04-28 updated: 2021-01-12 --- # Using vpnc as a Command Line VPN Client # Using vpnc as a Command Line VPN Client In many enterprises, Cisco VPNs are used to give remote developers controlled access to production web servers. By [ Andrew Berry ](/about/andrew-berry) April 28, 2011 In many enterprises, Cisco VPNs are used to give remote developers controlled access to production web servers. This allows machines in far-flung locations to operate as if they're on the same controlled network, making security and management much easier for the network administrators. For the remote developers, on the other hand, things aren't always so smooth. VPN clients are built into most desktop operating systems, making manual connections relatively simple. However, the GUI software for connecting to a VPN can sometimes be buggy or difficult to use. In addition, it's often useful to connect remote servers to a VPN; since they rarely have a GUI installed, the familiar VPN connection tools are missing. The command-line VPN client [vpnc](https://www.unix-ag.uni-kl.de/~massar/vpnc/) is a great solution to both problems. With it, you can quickly and easily establish a VPN connection, bypassing the GUI entirely. ## Installing vpnc First, we need to install the vpnc client using the package manager for our operating system. Here are a few examples: ### Red Hat / CentOS `# yum install vpnc ` ### Debian / Ubuntu *For Ubuntu you should prefix the command with "sudo" to execute it as root.* `# apt-get install vpnc ` ### OS X with MacPorts `sudo port install vpnc +hybrid_cert ` ## Configuring vpnc Once vpnc is installed, we need to create a configuration file for each VPN we'll be connecting to. Start by copying `/etc/vpnc/default.conf` (`/opt/local/etc/vpnc/default.conf` if vpnc was installed with MacPorts) to a file for your specific VPN, such as "lullavpn.conf". Edit the new file in the text editor of your choice. The default configuration file should contain something like this: `IPSec gateway IPSec ID IPSec secret IKE Authmode hybrid Xauth username Xauth password ` Fill in each line of information as needed for your VPN. If you want to be asked for your password each time you connect, comment out or remove the "Xauth password" line. Here's an example of what a vpnc configuration might look like after being set up: `IPSec gateway 123.234.123 IPSec ID lullavpn IPSec secret my-super-secret-shared-key # This VPN uses just a pre-shared key and no certificate, so set IKE # Authmode to "psk". # IKE Authmode hybrid IKE Authmode psk Xauth username robotic.coder Xauth password my-really-super-secret-password # By specifying local port as 0, we use a random source port for each # VPN connection, allowing multiple VPNs to be run at once. local port 0 ` As this file contains your credentials in cleartext, it's important to make sure that it's only readable by the root user. Use `chown root *.conf` to set the configuration files to all be owned by root and `chmod go-rw *.conf` to remove permissions from group and other. You can verify the file permissions by running `ls -la *.conf`. ## Connecting and Disconnecting a VPN Connecting a specific VPN is a single command away. Note that you do not have to be in the /etc/vpnc directory to run this command: `# vpnc lullavpn.conf ` To disconnect the VPN client: `# vpnc-disconnect ` Remember on OS X or Ubuntu to use `sudo` to run vpnc as root. If you have multiple VPN clients running, the disconnect command will *only* affect the most recently established connection. To kill all currently active vpnc connections, use the command: `killall vpnc`. VPNs can be an annoyance in day-to-day work, but they're a fact of life when managing complex hosting environments and working with most corporate networks. vpnc's simplicity and flexibility can help smooth out (or automate away) some of the rough edges; with it, working with servers on a VPN can even be a pleasure! 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 Web Project Together" url: "/articles/building-a-web-project-together" type: article date: 2011-07-07 updated: 2023-10-26 --- # Building a Web Project Together # Building a Web Project Together Why web projects work better when clients and vendors build together, the necessary ingredients for a successful collaborative project, and how to spot problems before they explode. By [ Rachel Scott ](/about/rachel-scott) July 7, 2011 It was the spinning tupperware drawer that got me. I was in a friend's recently built condo, and he was showing me the ins-and-outs of the build, when we came across *the drawer*. It fascinated me because it was obviously custom-built to meet my friend's obsessive-compulsive tupperware organization needs. "Was this drawer design in the blueprint?" I asked. "No," he said. "I just happened to come in when the builders were working, told them what I wanted, and they built it!" I was shocked; I had always thought you gave the builders a blueprint, told them the colors and textures you wanted, and they built it. At that point, I'd thought, you pretty much get what you get. In the web world, this is how projects often go. The client needs a website, and the client hires a vendor to help. A blueprint and design is created, the vendor builds the website, the client is trained, and from then on they're responsible for maintaining and improving themselves. This may be the ideal scenario for some projects, but it can be extremely risky when working on complex project with many unknowns, when using open-source software, or on agile projects that require frequent adjustments. I find that enterprise projects are most successful when both the client and vendor are equally engaged during the development process. Ideally, the client has a committed development team for the project, as if it was an internal project, and the vendor team acts as an extension to the client team. ## Why build a site together? ### Setting the client up for success A successful launch isn't the only thing that makes a project successful; a client's comfort in maintaining the site *after* launch is also critical. Close collaboration between the client and vendor development teams ensures the client's team knows the system when the launch arrives; they've already been working close with it, and ideally no special "training period" is needed! This approach is a huge plus for vendors, too. The earlier the client starts owning the project and the decisions made, the less chance there is of last-minute finger pointing! ### Requirements & Feedback Loop On the two most recent enterprise projects that I have worked on, the client's biggest complaint about past vendors was that they failed to fully understand the requirements. This [classic cartoon](https://www.bizbash.com/welcome-meetingsnet-readers) demonstrates the problem well. The client has an idea, perhaps notes captured on paper — heck, maybe even a working prototype! Their vision may not be clear, though; when the time comes to implement the full product there are varying perspectives, and the end result can fail to meet the client's needs. Sometimes, avoiding this fate is just a matter of the client being more closely involved in sprint reviews or progress meetings. However, I have found that the more the client's team understands *what* is being built and *how* it is being built, the better feedback the client can give along the way to achieve their vision. ### Decision Making/Conflicts Clients that are closely involved in the building process are better able to make informed decisions. This can prevent "blind decisions," moments when the client agrees to something without fully grasping the impact or the outcome. In addition, when conflicts arise, both the vendor and client are speaking the same language and can communicate problems and solutions more effectively. All of these build trust around estimates and performance, too; as they collaborate, clients become more aware of the things that are easy to implement and the ones that require research time or custom code. The success of the project becomes a shared responsibility, and both teams are in it together rather than tossing requirements and bug reports over an artificial wall. ### Insight on both sides Both the vendor and client can offer valuable perspectives throughout the project. The vendor team has the open-source experience working with the development framework, the experience of working with different companies, and a grasp of the most likely challenges that will be faced. The client team has the product expertise, and the knowledge of company history—as well as a deep understanding of the team members' personalities, the politics, and real stakeholder needs. Because of the varying viewpoints, both teams can spark ideas and help each other find solutions to inevitable challenges. ## Ingredients for successful collaboration ### The right team I have found that there are four crucial roles when both vendor and client are building a project together. 1. Developers: It's important that both teams complement each other. Sometimes, that means having matching roles on each side (1-1 database admin, etc.) while other projects may benefit from dividing different types of tasks between vendor and client teams. Either way, ensure that clear communication is maintained. 2. Architect: This is usually a senior developer on the vendor's side responsible for maintaining a bird's eye view of the system that is being built. They need the technical knowledge of both the tools being used and the system being built to answer the difficult questions that inevitably arise: they become the go-to person for analysis of changes or additions and how they'll affect the system. 3. Project managers: On both the client and vendor sides, project managers function as communication liaisons for the rest of the team. This is extremely important, as the two teams can easily drift into their own "tribes!" 4. Client stakeholders: These may be the most important people to keep closely involved. They don't need to know all of the day-to-day nitty-gritty details, but they'll need to ensure the team is building a system that meets their needs. It's essential to build several checkpoints into the project timeline, points where these stakeholders can review the system and ensure that they understand what the team is building, and that the team is heading for the right destination. ### Training In order for the client's development team to be involved, up-front training—not only for the framework being used, but vendor processes, tools, etc.—is usually necessary. It's important to establish these things early on so that the project can begin smoothly. Developers often get immediate attention when it's time to train, but anyone who'll be making technical decisions for the project will benefit from the same information. ### Confidence & trust Building trust helps to keep the teams engaged on the project and involved in the decisions made. I find trust is built when the vendor team clearly communicates what is being built, how it's being implemented, and why decisions are being made throughout the project. Vendors who do this are sharing best practices while they develop, and ensure that an eventual hand-off to the client's team will be smooth. This is another great way to build trust is through in-person collaboration. On one of our recent projects, the developers used large white-boards at the client's offices to decide on the various groupings of Drupal features we would create. The client's team participated in the brainstorming, and was immediately engaged in the analysis and decisions that resulted. ### Stick to the plan Once the client's development team becomes more familiar with the website framework, and how to best utilize it, they may want to start changing/adding things to the plan. It's important to stick to the vision the stakeholders signed off on, while communicating that gold-plating iterations can happen *after* the project is finished. This doesn't mean the spinning tupperware drawer cannot go in, but it needs to be evaluated in the overall plan. ### Project check-ins Take time to look at the project together as a whole and do frequent retrospectives to see how the teams can improve the working relationship. Evaluate things such as communication processes, ticketing, documentation, project schedule/flow, and resources. On a recent project, we did an evaluation midway through the project and realized we had too many people involved on the daily scrum calls. We limited the calls to our internal team members and transitioned to weekly calls with the client team. ## Red flags to watch for While collaboration between two teams can bring many benefits, there are times when the process stumbles and needs recalibration. Over many projects, I've learned to recognize some warning signs that should trigger a heart-to-heart with the client. *To our clients: don't worry! All clients appearing in this work are fictitious. Any resemblance to real clients, living or dead, is purely coincidental!* ### Lack of resources for the client team If the client doesn't have the team to support the project or the capacity to spend time on it, collaboration simply won't work. They may seem to have the perfect team structure, but if they're spread too thin on other projects and "fire fighting," the vendor's team will spend their time waiting for input or picking up the slack. Sometimes, a disengaged client team is a warning sign that the client isn't fully committed to the project. It may make more sense to move to a more traditional approach, with the vendor's development team implementing a spec outlined by the client. ### No point person On the client side, it's a good idea to have a point person who already has trust in the vendor, is familiar with the development framework, and has clear goals for the project that is being built. Without this point person in place, the project may be compromised simply because there is not a "cheerleader" encouraging the client team to work effectively with the vendor. Having a point person also limits the possibility of an "us vs. them" attitude evolving when conflicts arise. ### Limited timeline Ramping up a client's team in preparation for actual development takes time, and getting two different teams to work effectively together doesn't happen overnight. Evaluate the client's willingness to invest up-front time in training, coordination, and planning to ensure that the project goes smoothly. If the timeline is too tight, a traditional "build to the blueprints" approach may be a safer bet. ### Back-channel bribery Although it sounds obvious, one of the biggest red flags is a client that attempts to short-circuit the collaboration process by convincing individual developers to sneak in changes or feature additions. Although it's always tempting to bypass processes when changes need to be made, the divide-and-conquer approach can undermine the effectiveness of the group decision making process, and threaten the entire project if allowed to spiral out of control. This can easily spark political battles, siphoning energy from the important work of completing the project itself. ## Conclusion In the end, both clients and vendors want to build a website that meets the client's business needs. The better the vendor understands those needs and the client's vision, the better the outcome. I think the most important benefit of building a site together is the relationship that is created; in a healthy collaboration, the vendor is viewed as a genuine partner, rather than an outsider with different priorities. From my perspective on the "vendor" side of the fence, it's also a lot of fun working with a client's team! There are few things as rewarding as working together to build an awesome project. 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: "Effortless Inline Thumbnails: Image Resize Filter" url: "/articles/effortless-inline-thumbnails-image-resize-filter" type: article date: 2009-02-01 updated: 2014-05-15 --- # Effortless Inline Thumbnails: Image Resize Filter # Effortless Inline Thumbnails: Image Resize Filter By [ Nate Lampton ](/about/nate-lampton) February 1, 2009 There are a lot of approaches for resizing images (ImageCache, Image, iMCE, and many others). However when building a site for a simple user (no previous web experience), I found that *all* of these approaches required too much effort on the part of the end user. Why can't a user just resize an image in their WYSIWYG, and not worry about the size of the image at all? That's the goal accomplished by the Image Resize Filter. Despite its extremely techie-sounding name, it's ridiculously easy to use. It provides inline resizing of images to match any `` tag in any HTML inserted in any Drupal textarea that supports filtering. Image Resize Filter Project Page: http://drupal.org/project/image\_resize\_filter 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: "Drupocalypse Now (or, dangerous integer handling in drupal_write_record)" url: "/articles/drupocalypse-now-or-dangerous-integer-handling-in-drupal_write_record" type: article date: 2009-06-22 updated: 2014-05-15 --- # Drupocalypse Now (or, dangerous integer handling in drupal_write_record) # Drupocalypse Now (or, dangerous integer handling in drupal\_write\_record) By [ Jeff Eaton ](/about/jeff-eaton) June 22, 2009 A couple of weeks ago, Twitter started circulating news about the upcoming *Twitpocalypse.* The easy 'default' storage format numbers in many programming languages and databases is the 'signed integer.' It's usually capable of representing values from -2,147,483,647 to +2,147,483,647. As fate would have it, the number of Twitter messages in existence was nearing that limit, and any developers who'd built software that stored tweets would encounter errors unless they started using larger number formats to store Twitter IDs. Drupal's Twitter module (which James Walker and I co-maintain) had that problem: it archived Twitter statuses in the database, and it saved the Twitter IDs as signed integers. We released an update several weeks ago that changed the database column to an "unsigned bigint," capable of holding numbers as high as 18,446,744,073,709,551,615. Disaster averted! ### Not Quite When the big day arrived and Twitter Status ID 2,147,483,647 was finally posted, we started getting sporadic bug reports from users despite the fix we'd put in place. Even Sony Music, one of Lullabot's Twitter-using clients, got reports from their artists. Chris Daughtry's tweets weren't updating on his web site, and social media starvation was starting to set in. Time for some debugging! [After soliciting some feedback from affected users](http://drupal.org/node/491794), it became clear that Twitter module was only affected on 32-bit servers, or where PHP was running in 32-bit mode. On those machines, somewhere between retrieving the value from Twitter's API and saving it to the bigint column in the database, it was being squished back into a signed int and corrupted. Setting up on another server, I was able to reproduce the problem and track it down to its source -- Drupal core. ### The Terrible Truth Turns out, PHP *is* smart enough to handle those large numbers without immediately choking. If you pull in a large value from Twitter's API, for example, it will treat it as a double (not as large as MySQL's 'bigint' data type, but certainly better than a normal int) without any special instructions. And if you build a SQL query to insert that value into the database, everything works fine. The trouble comes when those numbers are passed through Drupal's [\_db\_query\_callback()](http://api.drupal.org/api/function/db_query) function. That function uses placeholders like %d and %s to sanitize numbers and strings, preventing SQL injection attacks. Unfortunately, the commonly used %d placeholder represents *an integer*. On 32-bit hardware, PHP is only able to handle big integers by treating them as doubles -- but passing them through any filtering methods with a %d placeholder converts them back to integers, overflowing the maximum size and discarding the "real" original value. The easiest way around this if you're building your own queries and calling db\_query() is to use the undocumented *%n* placeholder when dealing with large numbers -- it just ensures that a value is a number, but doesn't change its fundamental value type. ### Oh, drupal\_write\_record()... In Drupal 6, two great pieces of code were added to the project's bag of tricks. First, SchemaAPI. It allowed developers to defined their module's database tables using descriptive arrays, defining columns and indexes in a database-agnostic format. While SQL is pretty clean when it comes to SELECT, INSERT, and UPDATE syntax, table creation and support for various data types is all over the place -- inconsistent from one database system to another. SchemaAPI lets people say, "This column should be an integer, and it should be a BIG UNSIGNED one, at that" -- then generates the appropriate SQL to create the column for each database system. Now, if you're listening to that and thinking it sounds suspiciously close to the integer-handling issue we've been talking about, you're right. SchemaAPI collides messily with the issue when the drupal\_write\_record() function is used. That function takes any PHP object or array, along with the name of the table it should be saved to, and uses SchemaAPI information to build a safe, sanitized insert or update query for the object. It's extremely useful, and it's used all over the place in Drupal core and contrib. The node module, the user module, and the taxonomy module all save and update their data using the function. Unfortunately, SchemaAPI treats 'big unsigned integers' as just plain 'integers' when it comes time to construct one of those insert or update queries. It assumes that a %d placeholder would be appropriate, and ... voila. Numbers larger than the plain-old-integer size limit are lost, even if PHP is smart enough to deal with them and the database columns is large enough to hold them. This is the situation that Twitter module ran into. It's the situation that your entire Drupal installation could run into, too, *if* you ever hit more than a few billion node revisions and you're running on 32 bit hardware. ### What's the solution? If you're writing your own queries by hand and passing them through db\_query(), the easiest workaround is to use the %n placeholder instead of %d when manipulating big or unsigned integers. If you're using db\_write\_record(), it's more complicated. SchemaAPI will always try to use %d for integers, regardless of their real size: there's *no safe way* to insert or update bigints or unsigned ints as long as that is the case. There is [an issue in the Drupal bug queue highlighting the problem](http://drupal.org/node/333788), but cross-database compatibility are making things tricky. For the time being, the best option when using drupal\_write\_record() is to change the database schema itself. Instead of the SchemaAPI data type 'integer' with a size of 'big', use a 'numeric' column with a precision of 20 and a scale of 0. It's not as efficient for storage as a normal bigint (Versions of MySQL older than 5.0.2 will actually store the numbers as *strings*, for example), but it will be able to store the values accurately. More important, SchemaAPI will automatically use the %n placeholder instead of %d, allowing the large numbers to pass through without problems. 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: "MySQL Backups Using LVM Snapshots" url: "/articles/mysql-backups-using-lvm-snapshots" type: article date: 2012-09-13 updated: 2014-05-15 --- # MySQL Backups Using LVM Snapshots # MySQL Backups Using LVM Snapshots How to Backup Large Databases with Minimal Locking By [ Ben Chavet ](/about/ben-chavet) September 13, 2012 ## Overview The most straightforward method of performing a backup on a MySQL database is with the [mysqldump](https://www.lullabot.com/articles/importexport-large-mysql-databases) utility. This is a great tool with many advanced features! It is not without its pitfalls, though. The default behavior of mysqldump is to lock all of the tables in a database while it is performing the dump. This means that the data cannot be changed until the backup has finished, which is great for data integrity, but not so great for an active database that is frequently updated. Any updates need to sit and wait for the lock to be lifted, which is especially problematic for the Drupal sessions table, for example. This default behavior can be modified using --skip-lock-tables, which is fine in some cases, but generally speaking it is best to have that point-in-time state of the database preserved in a backup. Also, if every table in every database being backed up is innoDB, then --single-transaction (combined with --quick for large tables) can provide this point-in-time, but even this does not protect against certain statements like ALTER TABLE, CREATE TABLE, DROP TABLE, RENAME TABLE, or TRUNCATE TABLE. ## Enter: Logical Volume Manager (LVM) The Logical Volume Manager (LVM) is a block device subsystem provided by Linux that sits between the filesystem (ext3/4, xfs, etc.) and the physical disks (/dev/sda, /dev/sdb, etc.). It acts as a translation layer that allows some very advanced operations, such as the snapshot feature covered here, and is completely transparent to the filesystem. Configuring LVM is outside the scope of this article, but most mainstream Linux distributions provide it as an option during installation, and many even use it by default. One thing to note, though, is that it is important to reserve some of the physical drive space as unallocated. The unallocated space is what allows snapshots to be created. Generally speaking, a good practice is to allocate the amount of space currently required plus 10% for growth. Unlike traditional disk partitions, expanding an LVM volume is quick, easy, and does not require any downtime, so there is no danger to underestimating at this stage. Now, for the bad news. Many cloud providers do not support LVM. But, for those servers that can use LVM, the snapshot feature can be used to get a point-in-time backup of MySQL with minimal table locking. ## Discovery Phase Before proceeding, some preliminary information is needed. 1. Where are the MySQL data files stored? `~# mysqladmin variables | grep datadir

| datadir | /var/lib/mysql/ | ` This shows that the data files are located at **/var/lib/mysql**. 2. Which logical volume hosts this location? `~# df /var/lib/mysql

Filesystem 1K-blocks Used Available Use% Mounted on

/dev/mapper/vg0-var 209698268 45532856 164165412 22% /var ` This shows that the volume group is **vg0**, and the logical volume name is **var**. So, the full block device path that the LVM tools will understand is **/dev/vg0/var**. This can be confirmed with the following `~# lvscan

ACTIVE '/dev/vg0/var' [200.00 GB] inherit` 3. How much unallocated space is available for the snapshot in the vg0 volume group? `~# vgdisplay vg0 | grep Free

Free PE / Size 9551 / 37.31 GB ` This shows that there are **37.31GB** of free space on the vg0 volume group. This is important to note, because this free space is where changes to the live database are tracked while the snapshot is present. ## Create an LVM Snapshot 1. Connect to MySQL, flush the tables to disk, and lock them. Do not do this with mysqladmin, and be sure to leave the database session open. As soon as a client (such as mysqladmin) disconnects, this lock is lifted. In order to guarantee data integrity, the database must remained locked until the LVM snapshot is created. The amount of time that this operation takes will vary based on how much data needs to be flushed to disk, but it is generally very quick. `FLUSH TABLES WITH READ LOCK

` 2. In another terminal session, create the LVM snapshot. This snapshot needs to be large enough to accommodate the changes that will be made to the database while the snapshot is present. Because this snapshot will be short lived, the shortcut "100%FREE" can be used, which will use all 37.31GB of unallocated space in this case. This process is nearly instantaneous because LVM uses a copy-on-write (COW) snapshot method. `lvcreate -l100%FREE -s -n mysql-backup /dev/vg0/var

` 3. Back at the original MySQL session, release the read lock so that normal database operation can resume. `UNLOCK TABLES

` At this point, there is a consistent, point-in-time snapshot of the MySQL file structure stored in the LVM snapshot. The database can now go on with its business, and the only locking required was to flush any data from memory to disk. ## Snapshot Magic Using lvscan again, the new snapshot can be seen. `~# lvscan

ACTIVE Original '/dev/vg0/var' [200.00 GB] inherit

ACTIVE Snapshot '/dev/vg0/mysql-backup' [37.31 GB] inherit ` The snapshot can now be mounted at an arbitrary location (If /dev/vg0/var is an XFS volume, add "-o nouuid -t xfs" to the mount command). `mkdir -p /mnt/snapshot

mount /dev/vg0/mysql-backup /mnt/snapshot ` From here, any standard filesystem backup method can be used to store a copy of /var/lib/mysql as mounted under /mnt/snapshot. This method could range from [rsync/ssh](https://www.lullabot.com/articles/simple-offsite-backups-with-rsync-ssh-and-sudo), to a simple tarball, to some enterprise backup solution. ## Portability In order to *safely* restore a filesystem level MySQL backup, it would need to be restored to a MySQL server with the same major and minor versions (5.0, vs 5.1, etc). While this is may not be a problem when using stock packages from a distribution, a traditional mysqldump backup is much more portable. However, as powerful as mysqldump is, it cannot create a dump file from a set of raw MySQL files, it must pull the data from an active MySQL server. The solution is to start a second instance of MySQL using the data files from the snapshot mounted at /mnt/snapshot. There are three things to keep in mind for this second instance 1. The TCP port must be **different** than the primary MySQL instance, which can be found with `~# mysqladmin variables | grep port

| innodb_support_xa | ON |

| large_files_support | ON |

| port | 3306 |` 2. The MySQL socket must be **different** than the primary MySQL instance, which can be found with ``` ~# mysqladmin variables | grep socket | socket | /var/run/mysqld/mysqld.sock | ``` 3. The --innodb-log-file-size must be **identical** to the primary MySQL instance, which can be found with `~# mysqladmin variables | grep innodb_log_file_size

| innodb_log_file_size | 268435456` Taking these values into consideration, start the second MySQL instance `mysqld_safe --no-defaults --port=3307 --socket=/var/run/mysqld/mysqld-snapshot.sock --datadir=/mnt/snapshot/lib/mysql --innodb-log-file-size=268435456 &

` Now, a full mysqldump can be performed against this second MySQL instance, which can lock all it wants without affecting the primary instance. In order to ensure that mysqldump is using the second instance, specify the MySQL socket file. `mysqldump -S /var/run/mysqld/mysql-snapshot.sock | gzip > /path/to/mysql/backup.sql.gz

` ## Cleanup That's it! There is now a portable, consistent mysqldump file at /path/to/mysql/backup.sql.gz, and the only locking required was to flush the data to disk. All that is left is to clean up. 1. Stop the second MySQL instance with mysqladmin `mysqladmin -S /var/run/mysqld/mysqld-snapshot.sock shutdown

` 2. Unmount the snapshot volume `umount /mnt/snapshot

` 3. Delete the LVM snapshot `lvremove /dev/vg0/mysql-backup

` ## More Information There were some advanced topics touched on here, but were not covered in detail. More information about these topics can be found at the following locations. - [https://en.wikipedia.org/wiki/Logical\_Volume\_Manager\_(Linux)](https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux)) - http://tldp.org/HOWTO/LVM-HOWTO/ - http://en.wikipedia.org/wiki/Copy-on-write - http://en.wikipedia.org/wiki/Device\_mapper - http://dev.mysql.com/doc/refman/5.1/en/mysqldump.html Published in: - [ Drupal Development ](/topics/drupal-development) - [ 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: "Monitor Java with JMX" url: "/articles/monitor-java-with-jmx" type: article date: 2012-08-02 updated: 2014-05-15 --- # Monitor Java with JMX # Monitor Java with JMX By [ Ben Chavet ](/about/ben-chavet) August 2, 2012 JMX is a great way to monitor and manage any java application. Information about things like heap memory usage, garbage collection rate, and any custom data that the application supports can be retrieved. It is easy to enable and can be secured for remote access. ## Enable JMX By default, JMX is not enabled because it does introduce a security risk if not configured correctly. In order to enable it, a few flags need to be added to the command-line string that starts the application. This location varies by application as well as by platform and installation method. A typical tomcat installation has a $JAVA\_OPTS variable that can be set in the supplied catalina.sh for things like this, which will be used here for demonstration purposes. To enable JMX with no security options, use the following. Port 1616 is specified here, but there is no "standard" JMX port, so any unused port can be used. `JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1616"

JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" ` Restart tomcat, and that's it! JMX is now ready to be queried. Fire up jconsole to see the statistics `jconsole localhost:1616

` ## Security The configuration above is really only safe for use in a secured network environment with no access to the JMX port from the outside. Anyone that has access to the open port has full access to the JMX subsystem listening there, and a simple port scan is all it takes to find it. To combat this, JMX provides security by means of SSL encryption as well as authentication via username/password or a client-side SSL certificate. Using a client-side SSL certificate is generally viewed as the more secure option, so that is what is demonstrated here. Two java keystores and truststore pairs are required in order to use JMX over SSL with client-side certificate authentication. One pair to allow the client to trust the application, and the other to allow the application to trust the client. A keystore holds a private key and public certificate that identifies one side of the connection. A truststore holds a list of trusted public certificates which allows communication with whatever service has the matching private keys. First, create the keystore and truststore for the application, tomcat in this case. `keytool -genkey -alias tomcat -keyalg RSA -validity 365 -keystore tomcat.keystore -storepass password -keypass password -dname "CN=Ben Chavet, OU=DevOps, O=Lullabot, L=Des Moines, S=IA, C=US"

keytool -genkey -alias tomcat -keyalg RSA -validity 365 -keystore tomcat.truststore -storepass trustword -keypass trustword -dname "CN=Ben Chavet, OU=DevOps, O=Lullabot, L=Des Moines, S=IA, C=US" ` And do the same for the JMX client, jconsole in this case. `keytool -genkey -alias jconsole -keyalg RSA -validity 365 -keystore jconsole.keystore -storepass password -keypass password -dname "CN=Ben Chavet, OU=DevOps, O=Lullabot, L=Des Moines, S=IA, C=US"

keytool -genkey -alias jconsole -keyalg RSA -validity 365 -keystore jconsole.truststore -storepass trustword -keypass trustword -dname "CN=Ben Chavet, OU=DevOps, O=Lullabot, L=Des Moines, S=IA, C=US" ` Then, export the public certificates from the keystores `keytool -export -alias tomcat -keystore tomcat.keystore -file tomcat.cer -storepass password

keytool -export -alias jconsole -keystore jconsole.keystore -file jconsole.cer -storepass password ` Finally, import the certificates into the truststores. Again, this allows the application (tomcat) to trust the client (jconsole), and vice-versa. `keytool -import -alias jconsole -file jconsole.cer -keystore tomcat.truststore -storepass trustword -noprompt

keytool -import -alias tomcat -file tomcat.cer -keystore jconsole.truststore -storepass trustword -noprompt ` Now, copy the tomcat keystore and truststore to the tomcat server, and modify the startup options to use it `JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1616"

JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=true"

JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.keyStore=/path/to/tomcat.keystore -Djavax.net.ssl.keyStorePassword=password"

JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStore=/path/to/tomcat.truststore -Djavax.net.ssl.trustStorePassword=trustword"

JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.ssl.need.client.auth=true" ` Restart tomcat for these options to take effect, and then connect with jconsole using `jconsole -J-Djavax.net.ssl.keyStore=/path/to/jconsole.keystore -J-Djavax.net.ssl.keyStorePassword=password -J-Djavax.net.ssl.trustStore=/path/to/jconsole.truststore -J-Djavax.net.ssl.trustStorePassword=trustword tomcathost:1616

` The connection between the JMX client and the application is now encrypted, securely authenticated, and safe to use on any network, including the Internet. ## References - http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#CreateKeystore - http://docs.oracle.com/javase/1.5.0/docs/guide/management/agent.html#SSL\_enabled Published in: - [ Drupal Development ](/topics/drupal-development) - [ 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: "Mistakes Agencies Make: A Story in Three Acts" url: "/articles/mistakes-agencies-make-a-story-in-three-acts" type: article date: 2012-12-20 updated: 2018-06-11 --- # Mistakes Agencies Make: A Story in Three Acts # Mistakes Agencies Make: A Story in Three Acts It's easy for web agencies to fall into these classic traps: learning to recognize and sidestep them is critical for long-term success. By [ Seth Brown ](/about/seth-brown) December 20, 2012 There's a Niels Bohr quote that I love: "An expert is a man who has made all the mistakes which can be made, in a narrow field." After ten years working as a development manager at web agencies, I feel like I've made more than my fair share. What follows are three different mistakes that I've either made or seen other agencies make in hopes that I might save you, my generous reader, the trouble of making them yourself. ## Act I: The Blood of Unicorns > "...it is a monstrous thing, to slay a unicorn. Only one who has nothing to lose, and everything to gain, would commit such a crime. The blood of a unicorn will keep you alive, even if you are an inch from death, but at a terrible price. You have slain something pure and defenseless to save yourself, and you will have but a half-life, a cursed life, from the moment the blood touches your lips." — J.K. Rowling, Harry Potter and the Chamber of Secrets While in Berkeley last month for BadCamp, I met a designer friend for breakfast. Despite the hip, sun-drenched, vegan-friendly vibe, the excellent espresso and the excitement of the camp ahead, my friend seemed forlorn, and not fully present. My friend is that rare breed of graphic designer who also knows Drupal theming, markup, CSS frameworks, and jQuery...and understands UX...and responsive design...and PHP..and rapid prototyping. This makes him that rare, mythical beast known as a UX unicorn. “What’s wrong?” I asked my friend. “I’m thinking about going out on my own again.” This troubled me as I had recommended my friend find an agency job when he became burned out on the 80-hour weeks that go with running your own freelance business. I pushed for details and found out that my friend was literally booked for 85 billable hours per week on the schedule. Yes...85. He blamed his resourcing manager, but, as I probed for details, another story emerged. It turns out the resourcing manager was just caught in the crossfire between sales and production. It turns out that the agency had a disproportionately large sales organization, a much-too-small production team, and rampant opportunities. My friend’s agency, let’s call them Acme, had grown up in an area where web talent was hard to find and expensive. Instead of holding back sales, the company opened the floodgate, hiring junior developers, interns, and a legion of “account managers” to help manage client expectations. These teams would provide the illusion of service while waiting for the few talented engineers and creatives to free up. The account managers ostensibly hired to protect the developers from clients, turned on their besieged production team, jockeying with each other for the attention of designers and developers for their respective clients. In the end, according to my friend, Acme had quality control problems even though they were building fairly simple projects, and, while they were able to sell lots of web projects in the $15K - $30K range, their client retention was poor, and they didn’t have a reputation that would allow them to start taking fewer, larger clients—a typical path to sanity for smaller agencies. Acme’s problem lies in the disparity between its prodigious sales talent and the paucity of engineering talent. They were figuratively drinking the blood of unicorns to sustain their business, but at what cost? Low quality, a poor reputation, low morale, high employee turnover, and a higher cost of sales since there are fewer repeat customers. So how to solve Acme Agency’s problems? ### Finding Solutions First of all, they need to discipline their sales effort. At Lullabot, we’ve created a capacity chart. This is the photo negative of the typical manager’s resource utilization chart. Instead of showing who’s booked on what, it shows the sales team what availability can be sold. Once that availability is removed from inventory, it’s gone, and the sales team doesn’t try to sell it. At Lullabot, we try to book our team no more than 30 hours per week. There are occasional exceptions of course, as we still exist in the real world of unexpected technical hurdles, client-driven delays, and late launch nights, but it’s important to us to protect the sanity of our team. We don’t do this out of altruism. We do it because human beings and organizations need slack in the system. As Tom DeMarco says, in his fabulous book Slack: “Slack is the natural enemy of efficiency, and efficiency is the natural enemy of slack. There are things you can do to make an organization more efficient that interfere with its ability to change and reinvent itself later.” What does our Lullabot team do with this slack in the system? Many Lullabots spend hours working in the Drupal issue queue or on their contributed modules, others learn new technologies, and build their own projects and products. Most people have heard of Google’s 20 percent time, so this should be nothing new. But in addition to allowing us to innovate and adapt as an organization over time and retain good people, it also means our quality is higher (since our people aren’t overworked, they’re less likely to sacrifice quality for speed and, in their own time, they’re constantly researching and playing with the latest technologies). As quality improves, reputation improves, and, as your reputation increases you can take on bigger and bigger projects. With bigger projects comes the opportunity to book people solely on one project. Human beings are not fungible commodities like oil that can be horse traded in bits and pieces between projects. Task switching incurs an enormous penalty on efficiency. Some theorists estimate efficiency losses as high as 15% for each marginal project. If you can avoid it, don’t spend precious efficiency subdividing your people between a thousand different clients. Finally, the tools exist to manage your people virtually. If Acme were to relax their in-office requirement they might be able to find more Drupal talent wherever it exists, which would allow them to grow their overloaded production team. Find the best talent where it exists. Lullabot is a fully distributed company, which means we can hire the best Drupal developers wherever they choose to live. Throwing inexperienced production staff—or worse yet, account managers—at your technical debt just because they’re the best you can find locally is not a reasonable solution. - **Don’t overschedule your resources, the pursuit of efficiency can be the enemy of flexibility, retention, and sustainability.** ***Discipline your sales effort and make sure it matches your capacity*** **Consider a capacity report that shows your sales team exactly what they have to sell.** - **Hire talented people wherever they are, don’t limit yourself geographically.** ## Act II: Of Hubris and White Whales > “Talk not to me of blasphemy, man; I'd strike the sun if it insulted me. For could the sun do that, then could I do the other; since there is ever a sort of fair play herein, jealousy presiding over all creations. But not my master, man, is even that fair play. Who's over me? Truth hath no confines.” —Moby Dick “I would strike the sun if it insulted me!” I remember reading these words with adolescent awe and thinking, “F’ yeah.” I loved Ahab’s outrageous hubris. It was similar for me with Nietzsche and with every nihilist protagonist in Russian literature who would occasionally break their studied silence to play Vodka-fueled Russian Roulette to the great distress of some adoring tragic heroine. My judgment had a way to go. Hubris is after all tragic flaw numero uno. Earlier this year, Lullabot was referred to a sexy opportunity. This was way BIG for us…and our standard fare of clients like The Grammys, Sony Music, and Martha Stewart is not exactly small potatoes. With fame, fortune, and a wee bit of hubris in our eyes, we flew out to a major Eastern seaboard city and made our pitch. After hearing more about the opportunity, we decided to go for it. Marshaling a large part of the team’s energy, we put together a 100-page proposal, providing an architectural roadmap and recommendations for how we would do the project. A week later we heard back; we were a finalist! Even better, one of two finalists. We were ecstatic. We headed out East again with about 10 Lullabots to do a second round of presentations. During this time, I had a niggling, yucky feeling at the back of my mind. Because of the short timeline and massive scope, this project as we had proposed it was going to take nearly all of our resources and involve some of our partner network of companies. We were ignoring the fundamental rule of risk mitigation: diversification. Portfolio theory 101 tells us that risk and reward are inextricably linked. Because humans like reward and abhor risk, we diversify. In financial terms, the risk is essentially volatility. Projects are inherently volatile, but they are differently volatile. By having multiple projects, an agency mitigates risk. Some projects will go great, others will drag you down, but having a healthy mix means a lower exposure to volatility in any particular direction. But why make it so complicated? Our ancestors walking out of the cave with two separate baskets of eggs knew this principle. It’s common sense. But we were intent on this figurative white whale, swayed by the collective excitement of doing something big and unparalleled. A week after our visit, there was ominous silence from the potential client. We knew this wasn’t a good sign, but it wasn’t until a week later till we heard we’d lost the project to another, larger agency. We’d spent $10K chasing that whale, but we also felt strangely relieved as we returned to business as usual. Sadly, that’s not the end of the story. We found out a couple of months later that the BIG CLIENT ended up purchasing a major existing web asset to fill the gap and terminated the project after a month or so, leaving the winning vendor in an awkward position. We’d dodged a bullet on that one. To ramp up for a project this big and long, we likely would have slowed our sales effort, gotten into agreements with partner agencies, and committed the bulk of our team. Extricating ourselves from that mess may have proved difficult. ### Lessons Learned Thinking back on the experience, it’s not only the lesson of diversification that I learned. In Moby Dick, Starbuck, the Pequod's charismatic first mate, tries to save the ship, questioning Ahab's monomaniacal obsession with the whale. I feel like my inner Starbuck was trying to warn me all along, but I was unwilling to listen. If you find yourself chasing whales, make sure to engage in a broad, open and honest conversation with your team and examine your own instincts. Don’t quelch dissent by simply labeling it as pessimism. And, if you still decide to harpoon a Leviathan, make sure you’re capable of enduring the downside, not just the up. - **If you’re going to go chasing whales, check your hubris at the door and make sure you’re consciously assuming the risk.** ***If you’re risk averse think of your project portfolio as you would your 401K and diversify, diversify, diversify.*** - **Allow for healthy dissent in your company. Listen to those niggling discomforts in the back of your mind and bring them to the forefront to be analyzed.** ## Act III: The Ponze Ain’t Cool > “How sad it is! I shall grow old, and horrible, and dreadful. But this picture will remain always young. It will never be older than this particular day of June. . . . If it were only the other way! If it were I who was to be always young and the picture that was to grow old! For that-for that-I would give everything! Yes, there is nothing in the whole world I would not give! I would give my soul for that!” —Oscar Wilde, The Picture of Dorian Gray Dorian Gray trades his soul so that the damage from all of the years of sin and debauchery accrue to his portrait instead of himself. While the portrait shuttered up in the attic grows blacker and more hideous with each passing year, Dorian retains his youthful, innocent beauty. Of course, eventually things come home to roost, but I don’t want to spoil the ending. How does this relate to a mistake that an agency might make? When I started out with my first agency in 2003, we were three people renting the extra space in a server room from a larger area software company. We were eager to make a dent in the marketplace and build some awesome websites. We wanted to make dreams happen, especially our own. Like many small businesses, we didn’t have a lot of money to spend on accountants or process, so we used Quickbooks and set up a basic chart of accounts. In cash accounting, you recognize cash as it comes in and goes out. It’s that simple. This seemed far less complicated than accrual accounting, so it seemed like the right choice for us. Being the savvy business people we were, we didn’t want to get scammed, so we decided on a policy of 75% up-front and 25% on launch. Because our sites were small, most of the business people we wanted to work with could afford this, and we didn’t want to work with the ones who couldn’t. Life was good. This policy saved our bacon a couple of times, both because it protected us from unscrupulous players, but, also because there’s a time value to money, and cash sooner, trumps cash later. With lots of cash up front, we were able to bring on additional resources as the jobs got bigger and we didn’t suffer liquidity problems. At least in the beginning. Soon, problems began to emerge. It seemed like we could never finish projects and there were too many jobs to do, with new ones piling up at the door. Still, top-line revenue growth was off the charts, which suggested to us we should keep doing more of the same. We grew and grew, hired and hired. Because we weren’t tying revenue to work, the only incentive was to take more work, regardless of capacity. Eventually, it becomes like an addiction, a Ponzi scheme. You can’t afford to pay people to finish your current projects until you’ve secured new work, but the new work is paying for work you should have done in the past. ### Avoiding the Pitfalls Growth can be a Faustian bargain, camouflaging a host of problems. If you’re going to grow, do it carefully and fund it with cash reserves from profits you’ve booked in the past, not with the revenues from future projects. There’s such a thing as a [maximum financeable growth rate](https://en.wikipedia.org/wiki/Sustainable_growth_rate) if you’re growing without the benefit of outside capital. It’s beyond the scope of this article to explain how to make these calculations, but I was able to get a handle on this stuff after taking Guatum Kaul’s excellent, and free [Intro. to Finance](https://www.coursera.org/browse?source=deprecated_spark_cdp) class. In the end, Ponzi schemes by their nature come crashing down. When you’re oversold, your efficiency eventually seizes up, just as too many cars on a highway leads to a traffic jam. At a previous company, we recognized the problem before it was too late and switched to accrual accounting. In addition, we took a more studied approach to growth, but only after a period of illiquidity and diminishing profits caused us to do some real soul searching and self-education. Even if you’re a young company, try to tie revenue to work. Structure your payments into a third at project start, a third at the midway point, and a third at project completion, or tie payments to multiple milestones in the project. If you want the litmus test of money up front as we did, talk to your accountant or bookkeeper about how to stick the revenue on the balance sheet and then recognize it over time as you work the hours. Another strategy that Lullabot uses is to work with clients on an agile basis and have them pay per sprint for a set amount of resources. Our sprint model—based on a combination of scrum and rapid prototyping—typically involves a cross-functional team of three full-time resources for a series of two-week sprints at a fixed cost. Ideally, the team has a UX unicorn, a back-end developer, and a front-end developer. The client controls the backlog and dictates what we work on in each sprint. By being cross-functional, small, dedicated, and proficient with Drupal, we’re able to bring projects to market quickly, and for much less than comparable scope waterfall projects. Also, we solve the cash flow problem. Each team earns a regular two-week paycheck for the previous two weeks of work. How might you structure deals to avoid the cash flow roller coaster? - **You don’t have to fully convert to the complexity of accrual accounting to tie revenue to work.** - **Learn about SFG (maximum financeable growth rate) and maintain adequate working capital in the business.** - **Growth, like Dorian Gray’s portrait, can conceal a host of ills. Pace yourself and be careful not to disguise project or resource problems with growth. It will catch up with you in the end.** ## Epilogue The pitfalls discussed above don't appear out of nowhere; they often come alongside opportunities. Learning to sidestep them helps mature web agencies to make the most of the good without suffering from the bad. *Wood engraving image from the Illustrated London News in 1847* 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: "Assembling Pages with Drupal" url: "/articles/assembling-pages-with-drupal" type: article date: 2010-07-17 updated: 2017-10-03 --- # Assembling Pages with Drupal # Assembling Pages with Drupal Blocks vs. Context vs. Panels By [ David Burns ](/about/david-burns) July 17, 2010 As with many facets of Drupal, and coding in general, there are multiple ways to accomplish the same task. A good example of this was with the recent additions to the Lullabot team. The expanded team brought together three skilled developers and an amazing designer each with their own methods of site building. On one side we have [Jerad Bitner](https://www.lullabot.com/about/jerad-bitner) and [myself](https://www.lullabot.com/about/david-burns), who for the past few years have been building sites exclusively with [Panels](http://drupal.org/project/panels) module. On the other side we have [James Sansbury](https://www.lullabot.com/about/team/james-sansbury) and [Jared Ponchot](https://www.lullabot.com/about/jared-ponchot) who also build beautiful sites using the more recent [Context](http://drupal.org/project/context) module. Our first collaboration was the redesign of Lullabot.com since this project was initially designed and scoped by James and Jared, the decision to use Context module was already in place. I was in no rush to learn Context when I knew the same result could be achieved with Panels. Lucky for me James and Jared are both excellent resources for answering questions and giving great examples. Now that the project is complete I have a better understanding of Context module. This article is intended to identify the similarities, differences, pros, and cons of using each module to build a Drupal website. There is some overlap of terminology which will occur. A block is a block which can be used in both Context and Panels modules. Context is a stand-alone module, but not the same as "context" that is used in Panels module. This will be clarified as you read the rest of this article. But before we travel too far down the rabbit hole, let's answer two basic questions: **What is a Page?** A page is HTML/JS/CSS rendered to the browser through a series of theme functions, hooks, preprocessors and template files. **How does Drupal build a page?** By making calls to menus, blocks, nodes, and modules, which then provide markup that will be placed into regions within the template system. ## Blocks **"Blocks are the boxes visible in the sidebar(s) regions of your Drupal website. Most of the blocks that you will see (e.g., recent forum topics) are generated on-the-fly by various Drupal modules, but you can also create your own blocks."** ~ http://drupal.org/node/17170 The Block system that comes with Drupal core has been around since Dec 2000, back when Drupal was just a baby. Since that time it has undergone many revisions and improvements. Today, modules can provide their own blocks by invoking [hook\_block()](http://api.drupal.org/api/function/hook_block/6). These blocks can then be configured on the block administration page. A block can then be placed into a single region per theme and have its visibility set by path, the currently logged-in user's role, or custom which involves PHP logic that will return TRUE (enabled) or FALSE (disabled). For a number of websites this is enough options to accomplish most layouts, but as you gain expertise and work on more complex sites you begin to see the limitations of only having a single region to place a block within a theme. It's also worth mentioning that using custom and executing PHP from the database is very bad practice and could potentially be a security risk. ## Context **"Context allows you to manage contextual conditions and reactions for different portions of your site... Think of conditions as a set of rules that are checked during page load to see what context is active. Any reactions that are associated with active contexts are then fired."** ~ http://drupal.org/project/context I like to refer to Context as "The Advanced Page Builder". This is the block system on steroids, but even that is an understatement. Context module, created by the awesome team at [Development Seed](https://developmentseed.org/), lets you manipulate and activate items from the menu, block, module and theme systems using a robust set of conditions. Multiple conditions (or rules) can be set per context and multiple contexts can be set per page. Sounds confusing but hopefully, a brief use case and diagram will help clarify. **Context 1:** Condition - Current node is of type "Blog" and user viewing blog has a role of "Admin". Reaction - Display Admin Block in Left Column. Display a view list block of all nodes by current node author in Left Column. **Context 2:** Condition - Current node has taxonomy "Tech" Reaction - Display a view list block of all Blogs that have a "Tech" taxonomy in the Left Column. Set Active menu item to "Tech". Before a page is rendered, all of the context for a page will be called. If the current user logged in viewing this blog node has the "Admin" role, Context 1 and Context 2 will both exist, the resulting output of this page will have Admin Block, view list block of all nodes by current node author, and a view list of all Blogs that have a "Tech" taxonomy displayed in the region "Left Column" and the Active menu item will be "Tech". If the current user does not have the "Admin" role, only Context 2 will be true resulting in a view list of all Blogs that have a "Tech" taxonomy displayed in "Left Column" region and the Active menu item will be "Tech". ![context example](/sites/default/files/styles/wide_xs/public/context.png.webp?itok=moTBmmlX "context.png") Due to this ability to mix, match, and stack context per page you have the ability to place not just blocks, but also views (provided by [Views](http://drupal.org/project/views) module) and menus into various regions within a page. In comparison, the core block system limits a block to only appear in a single region per theme with constrained visibility rules. This is great for site builders who can conceptualize sections of a site into the specific context that will be needed to put together an entire site. However, this is not really great for handing a site over to a client who is expecting to modify page layouts without first explaining how Context module works. Another limitation of Context is that it's limited to the regions provided by the template system. The default regions available will be those provided by the theme. Context module allows you to provide your own template files which can add more regions and unique page layout. ## Panels **"The [Panels module](http://drupal.org/project/panels) allows a site administrator to create customized layouts for multiple uses. At its core, it is a drag-and-drop content manager that lets you visually design a layout and place content within that layout."** ~ http://drupal.org/project/panels I like to refer to Panels as "The visual Page Builder". If you are familiar with the tabbed navigation of Views 2, then you will be familiar with the navigation of Panels 3. The reason for this similarity is that both modules are the brainchild of Earl Miles ([merlinofchaos](http://drupal.org/user/26979)). Views and Panels work very well together because of this. At its core, Panels is a drag and drop interface for placing content into sections on a page. This is attractive to most site builders because it sounds easy, and if you're just dropping in blocks or views then, for the most part, it is simple to use. Panels is path driven. When creating a panel page you must declare a path. This path is what will invoke panels module to take over the rendering of the content area the page. This also means that Panels module can "hijack" the rendering of paths provided by Drupal core and other modules. As an example, let's say you enable Panels to render the output for 'node/%'. Once enabled you won't notice much of a difference when viewing the node, but if you look at the page source you'll see that there are divs with IDs and Classes which identify Panel panes. Panes are regions within panels in which content can be placed. Panels provides a number of layouts (templates) that come shipped by default, they include 1, 2, 3 column, stacked or bricked. Another option for layout is Flexible, which allows you to build very complex layouts using a JavaScript UI without the need of creating custom template files. However, if you're more comfortable [using custom template files](http://drupal.org/node/495654) that is also an option. Panels also provides you with the option to disable all sidebars provided by the theme, thus giving you further control of the rendered page. So back to our example of using Panels to render 'node/%', and we've decided upon a layout (example: 2 column). How do we change the layout for a different content type? This is actually pretty simple. Each Panel you create allows you to have "Variants". Variants use selection rules that will trigger a specific panel display. Our default for all 'node/%' will be 2 column, but we can create a variant for node type Blog which can have an entirely different layout with entirely different content displayed in panes. Aside from being a great way to change layout and position content on a page, Panels is a powerful tool that makes all content aware of each other. Since Panels is a wrapper that pulls in objects before rendering HTML to the screen, it has the ability to pass information about a block, node, or view to other objects within the panel. This is what Panels calls "context" (not to be confused with Context module). To build upon our original example of using 'node/%' as our Panel display, we then have the ability to pass anything (CCK fields, author info, relationships, taxonomy, flag counts) from the node, into a view being rendered in the same panel! ## Performance Performance is important for any site (just stating the obvious). Whether it's a high traffic site spread across multiple servers or a small site on shared hosting, making the best use of resources available to deliver pages to your user faster gives a better user experience. With that said Drupal has a great caching mechanism built into core that, when enabled, provides significant improvements. However, the core block system is not as efficient at pulling in content compared to Context or Panels. The core block system loops through block\_list() for each template region and selects all active blocks in that block for the current user. It then checks that block to see if it's enabled for current path or validates custom PHP rule. To use an analogy, think of an audience and a stage (which works well at DrupalCamp), and think of each member of the audience as a block. All members of the audience raise their hand if their first name starts with "A" (select region). The audience members with their hands raised keep their hands up if they consider themselves a themer (select active). Those remaining come up to the stage but put yourself in alphabetical order based on last name (sort delta). Repeat this process for each letter of the alphabet (each region). You can see this isn't exactly efficient. Context is a vast improvement over the block system. After creating a set of conditions (rules) and reactions (display) via the Context UI (a replacement to the Block Admin page) this information is stored. When the page object is being built before being passed into theme functions it identifies which context currently exist and adds the context reaction (block, menu, views, etc.) to the page. Continuing the audience analogy, select 2 separate groups (context 1 and context 2) of people (reactions) at random and tell them where they would stand on stage. Clarify that not everyone on stage is a block, some can be a view or active menu item. I tell them context 2 no longer exist, the people in that group would leave the stage. This is much more efficient than looping through each region and checking for active blocks. Panels has a bad wrap for being inefficient. I've know developers brought into projects to strip out Panels to improve performance. Let me publicly state that Panels is NOT the cause of slow loading pages. Inefficient and uncached queries used within views and blocks (both custom and provided by other modules) pulled into a Panel cause long page loads. Creating complex pages by stacking multiple inefficient blocks, views, and tabbed displays into a page will increase page load times drastically. However, the maintainers of Panels noticed this and have built in an incredible caching system that let's you cache individual panes inside a panel display. As stated before Panels is simply a wrapper that calls content into a page. Using the audience example one last time, I pick and choose the members I want from the audience and tell them exactly where to stand on stage. That's it, it's done. Panels is fast and direct. With all that being said, there are other more efficient ways to address server performance, like apache/mysql tweaking, reverse cache, memcache and/or CDN for static files. Some of these advanced performance techniques are impossible to achieve in shared hosting environments. You get what you pay for. ## Use Case For many new users and simple site builders, using blocks is enough to get the job done. If the job gets done quickly and correctly, isn't that all that should matter? Stick with what works. For more advanced projects that have complex layouts or require all changes to exist in code (exportable) then you'll need to use Context or Panels. Exportables are extremely important because it keeps all changes in code. This makes it very easy to develop in a local->dev->stg/qa->live environment. It also saves some time if someone makes a change that breaks the site or layout. Exportables make it very easy to revert, even more so if using a version control system. Context is great for developers, not so great for end users. The abstract model of context requires a strong understanding of the project and which context will need to be created to achieve the desired output. It is much easier to understand Context UI than Panels UI. Context UI is more dynamic at putting together page variants by the way it allows multiple contexts to be declared based upon what's rendered on the page. However, it's also limited to passing information within a piece of rendered content to other content that exists on the page. By adding jQuery UI and Admin module Context module can provide a drag and drop UI for adding content onto an existing page. This is an excellent feature that brings its usability closer to the drag-n-drop ability of Panels. ![Context UI](/sites/default/files/styles/wide_xs/public/context_ui.png.webp?itok=sUos97gM "context_ui.png") Panels is great for site builders, and if there were a simple mode (fewer options) for the UI it would be great for end users as well. As it stands the nice drag and drop interface works well, but once you get into the configuration options of Panels, things get confusing for those new to it. These issues are being addressed with [upcoming Panels 3](http://panels3.dev.logrus.com/). It's also good for non-developer site builders that are familiar with argument handling (from views module) to create a robust page with content that is all related. To me, this is the biggest benefit and underused part of Panels. I could go on for two days about all of the awesome features of Panels, but that'll need to be in a separate article. ![panels UI](/sites/default/files/styles/wide_xs/public/panels.png.webp?itok=wpp8Sq7G "panels.png") ## Future The ultimate goal here and with Drupal, in general, is to allow site builders to build robust sites with related content without the need to get into the code. As you can see Context and Panels modules do very similar task with very different approaches. But what is important to all of us is that they provide a solution. There was a minor disturbance in the force (aka our community) seeing two vital groups of developers addressing similar issues without collaborating. Well good news, this year at DrupalCon San Francisco, these bright individuals were forced into a small room to settle their differences. By the end of this meeting, there were hugs and tears of joy (okay, maybe just hugs). What does this mean for Drupal? It means that the power of context, the ability for content on a page to be more dynamic and share information between each other will be an initiative that hopefully we will see built into a future release of Drupal core. Our modules can tap into a context API which will be full of valuable information about the user, node, views, blocks, and menus that exist within the page. This is huge. This is important. This is what keeps Drupal ahead of all other Open Source Content Management 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: "Quick-and-Dirty Debugging" url: "/articles/quickanddirty-debugging" type: article date: 2007-04-27 updated: 2016-04-07 --- # Quick-and-Dirty Debugging # Quick-and-Dirty Debugging Printing errors and backtraces to your page By [ Angie Byron ](/about/angie-byron) April 27, 2007 So you're coding along, developing your site, minding your own business, when you load a page you see some totally vague, obscure error message such as: ```php warning: Invalid argument supplied for foreach() in /modules/node/node.module on line 504. ``` Where do you even start debugging something like that? Well, you could start with looking at line 504 of node.module... ```php foreach ($param as $key => $value) { ``` Uh, yeah. *That's* helpful. If you were astute, you might also observe that line 504 is inside of the node\_load() function. So that narrows it down to... only one of the 80,000 places node\_load() might be called from one of your core or contributed modules or themes. Ugh... You could decide to get all fancy, and install an IDE like Komodo, Eclipse, or Zend, and use their real-time debugging features to step through the code one line at a time to figure out what's happening. It's pretty time-consuming, and it takes up lots of resources on your machine, but it'll probably help you nail it down if you are observing carefully. **Or!** You can debug the quick and dirty way! Read on to find out how! In bootstrap.inc, find the function definition for **drupal\_set\_message** and place the following code at the top: ```php function drupal_set_message($message = NULL, $type = 'status') { // DEBUG: Go track down that stinkin' error... if ($type == 'error') { $message .= ' ``` ```php '. print_r(debug_backtrace(), 1) .' ``` `'; } // END DEBUG if ($message) { ... ` What will this do? It'll make that error above become something like this instead: ```php warning: Invalid argument supplied for foreach() in /modules/node/node.module on line 504. Array ( [0] => Array ( [file] => /includes/common.inc [line] => 552 [function] => drupal_set_message [args] => Array ( [0] => warning: Invalid argument supplied for foreach() in /modules/node/node.module on line 504. [1] => error ) ) [1] => Array ( [file] => /modules/node/node.module [line] => 504 [function] => error_handler [args] => Array ( [0] => 2 [1] => Invalid argument supplied for foreach() [2] => /modules/node/node.module [3] => 504 [4] => Array ( [param] => [revision] => [reset] => ... ) ) ) [2] => Array ( [file] => /sites/all/modules/custom/custom_module/custom_module.module [line] => 10 [function] => node_load [args] => Array ( [0] => ) ) [3] => Array ( [file] => /includes/form.inc [line] => 365 [function] => custom_module_form_alter [args] => Array ( ... ``` Note: If you have many errors on a page, this will probably crash your browser. ;) That's why we call it "Quick and Dirty." ;) Uh. So what the heck *is* all of that crap? It's called a **backtrace**... it's showing you in reverse order the functions that were called, and the arguments that were passed into each, in order to get to the current spot in the code. Let's take just chunk #0 and dissect it: `    [0] => Array



        (



            [file] => /includes/common.inc



            [line] => 552



            [function] => drupal_set_message



            [args] => Array



                (



                    [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. ![query_plugins_screenshot_1.png](/sites/default/files/styles/wide_xs/public/field_regular_upload/query_plugins_screenshot_1.png.webp?itok=qi3PBbLd "query_plugins_screenshot_1.png") 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. ![query_plugins_screenshot_2.png](/sites/default/files/styles/wide_xs/public/field_regular_upload/query_plugins_screenshot_2.png.webp?itok=RudtY0yo "query_plugins_screenshot_2.png") 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! ![query_plugins_screenshot_3.png](/sites/default/files/styles/wide_xs/public/field_regular_upload/query_plugins_screenshot_3.png.webp?itok=yZ1I-TWm "query_plugins_screenshot_3.png") ## 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! ![insert-med.png](/sites/default/files/styles/max_900/public/articles/newicons/insert-med.png.webp?itok=YIqOJPyt "insert-med.png") ### 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 ![Info Icon](/sites/default/files/styles/max_900/public/articles/newicons/info-small.png.webp?itok=GyrcmSLW "info-small.png") ![Info Icon Bad](/sites/default/files/styles/max_900/public/articles/newicons/info-bad.png.webp?itok=gSLRYXBE "info-bad.png") ![Info Icon Good](/sites/default/files/styles/max_900/public/articles/newicons/info-good.png.webp?itok=BdaDW3IS "info-good.png") 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 ![" width=](/sites/default/files/styles/max_900/public/articles/newicons/arrow-unmasked-big.png.webp?itok=RVB0JaV8 "arrow-unmasked-big.png") ![" width=](/sites/default/files/styles/max_900/public/articles/newicons/arrow-masked-big.png.webp?itok=1xL4CXQr "arrow-masked-big.png") ![" width=](/sites/default/files/styles/max_900/public/articles/newicons/arrow-unmasked.png.webp?itok=xVBv_Eec "arrow-unmasked.png") Unmasked icon on a color background ![" width=](/sites/default/files/styles/max_900/public/articles/newicons/arrow-masked.png.webp?itok=OG47XVC2 "arrow-masked.png") Masked icon on a color background ![" width=](/sites/default/files/styles/max_900/public/articles/newicons/arrow-alpha.png.webp?itok=tj1pMmKh "arrow-alpha.png") 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. ![directory.png](/sites/default/files/styles/max_900/public/field_regular_upload/directory.png.webp?itok=I1-4YYmn "directory.png") 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. ![in-action.png](/sites/default/files/styles/max_900/public/field_regular_upload/in-action.png.webp?itok=qkMkJAa5 "in-action.png") 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. ![installing.png](/sites/default/files/styles/max_900/public/field_regular_upload/installing.png.webp?itok=ZcKGm85O "installing.png") 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) ![Big-IP Administration Page](/sites/default/files/styles/wide_xs/public/u10/1-landing-page.png.webp?itok=ihGqvW6v "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](/sites/default/files/styles/wide_xs/public/u10/2-add-cert.png.webp?itok=G7gTALUK "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](/sites/default/files/styles/wide_xs/public/u10/3-cert-list.png.webp?itok=vd-728XU "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](/sites/default/files/styles/wide_xs/public/u10/4-ssl-profle.png.webp?itok=MNP24y0s "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](/sites/default/files/styles/wide_xs/public/u10/5-https-virtual-server.png.webp?itok=oEsdvxMc "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](/sites/default/files/styles/wide_xs/public/u10/6-virtual-server-pool.png.webp?itok=LKOfJ8OC "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. ![Keymap](/sites/default/files/styles/wide_xs/public/assets/2015-11/1_keymap.png.webp?itok=fedieNIB "1_keymap.png") ## 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. ![Characters](/sites/default/files/styles/wide_xs/public/assets/2015-11/2_indent.png.webp?itok=wEgyx7dZ "2_indent.png") 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. ![Predefined Drupal style](/sites/default/files/styles/wide_xs/public/assets/2015-11/4_drupal.png.webp?itok=CsdJ0ZVc "4_drupal.png") 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. ![Spaces](/sites/default/files/styles/wide_xs/public/assets/2015-11/6_spaces.png.webp?itok=z_3vlv_6 "6_spaces.png") 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 ![Wrapping and Braces](/sites/default/files/styles/wide_xs/public/assets/2015-11/7_wrapping_braces.png.webp?itok=poOcgX5U "7_wrapping_braces.png") ![Wrapping and Braces](/sites/default/files/styles/wide_xs/public/assets/2015-11/8_wrapping_braces.png.webp?itok=7VOKrhYw "8_wrapping_braces.png") 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. ![Inspection](/sites/default/files/styles/wide_xs/public/assets/2015-11/11_inspection.jpg.webp?itok=nis0uFv5 "11_inspection.jpg") Now you should get some nice warnings as reminders for keeping up the coding standards. ![Inspector Warnings](/sites/default/files/styles/wide_xs/public/assets/2015-11/12_highlight.png.webp?itok=tBYxo1M1 "12_highlight.png") ## 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. ![Zoom](/sites/default/files/styles/wide_xs/public/assets/2015-11/13_editor.jpg.webp?itok=0x0aF0r1 "13_editor.jpg") ## 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: ![Revision Moderation module in the SimpleTest screen](/sites/default/files/styles/wide_xs/public/assets/2016-04/revision_moderation_test.png.webp?itok=nwuF86aa "revision_moderation_test.png") ## 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: ![All SimpleTest tests passed](/sites/default/files/styles/wide_xs/public/assets/2016-04/simpletests_passed.png.webp?itok=N4ttqMe_ "simpletests_passed.png") 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: ![Some SimpleTest failures](/sites/default/files/styles/wide_xs/public/assets/2016-04/simpletests_failed.png.webp?itok=GmeacnXs "simpletests_failed.png") 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. ![Phing tasks](/sites/default/files/styles/wide_xs/public/u40/Screen%20Shot%202012-01-15%20at%2017.48.00.png.webp?itok=K2SZ7ZLG "Screen Shot 2012-01-15 at 17.48.00.png") 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. ![Terminal screenshot showing deployed site](/sites/default/files/styles/wide_xs/public/u40/Screen%20Shot%202012-01-16%20at%2014.01.53.png.webp?itok=LYp0aMzT "Screen Shot 2012-01-16 at 14.01.53.png") ### 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. ![Phing tasks subfolder](/sites/default/files/styles/wide_xs/public/u40/Screen%20Shot%202012-01-16%20at%2011.59.38.png.webp?itok=7In5XjcZ "Screen Shot 2012-01-16 at 11.59.38.png") 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. ![Jenkings Phing configuration](/sites/default/files/styles/wide_xs/public/u40/Screen%20Shot%202012-01-16%20at%2015.07.38.png.webp?itok=F8KblNVD "Screen Shot 2012-01-16 at 15.07.38.png") 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. ![Jenkins Phing configuration build properties](/sites/default/files/styles/wide_xs/public/u40/Screen%20Shot%202012-01-16%20at%2015.21.22.png.webp?itok=061NrDmV "Screen Shot 2012-01-16 at 15.21.22.png") 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. ![theme-header-setting.png](/sites/default/files/styles/wide_xs/public/theme-header-setting.png.webp?itok=AmKxRYAk "theme-header-setting.png") ### 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: ![The output of phpinfo() showing that memcache is successfully installed](/sites/default/files/styles/wide_xs/public/memcache-phpinfo.png.webp?itok=p3zU-znN "memcache-phpinfo.png") ## 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 ![wbs_example.png](/sites/default/files/styles/wide_xs/public/assets/2015-10/wbs_example.png.webp?itok=fED2NLTq "wbs_example.png") 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. ![varnish-server-diagram_0.png](/sites/default/files/styles/max_900/public/varnish-server-diagram_0.png.webp?itok=2Zp7HzZH) 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. ![matrix](/sites/default/files/styles/wide_xs/public/assets/2015-07/project-matrix-1.png.webp?itok=83GTSAaI "project-matrix-1.png") ## 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. ![matrix](/sites/default/files/styles/wide_xs/public/assets/2015-07/project-matrix-2.png.webp?itok=vq6-qvcj "project-matrix-2.png") ## 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. ![matrix](/sites/default/files/styles/wide_xs/public/assets/2015-07/project-matrix-3.png.webp?itok=TzoKuvWa "project-matrix-3.png") 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. ![calc](/sites/default/files/styles/max_900/public/assets/2015-07/project-matrix-calc.png.webp?itok=DiX7uMK1 "project-matrix-calc.png") ## 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. ![no-clean-urls.jpg](/sites/default/files/styles/wide_xs/public/no-clean-urls.jpg.webp?itok=pzPMfdYd "no-clean-urls.jpg") ### 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. ![clean-urls.jpg](/sites/default/files/styles/wide_xs/public/clean-urls.jpg.webp?itok=DuD1SBdp "clean-urls.jpg") ### 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-alias.jpg](/sites/default/files/styles/wide_xs/public/path-alias.jpg.webp?itok=Ye21bxP8 "path-alias.jpg") 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 ![cmnt-ddm.png](/sites/default/files/styles/wide_xs/public/cmnt-ddm.png.webp?itok=3QMPmKIM "cmnt-ddm.png") #### 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. ![cmnt-defdisporder.png](/sites/default/files/styles/wide_xs/public/cmnt-defdisporder.png.webp?itok=9f-44WUB "cmnt-defdisporder.png") ### 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**. ![cmnt-controls.png](/sites/default/files/styles/wide_xs/public/cmnt-controls.png.webp?itok=B0KsON3N "cmnt-controls.png") ### 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**". ![cmnt-defcpp.png](/sites/default/files/styles/wide_xs/public/cmnt-defcpp.png.webp?itok=U58ShGgz "cmnt-defcpp.png") ### 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 ![cmnt-anon.png](/sites/default/files/styles/wide_xs/public/cmnt-anon.png.webp?itok=cy3tmafD "cmnt-anon.png") ### 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 ![cmnt-subj.png](/sites/default/files/styles/wide_xs/public/cmnt-subj.png.webp?itok=3DfqRk5x "cmnt-subj.png") **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**. ![cmnt-prev.png](/sites/default/files/styles/wide_xs/public/cmnt-prev.png.webp?itok=FKTX_Tka "cmnt-prev.png") **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**. ![cmnt-location.png](/sites/default/files/styles/wide_xs/public/cmnt-location.png.webp?itok=oBBdemAV "cmnt-location.png") **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: ![Block region examples](/sites/default/files/styles/wide_xs/public/assets/2016-04/page-regions.png.webp?itok=gXKvt551 "page-regions.png") 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. ![checksheet-in-action.png](/sites/default/files/styles/wide_xs/public/field_regular_upload/checksheet-in-action.png.webp?itok=uhlJ6_UO "checksheet-in-action.png") 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. ![standard.jpg](/sites/default/files/styles/wide_xs/public/standard.jpg.webp?itok=IgLez0v2 "standard.jpg") ## 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. ![multiple_selects.jpg](/sites/default/files/styles/wide_xs/public/multiple_selects.jpg.webp?itok=i2f7AY2- "multiple_selects.jpg") ## 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. ![chosen.jpg](/sites/default/files/styles/wide_xs/public/chosen.jpg.webp?itok=W7gVFTpc "chosen.jpg") 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. ![dynamic_multiselect.jpg](/sites/default/files/styles/wide_xs/public/dynamic_multiselect.jpg.webp?itok=RV7cst0D "dynamic_multiselect.jpg") 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. ![entityreference_views_widget.jpg](/sites/default/files/styles/wide_xs/public/entityreference_views_widget.jpg.webp?itok=X2EdrGX1 "entityreference_views_widget.jpg") 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: ![improved_multi_select.jpg](/sites/default/files/styles/wide_xs/public/improved_multi_select.jpg.webp?itok=gckQ60ZW "improved_multi_select.jpg") ## 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: ![jqueryui_multiselect.jpg](/sites/default/files/styles/wide_xs/public/jqueryui_multiselect.jpg.webp?itok=qbbtyr7- "jqueryui_multiselect.jpg") 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. ![settings page](/sites/default/files/styles/wide_xs/public/imagecache_picture/settings.png.webp?itok=P8-oEHkI "settings.png") Configure your screen so that it is similar to the following. ![settings page after](/sites/default/files/styles/wide_xs/public/imagecache_picture/settings2.png.webp?itok=xPRa4S95 "settings2.png") 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'. ![picture upload](/sites/default/files/styles/wide_xs/public/imagecache_picture/upload.png.webp?itok=rS7OxTld "upload.png") 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. ![imagecache settings](/sites/default/files/styles/wide_xs/public/imagecache_picture/imagecache-settings.png.webp?itok=GOg1g2sB "imagecache-settings.png") 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 '
'.$picture.'
'; } } ``` You might notice that we're doing something a little tricky here. Checkout the original specification for [theme\_user\_picture()](http://api.drupal.org/api/HEAD/function/theme_user_picture). We've cut out a lot of code for simplicity's sake, but we've also added another parameter to the function: $size. We've set its default to 'thumb', so all modules which use the user picture will default to using that imagecache preset. If you desired, you could now create different imagecache presets and use different size images for various places on your site! The user profile page is a page where Lullabot often uses a larger profile picture, such as 250x250 pixels. Wherever you want a larger image, just pass in the imagecache preset as the second parameter, such as `theme('user_picture', $user, 'large');`. Let's checkout the before and after pictures! Before: ![profile before](/sites/default/files/styles/wide_xs/public/imagecache_picture/profile.png.webp?itok=1uBMWP8G "profile.png") After (much better!) ![profile before](/sites/default/files/styles/wide_xs/public/imagecache_picture/profile2.png.webp?itok=pYrDL63X "profile2.png") Pretty cool, huh? But wait, what if the user wants to change their profile picture? If you upload a new photo now for yourself, you'll notice that the profile picture displayed doesn't change. This is because imagecache isn't smart enough to know that the image has changed, so it doesn't update the thumbnail. Now we get into custom module development (yay!). If you're scared now, don't give up! We're almost there and we're going to do a lot of cool stuff! If you don't want to do the programming, you can just skip to the bottom and [download the zip file](https://www.lullabot.com/files/imagecache_picture/imagecache_profiles.zip) (imagecache\_profiles.zip) containing this module. Okay, rather than split up the code I'll paste it all right here. Create a new module directory called 'imagecache\_profiles'. Then create a new text file, 'imagecache\_profiles.module' in that directory. Paste in the code below: ```php /** * Implementation of hook_help(). */ function imagecache_profiles_help($section) { switch($section) { case 'admin/modules#description': return t('utilizes imagecache presets for user profile pictures'); } } /** * Implementation of hook_form_alter(). */ function imagecache_profiles_form_alter($form_id, &$form) { switch($form_id) { case 'user_edit': $form['#validate']['imagecache_profiles_user_edit_validate'] = array(); $form['#submit']['imagecache_profiles_user_edit_submit'] = array(); break; } } /** * Additional form validation for the user_edit form */ function imagecache_profiles_user_edit_validate($form_id, $form_values) { // Add a minimum size requirement to the image upload form if ($info = file_check_upload('picture_upload')) { $image_info = image_get_info($form_values['picture']); if ($image_info['width'] 160 || $image_info['height'] 160) { form_set_error('picture_upload',t('Your profile image must be at least 160 pixels wide and tall (your image was @width x @height pixels).',array('@width' => $image_info['width'], '@height' => $image_info['height']))); } } } /** * Check for new or deleted uploads and clear the imagecache if necessary */ function imagecache_profiles_user_edit_submit($form_id, $form_values) { if (file_check_upload('picture_upload') || $form_values['picture_delete']) { imagecache_image_flush($form_values['picture']); } } ``` Note that this is made to work with Drupal 4.7 also (hence the hook\_help implementation). If you're running Drupal 5, you'll also need to create imagecache\_profiles.info file and paste this code: ``` ; $Id$ name = Imagecache Profile Pictures description = Utilizes imagecache presets for user profile pictures. ``` Save and enable your module, now we can upload new photos and they will automatically be updated by imagecache! Let's review our code piece by piece. hook\_form\_alter ```php /** * Implementation of hook_form_alter(). */ function imagecache_profiles_form_alter($form_id, &$form) { switch($form_id) { case 'user_edit': $form['#validate']['imagecache_profiles_user_edit_validate'] = array(); $form['#submit']['imagecache_profiles_user_edit_submit'] = array(); break; } } ``` This is a very powerful piece of code. [hook\_form\_alter()](http://api.drupal.org/api/HEAD/function/hook_form_alter) allows you to edit the behavior of any form in all Drupal. What we're doing here is adding two additional properties to the form, additional validation and additional submit handling. The name of the function is the second key in the array (such as 'imagecache\_profiles\_user\_edit\_validate') and we're passing in an empty array, meaning we don't need to send any additional parameters. imagecache\_profiles\_user\_edit\_validate ```php /** * Additional form validation for the user_edit form */ function imagecache_profiles_user_edit_validate($form_id, $form_values) { // Add a minimum size requirement to the image upload form if ($info = file_check_upload('picture_upload')) { $image_info = image_get_info($form_values['picture']); if ($image_info['width'] 160 || $image_info['height'] 160) { form_set_error('picture_upload',t('Your profile image must be at least 160 pixels wide and tall (your image was @width x @height pixels).',array('@width' => $image_info['width'], '@height' => $image_info['height']))); } } } ``` This is the first function we added to the user\_edit form. Remember way back when we set the user profile picture settings? We required that all user photos are at least 160 pixels before upload. This small function ensures that minimum size is actually enforced. imagecache\_profiles\_user\_edit\_submit ```php /** * Check for new or deleted uploads and clear the imagecache if necessary */ function imagecache_profiles_user_edit_submit($form_id, $form_values) { if (file_check_upload('picture_upload') || $form_values['picture_delete']) { imagecache_image_flush($form_values['picture']); } } ``` Finally, this last piece of code performs the necessary magic to make sure our cached images are always up-to-date. Imagecache.module provides a mechanism for clearing the cache of any particular file it has saved in all presets. Just pass in the file path the original, and it deletes all the cached versions. They'll automatically be recreated next time the image is requested. Phew, we covered a lot of code. I hope everyone enjoyed and happy imagecaching! [Download the Code](https://www.lullabot.com/files/imagecache_picture/imagecache_profiles.zip) (imagecache\_profiles.zip) 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 Solr for use with Drupal" url: "/articles/installing-solr-for-use-with-drupal" type: article date: 2013-07-31 updated: 2014-05-15 --- # Installing Solr for use with Drupal # Installing Solr for use with Drupal Apache Solr can take your site's search to the next level, but it requires special setup. By [ Ben Chavet ](/about/ben-chavet) July 31, 2013 ## Overview Solr is a powerful and feature-rich search platform released by Apache. Integrating it with Drupal allows for faster and more advanced search options. However, it also means that a Solr instance needs to be installed and running somewhere, similar to how a database like MySQL is required. Solr is a Java application, and can be run independent from any server technology. However, for a production environment, it is typically best to run it in a J2EE server environment such as Tomcat, Glassfish, JBoss, etc. This article describes how to install Solr 4.3.0 for use by Drupal running under Tomcat 7 on a Linux server. ## Java Installation Tomcat and Solr are both Java applications, so the only real prerequisite is to install Java. The method of installation will vary by Linux distribution and the desired flavor of Java. Redhat, CentOS, Debian, and Ubuntu all provide OpenJDK implementations of Java 7 in their software repositories, which is used here. Any Java implementation should work here, though. So feel free to skip this step if if a different flavor is desired, or if Java is already installed on the server. Please note that a full Java Development Kit (JDK) must be installed. A Java Runtime Environment (JRE) installation is not sufficient. Redhat/CentOS: ``` yum install java-1.7.0-openjdk ``` Debian/Ubuntu: ``` aptitude install java7-jdk ``` ## Tomcat Installation Some Linux distributions provide a Tomcat package in their software repositories. However, installing the latest version from the Apache Software Foundation ensures that all of the latest security and bug fixes are present. It also keeps all of the configuration and data files consolidated into one location, and works the same regardless of Linux distribution. **Step 1**: Create a low-privilege user, which will be used to run the Tomcat service. ``` useradd -Mb /usr/local tomcat ``` **Step 2**: Download the latest tar.gz binary of Tomcat 7 from to */usr/local/src/* on the server. **Step 3**: Unpack the Tomcat tar.gz file to /usr/local/tomcat ``` tar -C /usr/local -zxf /usr/local/src/apache-tomcat-7.*.tar.gz mv /usr/local/apache-tomcat-7.* /usr/local/tomcat ``` **Step 4**: By default Tomcat listens on port 8080. However, that is a commonly used port for other services as well. To avoid conflict, change Tomcat to use port 8983 instead with this search/replace comand. ``` sudo sed -i s/8080/8983/g /usr/local/tomcat/conf/server.xml ``` **Step 5**: Finally, change the ownership of the Tomcat directory, and start it to verify that it is working ``` chown -R tomcat:tomcat /usr/local/tomcat sudo -u tomcat /usr/local/tomcat/bin/startup.sh ``` It is important to note that there are some security implications of running Tomcat that need to be considered. Tomcat security is outside of the scope of this article, but there are a number of good resources available that describe how to harden a Tomcat installation, including one provided directly by the Tomcat project at . Generally speaking, though, if the only use for this Tomcat instance is to serve internal Solr requests, blocking outside access with the use of a firewall is usually sufficient. ## Solr Installation Some Linux distributions also provide a Solr package in its repository, but it is typically an old version. Just like with Tomcat, installing the latest package from the upstream project is the method used here. **Step 1**: Download Solr-4.3.0 from to the server and unpack the downloaded file. ``` tar -zxf solr-4.3.0.tgz ``` **Step 2**: Copy the java libraries provided by Solr to the Tomcat library directory ``` cp solr-4.3.0/dist/solrj-lib/* /usr/local/tomcat/lib/ ``` **Step 3**: Copy the log4j configuration file provided by Solr to the Tomcat configuration directory ``` cp solr-4.3.0/example/resources/log4j.properties /usr/local/tomcat/conf/ ``` **Step 4**: Copy the Solr webapp file to the Tomcat webbapp directory ``` cp solr-4.3.0/dist/solr-4.3.0.war /usr/local/tomcat/webapps/solr.war ``` **Step 5**: Create the Solr context file at */usr/local/tomcat/conf/Catalina/localhost/solr.xml* with the following contents. ``` ``` ## Solr Indexes Solr is capable of providing multiple search indexes, or cores, using just one instance of the Solr application. Each core is independently configured, and there is a single configuration file to define each of the cores. The steps below show how to create a core named drupal. These steps can be used to create as many cores as required, each with a unique name. **Step 1**: Create the base Solr directory, and create a copy of the example configuration ``` mkdir -p /usr/local/tomcat/solr cp -r solr-4.3.0/example/solr/collection1/conf /usr/local/tomcat/solr/ ``` **Step 2**: Download the latest version of the apachesolr Drupal module from to the server and unpack the downloaded file. ``` tar -zxf apachesolr-*.tar.gz ``` **Step 3**: Copy the Solr configuration files from the Drupal module to the example Solr configuration directory from above. ``` rsync -av apachesolr/solr-conf/solr-4.x/ /usr/local/tomcat/solr/conf/ ``` **Step 4**: Create the Solr core definition file at /usr/local/tomcat/solr/solr.xml with the following contents to define the drupal core. ``` ``` **Step 5**: Create the drupal Solr core directory as defined above, and copy the example Solr configuration files to that location. These configuration files can be further modified as needed for any specific requirements for this core. ``` mkdir /usr/local/tomcat/solr/drupal cp -r /usr/local/tomcat/solr/conf /usr/local/tomcat/solr/drupal/ ``` **Step 6**: Stop Tomcat, make sure the permissions are correct, and start Tomcat back up ``` /usr/local/tomcat/bin/shutdown.sh chown -R tomcat:tomcat /usr/local/tomcat sudo -u tomcat /usr/local/tomcat/bin/startup.sh ``` The new Solr core admin interface is now available at http://localhost:8983/solr/#/drupal. The URL to use in Drupal's Apache Solr configuration is http://localhost:8983/solr/drupal. ## Wrapping Up The last order of business is to provide a method for which Solr can be started automatically when the server reboots. [Attached to this article is an init script](https://www.lullabot.com/sites/default/files/field_regular_upload/init-d-tomcat.txt) which will provide exactly that, and will work with both Redhat based and Debian based Linux distributions. Create the init file at */etc/init.d/tomcat*. Then, make sure it is executable, and configure it to start on reboot with the following commands. ``` chmod +x /etc/init.d/tomcat ``` Redhat/CentOS: ``` chkconfig --add tomcat ``` Debian/Ubuntu: ``` update-rc.d tomcat defaults ``` If everything goes well, you'll have a working Solr search server. For more information about integrating it with Drupal, you can visit the [Apache Solr Integration](https://drupal.org/project/apachesolr) project on Drupal.org. div.codeblock { font-size: .9em; font-weight: bold; margin-bottom: 1.5em } 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: "What is a Distributed Company?" url: "/articles/what-is-a-distributed-company" type: article date: 2014-05-22 updated: 2019-02-02 --- # What is a Distributed Company? # What is a Distributed Company? Virtual? Remote? Telework? Nope. We're distributed. By [ Jeff Robbins ](/about/jeff-robbins) May 22, 2014 ## Introduction Since 2006, Lullabot has existed as a company without a central office. Early on we called ourselves a "virtual company". We also toyed with other terms like "officeless" or "remote", but none of these terms ever felt right. The term "virtual" always felt ephemeral – like the business itself was made up and the company could evaporate at any time. Even the term "office optional" feels a bit wrong to me. With the rise of the home office, I would argue that just about all of our employees have an office – they have a space from which they do professional work. It's not too far off though. ![electronic equipment](/sites/default/files/styles/max_900/public/assets/2015-09/harris_ts22l.jpg.webp?itok=cZuzYpQ- "harris_ts22l.jpg") "Telework" is accurate. But it feels very technical and mechanical – more focused on the technology than the people. And the word seems to dominate any mental imagery around the work. Saying I run a "telework company" invokes images of people in hard hats hanging from telephone poles. Admittedly, those red lineman's handsets with the dial pad on the back are pretty badass. But that's not what we do. "Remote" is very common. But it uses the same latin roots as "removed" and, to me, implies that our employees were separated from the center of the action; an exception to the rule. And while this might be an accurate description for workers at some companies, Lullabot doesn't work like this. Our employees are exactly where they should be. Most of them work from home. Several work from shared offices or co-working spaces. But [none of them feel like they're missing out on the company culture](https://www.lullabot.com/articles/yahoo-best-buy-and-telecommuting-advice-from-a-distributed-company), idea spaces, casual "water cooler" conversation, or fun activities happening in our central office. We don't have a central office – at least not a physical space where these types of activities happen. ## Distributed Company I believe it was Toni Schneider, then CEO of Automattic, from whom [I first heard this term](https://toni.org/2010/03/08/5-reasons-why-your-company-should-be-distributed/). It just felt right. Our people are *distributed*. They're spread out. No negative connotations. They're not removed or separate from anything. Lullabot quickly adopted this term and we haven't gone back. We're a **distributed company**. ## What is the definition of a distributed company? Okay, so we're different than a company with remote employees. But let's draft up a definition and see if we can build some consensus. Picture a conventional company in a conventional office – cubicles and everything. Everyone is an employee. Everyone has benefits and job security. Everyone knows each other and works together and communicates in a tightly-knit environment. Now get rid of the office. Now spread those people out and allow them to live and work from wherever they'd like. This is a distributed company. Communication and culture need to adapt to accommodate this new modality. A distributed company is one in which the vast majority of employees work from wherever they are comfortable and productive. Perhaps most importantly, communication and culture are moved outside the boundaries of a physical location so that everyone is able to be included wherever they live. Although these companies may have a hub for in-person activities, this is not *the* office. Folks may gather at this location for specific events, but care is made that use of this space doesn't result in anyone being out of the loop. A distributed company is not: - One with a prominent central office where the majority of people work most of the time - A corporation with multiple locations - A company where people are often allowed to work from home - A company that takes advantage of outsourcing, freelancers, or subcontractors - A co-working space where people freelance on similar projects - A single freelancer - A department within a large company where most people work from home (for instance, the customer service branch of an airline) – though this may qualify as a *distributed team* ## How is a distributed company different from one with remote employees? The truth is that in-person communication and activities will always trump virtual/tele/digital communications. After all, humans have been communicating in-person since the beginning of time. It is our default setting. So a company with remote employees begins with the deck stacked against them. It's not an even playing field. It's really going to be a challenge to make remote employees feel like they're part of the team. Resentment and imbalance can build up between those who "get to stay at home" and those who "get to have lunch with the boss every day". A distributed company doesn't have these problems. Most of its communications happen online and are available for everyone in the company to participate. Whether these are phone conferences, email threads, or message-board and issue-queue posts, it's very easy to be inclusive despite geographical separation. ## Too exclusive? It may seem like I'm being exclusive with this definition. Remote work is on the rise and there are so many companies that want to jump on this bandwagon. However, I feel like Lullabot has been so unique – especially in the way that we handle our communication and culture – that I'm actually trying to to be more *inclusive*. I really want to find and connect with other companies who run the way we do. This is why we started the [Yonder Conference](http://yonder.io) for leaders of distributed companies and teams. I feel like the challenges faced by distributed companies and those with remote employees are different. There are different dynamics. These are different types of companies. So while I think we've got a lot in common, we should have different terms and different definitions for these different types of companies. Lullabot is a distributed company. Our employees are *distributed*. They are not *remote*. They're well connected. And they are exactly where they should be. 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: "MSNBC Video and DrupalCon Case Study" url: "/articles/msnbc-video-and-drupalcon-case-study" type: article date: 2014-05-29 updated: 2019-02-04 --- # MSNBC Video and DrupalCon Case Study # MSNBC Video and DrupalCon Case Study An impossible mission, accomplished By [ Jeff Robbins ](/about/jeff-robbins) May 29, 2014 In the past 8 years, we've had the privilege of working on some amazing high-profile projects. We helped extend the reach of Drupal, building some of the earliest household name Drupal websites including projects for MTV, Sony Music, FastCompany, WWE, Martha Stewart, Harvard University, and many more. Late in 2012 we were approached about what would become one of the most ambitious projects we've ever launched. Lullabot lead development of [MSNBC.com](https://www.ms.now/), a complete ground-up rebuild and reworking of the popular news site and online arm of one of world's most popular cable news networks. The network, which had started as a joint venture between Microsoft and NBC, had fallen back into the hands of NBC Universal. The domain had been redirected and there was no longer a website at MSNBC.com. Unlike most of the projects we work on, there was not a lot of legacy functionality to port to the new site. It was basically a blue-sky project – a complete from-the-ground-up build of the web presence of one of the biggest names in television news. Needless to say, we were honored and excited to be able work on this project. A large portion of the Lullabot team worked on the project for the better part of 2013 and the new site launched back in October. The project was not without its hiccups. But in our experience, it was one of the smoothest and most rewarding sites we've launched. To celebrate the project and showcase the site, we commissioned an interview video with the major stakeholders on the project. We're really proud of the way it came out. [Embedded Video](http://www.lullabot.com/media/oembed?url=https%3A//vimeo.com/90231426&max_width=0&max_height=0&hash=9wrl-W9Gdp3AZqI49YYjiZuaSKp5SZ822YZqk9w60zs) At DrupalCon next week, we will be presenting a panel session featuring Karen Stevenson, Jeff Robbins (that's me), and the MSNBC stakeholders. We will give an overview of the project and talk about the methodologies, technologies, and experiences which made the project a success. It should be both fun and enlightening for anyone who hopes to build complex Drupal sites. [The session will take place Tuesday, June 3rd at 10:45am (central) in Room 17 at DrupalCon](https://austin2014.drupal.org/session/msnbccom-mission-impossible) in Austin. If you'll be at DrupalCon, please come by and participate! If you miss the session, please stop by the Lullabot booth in the exhibit hall. We're a friendly bunch and we're always happy to meet new people. Published in: - [ Drupal Development ](/topics/drupal-development) - [ 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: "Receive Desktop Notifications in Chrome for Drupal.org Tests" url: "/articles/receive-desktop-notifications-in-chrome-for-drupalorg-tests" type: article date: 2014-06-13 updated: 2017-10-04 --- # Receive Desktop Notifications in Chrome for Drupal.org Tests # Receive Desktop Notifications in Chrome for Drupal.org Tests I discovered what an enthusiastic test refresher I am while at DrupalCon Austin. Here's what I did to make testing a much easier process for the compulsive refreshers out there. By [ Sally Young ](/about/sally-young) June 13, 2014 During the extended weekend sprints at [DrupalCon Austin](https://austin2014.drupal.org/), I discovered what an enthusiastic test refresher I am. When you submit a patch to the [Drupal core issue queue](https://drupal.org/project/issues/drupal), and even some contributed modules, a handy [testbot](https://drupal.org/automated-testing) will come along and ensure that your proposed code changes don't break any existing tests. Like most people, I found myself with lots of tabs open, compulsively refreshing each one to see if my test had completed or not. Getting JSON data out of things seems to have been the [theme for the week](https://groups.drupal.org/headless-drupal) for me, so with the help of [Jeremy Thorson](https://twitter.com/jeremythorson), we were able to add JSON feeds to [qa.drupal.org](https://qa.drupal.org/). After getting the data in place, I wrote a small [Chrome plugin](https://chromewebstore.google.com/detail/drupal-qa-notifier/fcgjigcnkbhpjdhimnhlieoplnpoojhi) that allows a developer to subscribe to tests on drupal.org, poll the test feed in the background, and display a linked popup notification when it has completed. ![Example of test subscription button on drupal.org](/sites/default/files/styles/wide_xs/public/field_regular_upload/screen_shot_2014-06-13_at_13.33.10.png.webp?itok=qQmj5Eot "screen_shot_2014-06-13_at_13.33.10.png") ![Example of qa.drupal.org test subscription notification](/sites/default/files/styles/wide_xs/public/field_regular_upload/screen_shot_2014-06-13_at_13.34.25.png.webp?itok=K-PtWfaM "screen_shot_2014-06-13_at_13.34.25.png") The source code for the Chrome plugin is available on [GitHub](https://github.com/Lullabot/drupal_qa_notifier), feel free to come and contribute! Published in: - [ Community ](/topics/community) - [ 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: "CSS Regression Testing with Resemble.js" url: "/articles/css-regression-testing-with-resemblejs" type: article date: 2014-06-25 updated: 2014-06-27 --- # CSS Regression Testing with Resemble.js # CSS Regression Testing with Resemble.js Automate the "visual spot check" to avoid embarrassing production bugs By [ Blake Hall ](/about/blake-hall) June 25, 2014 Roughly a year ago [Alex Sexton's Smashing Magazine article](https://www.smashingmagazine.com/2013/06/11/front-end-ops/) highlighted a new role emerging amidst the increasing complexity of front-end work in a responsive world: *Front End Ops.* Since that article was published, there's been an [entire conference](https://feopsconf.com/) devoted to the subject, and folks like [Chris Ruppel](http://rupl.github.io/frontend-ops/#/) have evangelized similar roles and supporting tools to the Drupal community. Sitting in [Ruppel's talk at BadCamp last year](http://2013.badcamp.net/sessions/frontend-ops) was my first exposure to the idea of front-end ops. I'm a big fan of automation and consistency; I'm also relentlessly tinkering with my local development tool chain to find ways to work more efficiently. Naturally, I was intrigued, and needed to learn more. It's an ongoing journey, as [front-end tooling is changing incredibly rapidly](https://www.youtube.com/watch?v=Yb6MQ14vGIk), but one tool I've recently completely fallen in love with is [Resemble.js](http://huddle.github.io/Resemble.js/). In a nutshell, Resemble.js takes two images, and generates a third that highlights the *differences* between the source images. Why is that exciting? With the right support tools, it allows us to do CSS regression testing via image analysis. I've created a simple example of the technique below, using our work on lullabot.com. In a pull request, I tweaked our sass to replace the site's Proxima Nova font with Helvetica Neue (typographic sensibilities be damned). Although it might seem innocuous, the change causes all sorts of havoc with spacing. In addition, my changes caused our rotating photo gallery to disappear from the rendered page. Below, you can see before-and-after versions of the site, with the production version on the left and the test server on the right. ![sample sites](/sites/default/files/styles/wide_xs/public/field_regular_upload/side_by_side_2014-06-12_12-24-59_2014-06-12_12-25-41.png.webp?itok=E2HUCk9T "side_by_side_2014-06-12_12-24-59_2014-06-12_12-25-41.png") The changes introduced by the pull request are fairly subtle if you're relying on manual comparison between browser tabs. A slightly different font, some spacing changes, and a missing photo gallery (rather than an obviously broken one) would be easy to miss for a developer doing a quick hit-and-run QA pass. Thankfully, using resemble.js, we don't have to rely on our typographic sensibilities to notice this regression. To demonstrate how easy it is to find these regressions once resemble.js has compared them, here is what the diff output looks like: ![sample diff output](/sites/default/files/styles/wide_xs/public/field_regular_upload/1440x1880_diff.png.webp?itok=OJZghK37 "1440x1880_diff.png") It's readily apparent that something is amiss. Taking full screen screen shots like this isn't exactly a best practice, because it is likely to yield false positives and hard to read diff images. However, it does a decent job at catching our attention when there are large scale issues. We're using this basic approach on several projects right now to speed up the QA process. We use resemble.js with PhantomJS to capture screenshots from a live site and an automatically-built test site for each pull request. These screen captures can be taken at any path, and any resolution we'd like to test. The resulting image files are then referenced in an automated github comment generated by our build tool, [Tugboat](http://tugboat.qa). This gives us a way to quickly scan a pull request to see if we've accidentally introduced any visual regression bugs. For more fine-grained visual regression testing, I'd recommend [Phantom CSS](https://github.com/Huddle/PhantomCSS). PhantomCSS, another tool from the folks at [Huddle](https://github.com/huddle), uses resemble.js under the hood for image analysis and comparison too. PhantomCSS can target specific page components with css selectors, making the tests more atomic and the results easier to digest. What's the easiest way to get started using resemble.js for your projects? I've written a [small command line utility in node.js](https://github.com/blakehall/resembles) to wrap up this functionality in an easy to use tool. Assuming you already have nodejs and phantomjs installed, you can make use of it by checking out the git repository and running `npm install` to grab the required dependencies. Additionally, in order to be able to configure the settings of the diff image, [a patch is required](https://github.com/kpdecker/node-resemble/pull/1) to integrate the latest resemble.js code. Screenshot comparisons can then be generated by running `node resemble http://example.com http://stg.example.com`. The actual [code that creates the images](https://github.com/blakehall/resembles/blob/master/resembles) is relatively straightforward, and weighs in at 130 lines of code. Using node.js to write small command line utilities like this is a fun and easy way to create and customize your front-end ops workflow. I'm looking forward to writing more small tools like this to help make all of our lives a little bit easier. 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: "Carving a Future for Responsive Images" url: "/articles/carving-a-future-for-responsive-images" type: article date: 2014-03-27 updated: 2014-06-27 --- # Carving a Future for Responsive Images # Carving a Future for Responsive Images By [ Kris Bulman ](/about/kris-bulman) March 27, 2014 It's happened to all of us—you’ve created an awesome website that looks fantastic on your hi-res display, and each one of your giant images looks as sharp as a tack. But, you know deep down this is all too easy. After inspecting the site with dev tools you find out the page weight is massive because of those bitter sweet images, and you just know mobile devices will never withstand it on 3G. So, you start looking into Responsive Image solutions, and find there are a [ton of approaches to research](https://css-tricks.com/which-responsive-images-solution-should-you-use/). Doesn’t it feel like there are too many options? Doesn’t each option seem to have some annoying limitation? Doesn’t each one just feel…hacky? Sure, you can run with one for a while, but is the one you chose really a viable long term solution? Will it scale up in a massive production environment? It doesn’t need to be this difficult. You’re about to learn that there is something you can do about this, and there are others with the same hardships that want to make your life easier. ## What's being done about this? Right now, responsive image solutions are all over the board, often with limitations attached to them such as redundant HTTP requests causing wasted bandwidth, lack of declarative hooks for image sizes, server side configuration woes, etc. This brings a real need to standardize the workflow and solve this problem at the browser level. The [Responsive Images Community Group](http://responsiveimages.org/) (RICG) is running an [Indiegogo campaign](https://www.indiegogo.com/projects/picture-element-implementation-in-blink) for developer support to get the picture element into Blink — the rendering engine for Chrome and Opera. [Mat Marquis](https://twitter.com/wilto), the chair of RICG, and developer [Yoav Weiss](https://twitter.com/yoavweiss) lead the campaign, and they’re willing to solve our responsive image problems with a little help. Until now, Yoav has been working for free in his spare time contributing to the srcset attribute and on the picture element for Blink. With him as a full-time dedicated developer on this task, we have a pretty good chance of having this feature get implemented in most modern browsers in a timely fashion. ## What are srcset and <picture>? The srcset attribute, already implemented in Blink and Webkit, provides a method of delivering different images based on criteria such as display density, connection type or user preferences. The picture element, not yet implemented, builds on the srcset attribute, allowing the grouping of multiple sources of images and serving them based on format, resolution, orientation, etc. Here's an example: ``` James Sansbury pleading with a plastic chicken. ``` ## What can we do? Two years ago this might have seemed like a pipe dream, but thanks to the work of the RICG, it's now a reality and browser vendors are considering viable solutions. As developers, we have the responsibility to do everything we can to move the responsive web forward. The end goal here serves all users of the web. As an individual I have contributed to this effort because I know what it’s like to work for free on something you’re passionate about, it’s time consuming and it's a noble task. Plus, how can you pass up that sweet t-shirt? As a company, Lullabot feels strongly about the future of the responsive web; as a duty to our clients, the front-end community and our developers, we’re donating $500 to the [Indiegogo campaign](https://www.indiegogo.com/projects/picture-element-implementation-in-blink). We encourage others in the community to do what they can to show their support; we will all benefit from having this sooner than later, and this campaign is how we achieve that goal. Published in: - [ Front-end Development ](/topics/frontend-development) - [ 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 Alley Effect" url: "/articles/the-alley-effect" type: article date: 2014-07-16 updated: 2014-07-16 --- # The Alley Effect # The Alley Effect Yelp And The Decentralization Of Business By [ Jeff Robbins ](/about/jeff-robbins) July 16, 2014 I had a really great meal today at a new restaurant called [Denden Café Asiana](https://www.facebook.com/Dendencafeasiana) in Providence. It's kind of out of the way – on a historical residential street that doesn't get a lot of drive-by traffic. It's not near any stores. It's kind of hidden away. I found out about it because it had amazing reviews on Yelp. I got a parking spot right out front. The food was amazing and it wasn't particularly expensive. I had a great meal in a nice little restaurant. There's a quiet revolution going on that might be easy to miss. Our discovery and recommendations of the things around us is changing. No longer does a great restaurant need to be located on a major street with a lot of foot traffic in order to be successful. Thanks to tools such as Yelp and Google, a great restaurant located in an alley is now just as discoverable as a crappy chain restaurant on the main strip. In fact by lowering their overhead, these restaurants can focus on quality and customer experience without needing to charge outrageous prices. Okay. This guy discovered Yelp. Big deal. Why should I care? This trend isn't just affecting restaurants. It's affecting all types of business. There are now discovery and recommendation services for lawyers, doctors, contractors, and lawn service companies. It seems like every hair salon has a Facebook page. While there may still be a certain prestige to certain streets in any given city, it is becoming less and less of a necessity for any small business to depend on foot or car traffic for discovery and success. Find a good space in a nice alley. Spend the money you saved on a nice website and a great quality product. I know this because I've been making a living from online discovery and word of mouth since 2006. Lullabot does not have an office on a major thoroughfare. Our team is entirely distributed. We don't actually have an office at all. Yet we're not difficult to find. It certainly helps that we're a web-oriented design and development company and people kind of expect to find companies like ours on the web. But as more people get in the habit of using services like Yelp, FourSquare, Google, Facebook, and Angie's List to discover a wider variety of businesses, there will be less and less reason to be centrally located. There might not even be a reason for many businesses to have an office at all. It's certainly worked out that way for us. 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: "Hiring Secrets Of A Distributed Company" url: "/articles/hiring-secrets-of-a-distributed-company" type: article date: 2014-07-23 updated: 2019-05-20 --- # Hiring Secrets Of A Distributed Company # Hiring Secrets Of A Distributed Company A Peek into Lullabot's Process for Finding and Retaining Talent By [ Esther Lee ](/about/esther-lee) July 23, 2014 In my experience as a human resources professional, Lullabot hires like nowhere else. Being a 100% [distributed company](https://www.lullabot.com/articles/what-is-a-distributed-company) means we have a few things working for and against us that traditional companies do not. In my capacity as Lullabot's HR Coordinator, I've helped facilitate the hiring of more than 50 people since 2010. Here are some things we’ve learned along the way. ## Culture I live outside of Las Vegas, Nevada (just Vegas to us locals) and am a huge fan of Tony Hsieh, the CEO of [Zappos.com](https://www.zappos.com/). You can’t drive ten miles here without seeing a “This mile is sponsored by ZAPPOS!” Adopt-A-Highway sign. Locally, Hsieh is known for changing the tune of the entire city. Nationally he is known as an advocate for culture. Hsieh says, “For individuals, character is destiny. For organizations, culture is destiny.” To Lullabot, cultural fit matters as much as a person's code quality, professional experience, or academic credentials—maybe even more so. After observing ourselves over the first few years of the company, we decided to write down our [values](https://www.lullabot.com/values). Those values are now a vital part of the hiring process and we’ve developed a variety of interview questions around each value to evaluate how well these values resonate with our applicants. It’s not that we’re looking for cookie-cutter hires. Quite the contrary—we are looking for candidates who embrace or can speak about these tenets of our culture in novel ways—no matter who they are, what their experiences or where they live. Keeping culture paramount in our hiring process means we get to share this journey with people we can relate to in some pretty fundamental ways. In his classic book \*Good to Great\*, Jim Collins writes: "For no matter what we achieve, if we don't spend the vast majority of our time with people we love and respect, we cannot possibly have a great life. But if we spend the vast majority of our time with people we love and respect—people we really enjoy being on the bus with and who will never disappoint us—then we will almost certainly have a great life, no matter where the bus goes. The people we interviewed from the good-to-great companies clearly loved what they did, largely because they loved who they did it with." I dedicate 40 hours a week of my life to work. I'm not sure I could do that if I didn't love each and every person I work with. Honoring our culture when we hire imbues our professional lives with meaning. ## Follow the Steps Over the past 4 years I’ve been at Lullabot, not every hire has worked out. With each “mistake” we’ve made, we’ve iterated on our formal hiring process so that we don’t fall prey to the same oversights again. Some examples, you say? Maybe we were initially charmed by an applicant and so tired of having a vacancy that we hired in haste, neglecting to adequately vet their work samples or set up a trial project. Or, in other instances, perhaps we didn’t do the full three interviews that each candidate normally goes through at Lullabot (screening, technical, executive). Over time, we’ve learned to trust our process and patiently follow the steps—video, peer review, three interviews, trial contract, references, etc. When we skip steps, things don’t always work out. We need to follow the process not just to protect ourselves but to protect the employee as well. What’s most important of all? Through trial and error, we have realized that the best way to gauge if someone is a fit for Lullabot is to actually work with them. There. Is. No. Substitute. Seeing work code samples, writing samples, marketing plans, etc. are essential, but nothing takes the place of seeing someone in action and working with them. For this reason, we like to create small contracts for possible hires (internal projects such as working on Lullabot.com work well). It not only helps us gauge whether their experience and personality fit in with our need, but also affords them an opportunity to see whether Lullabot is somewhere they’d like to work. ## Take Your Time From posting a job to extending an offer, it takes us a long time to come to a hiring decision. To quote our CEO & Co-Founder Jeff Robbins, “We are exceedingly slow at hiring. We do auditions, we run people through processes before hiring them. This is how Lullabot gets great people.” Hiring is one of the most important decisions a company can make, and firing is one of the worst things a manager has to do. Firing can really affect company culture and is not something we take lightly. Being a distributed company means that we can hire the best people we can find, wherever they may reside. We tend to post our job openings with the disclaimer that we take our time throughout the hiring process. We will wait to have a large pool of applicants to choose from before we pursue next steps. Robbins talks about how Lullabot is always hiring and never hiring. He means we can always try to find a place for a great candidate, even if we’re not hiring. Nevertheless, we’re not going to just hire to put bodies in seats to get a gig or meet a growth quota. We’re happy to be patient and wait for the right person to come along. ## Open Doors Once upon a time, there was a really great guy. Let’s just call him Brian. Brian worked in sales for a company that Lullabot regularly did business with. Lullabot always admired Brian’s charisma and the way he conducted himself. Lullabot wished they had a great sales guy like Brian, but didn’t want to jeopardize their relationship with his company by pursuing him. Lullabot decided that the best thing to do would be to just let Brian know they respected him and valued his work. Turns out, Brian admired Lullabot, too. When his company was bought out several months later, he approached Lullabot about a position. Lullabot hired Brian! Everyone lived happily ever after. Sometimes the best people already have jobs. While we don’t want to poach from our friends or competitors, we do feel like it’s important that every individual find the right fit for themselves. It may be that the person you want, even if they have a job, might be unhappy or looking for the next thing. At least reach out and have the conversation. Reach out and express your appreciation for their achievements such as a newly launched site you admire, or a blog post that hit home. Sometimes these conversations will yield nothing in the short term, but come back around a year or two later. ## It Comes Back to These As it is with all things Lullabot, we try to embrace our [Core Values](https://www.lullabot.com/values). I think this means that throughout the hiring process, we treat applicants with respect, dignity, and care in deference to our 'Be Human' value. I often reread my emails and make sure I’m conveying the feelings I mean to (friendliness, appreciation for a candidate’s time) and edit as I see fit. Communication throughout this lengthy process is important. I \*do not\* want an applicant to think I forgot about them, and I \*do\* want that person to know that if they want to check in with me, I’m happy to hear from them. ## In Summary Hiring processes should not remain stagnant. Since Lullabot is not a brick and mortar company, we aren’t able to follow many of the typical hiring practices. We’ve had the opportunity to create our own processes and continue to tweak them. Our admin team recently had a discussion about the necessity of a reference check, and the value it does/does not bring to the hiring process (I vote to nix them). Sometimes even though we follow the steps, a candidate still doesn’t work out. Working distributed can be hard in ways you don’t understand until you’re in it. Not everyone is cut out for it! Evaluating and changing the steps that you find work for you is part of the evolution of hiring. Staying true to the steps that work can merit success by hiring people that embrace your culture and add value to your team. Published in: - [ Business ](/topics/business) - [ Community ](/topics/community) - [ 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: "How Working at Home Works (For Us)" url: "/articles/how-working-at-home-works-for-us" type: article date: 2013-03-06 updated: 2019-03-08 --- # How Working at Home Works (For Us) # How Working at Home Works (For Us) Lullabot is a 100% distributed company: here's how we've learned to keep things running smoothly! By [ Esther Lee ](/about/esther-lee) March 6, 2013 ## In the beginning When I tell new acquaintances that I work for a distributed company, they inevitably ask: “You do all of that from home? *How?*” We discuss this *how* on a regular basis, but haven't taken these conversations outside our figurative halls. In this article, all will be revealed! Lullabot is a distributed company with clients all around the world—we don’t have a single office and almost all of us work from home. You might think that means we’re disconnected (not at all!), that we are lonely (never!), or bored (ha!). You might even think we’re nerds! You’d be right on that count, but that’s irrelevant to our working arrangements. Being distributed requires a more deliberate approach to communication and getting “in the zone.” Here are some of the highly effective habits of our intercontinental team: ### Get dressed like you’re going to work Nine out of ten Lullabots agree (this is a made-up statistic, but keep reading) that their productivity is boosted when they prepare for the day *as if they’re leaving the house to go to work.* That means pants! And shoes! You don’t have to bust out a suit, but readying yourself for a purpose helps you get into a focused work mode. (The fact that you don’t actually have to leave the house just makes it a little bit happier.) ### Have a schedule To paraphrase our Director of Operations, Seth Brown: “Find your ideal schedule or pattern for productivity. For me, it involves working 8 to 6 with two hours for a long lunchtime workout. This restores me. For someone else, it might be about starting work at 10, having a nap in the afternoon, but working in the evening when they feel most productive. The key is finding your groove. We’re all unique. There are morning people, evening people, and everything in between. We have the flexibility to choose, but it’s important to be consistent with that choice so others can plan around you.” Getting up early is the most productive time of the day (by far) for Karen Stevenson, Senior Drupal Architect. Karen's daily schedule: get to work early (usually around 4 a.m.), get done early and enjoy a walk or some family-time, and come back later if need be. For Sean Lange, a front-end developer, having set work hours and a daily routine is what works. The importance of a schedule seems to be key for all of us, no matter how offbeat that schedule might be. Have an idea where your time will be spent each day, stick to a pattern that feels comfortable and repeatable, and let your team members know when you’ll be in your groove. Otherwise, time tends to slip away and you find yourself “working” all the time with reduced productivity. ### Draw a boundary As I was doing interviews for this article, many Lullabots mentioned the importance of boundaries. Senior Drupal Architect Jeff Eaton argues, “The valuable thing about getting dressed like you’re going to work, and similar rituals, is that it puts a distinct dividing line between schlepping around and serious business. Because so many of the ‘Bots work on things they find interesting and are passionate about, it’s easy to stay half-connected 24/7. I’ve found that explicitly disconnecting makes it easier to focus when I *am* connected, regardless of the mechanism used to draw that dividing line.” Angus Mak, Developer, says: “I never run straight to the computer after waking up. I always take some time to make coffee and let the dog out, so I don’t feel like I’m working before I’m even awake.” ### Have a dedicated workspace (and/or device) Having a home office is crucial for me. I need a desk with a proper chair to really direct my attention to work projects. Otherwise, I fear I’d be floating about the house, distracted by home duties and shiny objects. Having a dedicated workspace helps keep me organized and efficient. Jeff Robbins, Lullabot CEO & cofounder agrees: “It’s good to have a home base where both you and your family know that you’re working. Not only is this a good physical, personal reminder, but it also sends a message (Don’t bother Dad!) to your family without needing to interrupt your work to explain it. Working at home is living at work. Do what you can to differentiate work and leisure time.” “I also use a different computer with nothing work related on it for entertainment,” says Angus Mak. “When I’m done with work, I switch to my personal computer. I still check work emails on the phone, but at least I don’t see any work related files, programs, code, etc. on my personal computer that might suck me back into work.” If a separate computer isn’t in the cards for you, you can create separate accounts on your computer. Using one for work, and personalize the other one with your home life in mind. While we’re talking about work devices, Jeff Robbins advises: “Spend the extra money for a quality microphone headset. If you work virtually, it isn’t just an adjunct communication device. It’s how you’ll be hearing all of your clients and colleagues, and it’s also how they’ll be hearing you. There’s nothing worse than being that guy that people can’t quite understand on the conference call. Never use speakerphone or your computer’s built-in microphone on conference calls. Ever.” ### Be active If there’s one thing we’ve learned, it’s that Lullabots can “walk the talk," or at least, walk *while* they talk. I was surprised to hear how many of us walk, hike, or stand when we’re on long phone calls. According to Seth Brown: “Walking on phone calls is critical and gives me the endurance to get through the day. When I’m in front of the computer I don’t listen well—there are too many distractions.” Karen Stevenson advises, “Stand up while you’re on phone calls, at least the important ones.” Not only does this keep us focused on the phone call (instead of being distracted by other online tasks), it keeps our bodies and minds alert during a time when our attention could easily wander. Another great piece of advice comes from Senior Drupal Architect Andrew Berry, “I try to keep snacks and drinks stored *away* from my office. Otherwise, it’s too easy to stay sitting at the desk all day: snacks are just a long reach away from your desk. Keeping them elsewhere means an extra minute or two, but also gives your eyes and body a rest.” “I love going for a walk in the middle of the day,” says Matt Westgate, president and cofounder. “I usually reserve the hardcore gym exercise until the end of the day to release stress. My usual transition from work life to personal life is to prepare dinner while I’m having an end-of-day wrap up with Jeff or Seth. Cooking is a creative outlet for me, and it has the byproduct of being (usually) delicious!” ### Get out Leaving the house during a workday is often a good idea. A coffee run, a class at the gym, or some errands around town can help relieve cabin fever. If you’ve ever had the experience of working at an office, you know how relaxing a lunch away from the office can be. The same principle applies when you work at home! Take a step away, focus on something you need to do in your personal life, reboot, and return. Jeff Robbins advises: “If you really want to bear down and work, get out of the house. Go to a cafe, get a big caffeinated drink, and put on headphones. The peripheral activity of the staff and other patrons energizes me. Because I don’t know anyone there, I stay isolated and can really focus on my work without distraction. Their chairs usually aren’t as comfortable as mine, so I don’t stay there all day, just a couple of hours. Then I can head home and appreciate my comfortable chair that much more!” ### Use being at home to your advantage Just because you’re working doesn’t mean you have to forego the comforts of home. Do the things you can’t do at an office -- put on some music, work from the patio, or open the windows! Jared Ponchot, Creative Director, has his afternoon relaxation time down. “I try to schedule some sort of relaxing activity for the afternoon. I typically hit a wall between 2pm and 4pm, so I try to listen to that and either take a walk, go play with my kids, make a cup of tea, or do something for at least 15-30 minutes that’s completely relaxing. That moves all my brain power from my prefrontal cortex back into a more balanced right/left brain, I think.” Having multiple places to work in your home is another advantage over a typical office. Jerad Bitner, Senior Technical Project manager, mixes it up between a traditional desk, treadmill desk, a lazy boy, and a standing desk. “Sometimes I like the couch,” he says. “It’s a great way to help prevent repetitive stress injuries, which often result from working long hours in the same position.” ### Shower It surprised me just how many Lullabots mentioned the shower when I was researching this article! From Nate Lampton, Senior Drupal Architect, “In the midst of all the phone calls, naps, and lunch, I usually take at least one shower in the middle or end of the day. To me the shower is the absolute *most* productive place in my entire ‘office.’ There’s no problem so difficult that it can’t be solved with a good shower.” Jared Ponchot also had an opinion on the shower. “I fully relate to the idea that being showered and dressed before work can help orient one’s self. However, I’ve actually changed my routine, intentionally waiting to shower until late morning or midday. I’ve found that it’s a powerful tool for ‘creative pause’ and cranking alpha waves. I want it to disrupt my ultra focus, a zone I get into when I’m working on for more than a few hours. There are so many times when I scramble to get out of the shower because I’ve suddenly understood something or had an important idea during that break.” ## Work. Rinse. Repeat. ### Be interactive (in work and other aspects of your life) “Connecting with people face-to-face during the day is really important” says Matt Westgate, and many other Bots agree. “That connection could be family, a local group, or going to the coffee shop or the gym. I find those face-to-face connections remind me that I’m *also* making human connections when I’m on the phone or writing email. It helps me better empathize.” Being interactive during work could mean contacting a client or colleague using videochat via Skype, Google Hangout or GotoMeeting rather than shooting off an email. Lullabot uses several means of communication for our team, but nothing feels quite like face-to face time. Outside of work, “it’s important to talk about non-work things, to make up for the social interaction you’re losing by staying at home,” cautions Karen Stevenson. It would be incredibly easy to turn into a hermit. Many Lullabots recommend becoming involved in a community or interest group outside of home. “The biggest thing that helps me keep the balance is being in a group.” Angus Mak continues, “I train dogs a few times a week at a dog club, and that forces me to leave the house at 6 p.m. Having something outside of work that I am passionate about really helps.” “If you’re a designer working from home, you need to join some sort of group in your local area and force yourself (against your will if you’re wired like me!) to go hang out at their events,” says Jared Ponchot. “I’m a part of the Atlanta Web Designer group. I’ve had to miss it a few times and keenly feel the loss.” Speaking of family, it can be hard to work at home with family present, as they are (and should be) Priority Number One. Finding the work/life balance in regards to family is something that I’ve had to work hard at. It’s helped to get to a point where I can say firmly, “Right now I am working!” and stick to it. After work, if I’m as diligent giving my family attention as I am at giving work attention, we all feel good. This is also an important issue for Seth Brown who has three young daughters. “For me,” he says, “the act of shutting my door is symbolic. It says, I’m at work, don’t bother me. But if a child falls off a bunk bed or there’s some other emergency, like sick kids at home, you have to step out of work mode. I feel like it’s a blessing and a curse. It’s a blessing to be there for your family when they really need it, but sometimes it can come up at the most inopportune times. It's stressful trying to listen and participate in a conference call while trying to calm a crying child.” ### Work at NOT working Working from home with a distributed team comes with a few challenges that wouldn’t exist in an office. Different time zones, different work schedules, and spotty Internet connections can all work against you. The biggest issue seems to be the same wherever we are: the tendency to overwork. How can you leave work at work when you work from home? Blake Hall, Senior Developer: “For me it’s always key to remember (and sometimes force myself) to carve out time where I have no Internet access. I inevitably get sucked into something interesting (even just Yammer) if I don’t.” “It’s easy to get me to read an e-mail or message,” explains Nate Lampton, “but I’ll only respond when I’m at my desk. The variability of my days means that I always schedule phone calls and meetings in advance, preferably not the same day. I usually don’t accept direct phone calls and I use conference lines or Skype as much as possible to prevent clients or even co-workers from using my phone number and breaking that scheduled time.” ## Now, for the summary The end all, as they say, is simple: don’t be afraid to try new things, even if they seem odd. Don’t be afraid to explore, find the schedule and best practices that work for you, and work that way. There’s no “right” or “wrong” way to work from home, if it works for you. I think James Sansbury, our Development Manager, said it best: “It’s working from home, not homing from work, so the point everyone’s making is to create a clear line for what work is and what home is. If that means you get dressed before going to work, get dressed. If that means setting specific work hours, set those hours. If that means taking a long lunch, take a long lunch. The easy part is determining those things; the hard part is actually doing it. DO IT. DO IT!” 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: "Yahoo, Best Buy, and Telecommuting: Advice From A Distributed Company" url: "/articles/yahoo-best-buy-and-telecommuting-advice-from-a-distributed-company" type: article date: 2013-03-27 updated: 2014-07-31 --- # Yahoo, Best Buy, and Telecommuting: Advice From A Distributed Company # Yahoo, Best Buy, and Telecommuting: Advice From A Distributed Company What can office-based companies learn from Lullabot? By [ Jeff Robbins ](/about/jeff-robbins) March 27, 2013 As you may know, Lullabot is a completely distributed company. Most of our people work from their homes all across the U.S., Canada, and Europe. We have an office in Providence, RI near where I and Lullabot's other co-founder, Matt Westgate, live. However none of us work from that office as our primary location. We've found ourselves to be much more productive working from home or wherever we can get a connection for our laptop and mobile phone. Over the past 8 years we've had almost 75 employees and we've worked with many Fortune 500 companies. The walls of our office are covered with logos of companies like NBC Universal, The Grammys, Turner Broadcasting, MTV, Sony Music, Harvard University, and more. We use the space for meetings, meetups, and workshops, but we don't work there. There's been a lot of talk recently about Marissa Mayer's memo requiring Yahoo's remote workers to relocate to company facilities. Now Best Buy, a company whose logo is also on our wall, has jumped on board and started reeling in its telecommuters. I've felt obligated to weigh in, and many of my friends and coworkers have been pushing for some defense of remote working. However, I've been struggling a bit to come to terms with my feelings about this. The truth is, I'm just not sure this is a bad thing for Yahoo or Best Buy. ## Alienating Remote Employees Certainly it's a bad thing for the employees. And Lullabot would [love to hire](http://jobs.lullabot.com) some of those talented, self-motivated, Yahoo or Best Buy programmers, designers, and project managers who want to continue working from home. We think we can make them happier and more productive. My feeling is that most conventional co-located companies simply don't know how to manage, and more importantly, how to *include* their remote workforce. It creates a situation which sucks for the company and sucks for the employees. Acknowledging the hubris in quoting oneself, I have a saying that I like to use: > “A conventional company with several remote employees is a company with several alienated employees.” This discussion isn't all about productivity. It's also about culture, relationships (both romantic and platonic), understanding office politics, in-jokes, birthday parties, and general inclusion. Without these things, a company's work-at-home staff won't feel like they're part of the team. They may feel too disconnected to surface concerns about a project early. They may not understand the politics of a situation well enough or feel included enough to make suggestions for the direction of the project. They may not even think about the project as a whole as opposed to their role within it. Feeling alienated sucks. These employees can become myopic, focusing only on the work that comes to them via email and nothing else. For many remote employees, there will always be the feeling that there is a "mothership" and they're not on it. Do you know how excited people get when a birthday cake gets brought into the office? Perhaps a little too excited? Well the remote employees aren't getting any cake. I've seen remote workers drive great distances and carefully coordinate an in-person meeting or two so that they could be there for the cake party. It sucks not getting cake. The truth is that they're missing out on a lot more than cake. They're missing out on stories and in-jokes, engagement announcements, and new babies, not to mention office drama and politics, rivalries, and conflicts. While some of this might seem like a relief, it all adds to a feeling of disconnectedness. It leads to unproductiveness and unreliability. Vice versa, for office-based employees who might spend two hours a day or more commuting into the office, it can feel like the remote workers are cheating. They're cheating because they don't need to sit in traffic. Maybe they're even cheating on their hours. Are they even putting in a full work day? Is their work really taking as long as they're saying or are they sitting around in their underwear playing XBox? This paranoia and resentment can really build up over time. Companies can end up with factional splits between the in-office and remote workers. Nasty business. ## The Difference Between Remote And Distributed Perhaps I need to make a distinction between "remote" and "distributed" here. In my mind, "remote" workers are remote to something – removed – from the center of activity. Things are happening somewhere and they're not there. By contrast, "distributed" workers are simply spread out. There's no implication of a center of activity. The activity is distributed across the team. So am I saying that remote workers are a bad idea? To be clear, I am not. I'm simply saying that a company who wants to add remote workers should probably learn a thing or two from distributed companies like ours. They need to figure out how to avoid the cake problem. They need to figure out how to avoid the cheating-on-work problem. They need to figure out how to avoid splits between the in-office workers and the remote workers. ## Evenly Distributed Being distributed, Lullabot has no mothership. Yet, there still needs to be a center of action, doesn't there? A canonical place where work happens? And in a distributed company the office functions need to move into the virtual realm. They move online. We can see each other "at work" because we can see each other logged into our chat room. We can see each other posting and commenting on our microblog. We can see the results of our work as we post progress on our project trackers and code repositories. Our meeting rooms take the form of telephone conference lines. We have frequent meetings to check on progress of our projects. Management has frequent calls to discuss company directions and ideas. We brainstorm a lot. We have twice weekly meetings where every person at the company gets 2 minutes to talk about basically whatever is on their mind. It's a level playing field. No one feels alienated. We're all using the same tools for communication. We all get access to the same information in the same ways. We're all equally connected. ## How It Works For Us As a fully distributed company, it's built into our DNA to avoid remote worker alienation. We bend over backwards to make our team feel connected and involved in the company. Being a good proactive communicator is a requirement for any job at Lullabot. And our company's infrastructure is built around facilitating many different types of communication. We can easily and quickly see who's working at any given moment. We can easily get quick answers from anyone on the team whether they're online or off. We can post questions company-wide for discussion. We spend a lot of time on conference calls, but people are often multitasking and we rarely feel like a meeting was unproductive. Most new Lullabot employees say that they feel more connected, involved, and supported at this distributed company than they ever did at an office-based company. All communication has to happen clearly, and explicitly. Very few of our interactions are implied. We usually opt for over-inclusiveness, cc'ing liberally to make sure that even people peripherally involved with a project can keep track of what's going on. Positive feedback is also clear and explicit, and without co-workers in adjacent cubicles, can also be more focused without making others jealous. We celebrate birthdays online. We meet new Lulla-babies online. (There have been a lot recently!) We provide sympathy when employees get sick or have hardships. We have weekly "post a picture of..." threads and people have posted pictures of their workspaces, the view from their desk, and had impromptu facial hair photo contests. We celebrate engagements. We gossip. We talk about the weather. We complain about difficult clients. We appeal to the company "hive mind" when we're stuck or lost. We link to funny videos. We turn each other on to books we're reading, new technologies we're exploring, and must-read blog posts. We inspire one another. Our company is the best social network I've ever been a part of. One of the 'bots lost 110 pounds in the past year and it was really wonderful to see the support and encouragement he got from the rest of the Lullabot team. This communication is explicit. He posted, "I've lost 50 pounds so far," along with a picture of himself. And everyone cheered, posting words of celebration and encouragement as comments on his post. Later he posted at 75 pounds and so on receiving similar support from the team. If we worked at a co-located company, I don't know if the emotional support would work like this. Would he post a company-wide email? That seems a bit extreme. Would he try to work it into conversation when he could? Would coworkers who didn't know him very well watch him getting thinner and thinner and wonder if they should comment? In some ways, the conventional in-person communication inhibits the type of support we were able to give him. He explicitly posted (paraphrasing) "I'm losing weight. Celebrate with me!" And we did! ## Face To Face Don't get me wrong, there is a lot to be gained from in-person interactions. Telephone and online communications may have their advantages, but our pre-verbal monkey brains are hardwired for face-to-face communications. Beyond spoken interactions, our senses are well honed for things like body language, tribal authority hierarchies (a.k.a. office politics), and even the personality messages that people project with their clothing style. And "sense of humor" isn't just a saying – it's critical for understanding the nuances of productive, friendly, and respectful interactions. This stuff is really important for understanding how to communicate with people and how to understand the messages they're sending in the virtual world. The guy with the dry sense of humor may just seem like a jerk over the phone. But then you meet him in person and with a wink you realize he's actually a comic genius. We like to start off most of our client engagements with an in-person meeting. It really helps get the communication off on the right foot. It also adds a level of formality and clear commencement of the project. "We're here. We see you. You see us. Let's all get this project started and do a great job." Distributed communication becomes a lot easier after these meetings. ![Lullabot Wall](/sites/default/files/styles/wide_xs/public/u2/IMG_4129-bw.jpg.webp?itok=n_I8-znd "IMG_4129-bw.jpg") We do a lot of traveling to meet with our clients and with each other. We get together in exotic locations to work on important projects. We go out to dinner together and we bond. We may not eat cake together, but we hang out. We laugh. We eat food. We establish strong face-to-face relationships which we can become the basis for our online interactions. Each person in Lullabot will go to two company retreats per year – one for their department and one for the entire company. Company retreats seem to be a common device for almost all of the distributed companies I've talked to. In-person interaction is important. It bonds people together. But it's not needed every day. It's not even needed once a month. We can get together, connect on a personal level, get comfortable with one another, then build these relationships online. ## The Distributed Company Guy Sides With Marissa… Kind Of While it may seem like a wonderful convenience and even a perk for conventional office-based companies to let employees work remotely, it's not as easy as enabling a VPN and sending employees home with a laptop. Culture, communication, morale, and feelings of equitability can erode very quickly while no one is watching. And from a bottom line perspective, once these things have eroded, the work-at-home employees will become less and less productive. Remote workers don't want to feel like they're sneaking around and slacking off. But if they are being isolated and essentially pushed out by others at the company, they will fall into this pattern because they won't know what else to do. There is a lot that office-based companies can learn from distributed companies like Lullabot. Remote workers need to be supported. They can't feel like they're missing out, shielded, or disconnected from the company. The solutions to these problems extend from decisions around communications technologies to basic company cultural decisions such trusting workers to self-direct, committing to a results-oriented work environment (ROWE), and committing to an open communication style where the day-to-day affairs of the company are more widely shared. Companies can't assume that it all evens out because remote workers don't need to commute. Office-workers get cake. Remote workers can slack off on their hours. A feeling of alienation is not a fair tradeoff for working from home – not for remote workers, not for their office-based counterparts. Remote workers can't be second-class citizens. They need to be just as involved, immersed, and woven into the fabric of the company as everyone else. If companies aren't going to spend time to understand these things, they will alienate their remote employees. And once they've done that, they're probably right to shut down their telecommuting policies. The truth is that technology is allowing us all to communicate better and be better connected wherever we are. High definition videoconferencing and screen sharing will soon be ubiquitous and we will all get over the social uncomfortableness which currently encumbers these technologies. Remote employees will be less remote. There will be more and more ways to be together without actually being together. It's progress. It's the future. Some companies will get it. Some won't. For the companies that don't get it, they’re better off not pretending. Do it right or don't do it at all. 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: "Communication for Distributed Teams" url: "/articles/communication-for-distributed-teams" type: article date: 2014-08-27 updated: 2014-08-27 --- # Communication for Distributed Teams # Communication for Distributed Teams The Secrets to Staying Together, Apart By [ Jared Ponchot ](/about/jared-ponchot) August 27, 2014 With the birth of the internet, and especially since the early days of open source projects (meaning before the term "open source" was used to describe them), developers have been working together on specific projects as distributed teams of people. In some cases they formed passionate communities, all devoted to a piece of software. Years ago, one of Drupal's taglines was actually "Come for the software, stay for the community." Odd, right? Those two things don't seem to go together to me; almost like "come for the toilet paper, stay for the delicious food." Nevertheless, open source projects truly have thrived and even created communities of people that have relationships that go beyond the software they're building together. Rather amazing if you ask me! When I joined Lullabot back in 2010, I'd never been a part of an open source project before, nor had I ever worked for a fully [distributed company](https://www.lullabot.com/articles/what-is-a-distributed-company). The whole notion of the latter seemed novel and a bit frightening to me. Over time, I've realized that some of the basic philosophies and methods that open source projects use for communication apply directly to distributed companies. This isn't a huge surprise, especially for distributed companies that are doing software development using open source software. But what about other disciplines beyond development? Do the same rules apply, for example, for teams doing design or strategy work? Before I tackle that question and dive into the specifics of distributed design teams (which I'll do in a follow-up article to this one), I want to first lay out the foundations for communication in distributed teams. These are the things that seem to apply across the board, regardless of discipline, and the things that have helped Lullabot foster great communication within our distributed team. ## Lessons from Open Source You might sum up the lessons you can learn from the way open source projects communicate into these three principles or rules ... - **Write liberally** (asynchronous communication) - **Chat frequently** (synchronous communication) - **Congregate occasionally** (in-person communication) ### Write liberally People involved in open source software projects do a lot of writing. They create "issues" in written form that describe problems they want to solve, they hash out ideas in those same issue queues with groups of people, they document the work they do and the software they're creating, they write articles and blog posts about the things they're passionate about ... they do lots of writing! One thing that's worth noting about written communication is that, with the exception of the annoying tv news ticker, it's primarily an asynchronous form of communication. One individual writes something, another individual can read it at their convenience, and then write something in response if they so desire. Being a distributed team of people with members in various time zones makes it highly advantageous to find ways to leverage asynchronous communication. Distributed teams *have* to be more intentional about writing things down than do collocated teams, which is quite often an advantage. Having spent more of my career working with collocated teams than distributed teams, I can say from experience that collocated teams can easily take written communication for granted, and thereby suffer. The more you write, the better you get at it, and I truly believe that people who write well, think well. When teams are forced to be more intentional with describing their problems, solutions and work in written form that often leads to more thoughtful assessments, solutions and products. > "... being a good writer is about more than clear writing. Clear writing is a sign of clear thinking. Great writers know how to communicate. They make things easy to understand. They can put themselves in someone else's shoes. They know what to omit." — Jason Fried Nevertheless, writing liberally can *create* problems as well as solve them. While lots of documentation can prove helpful to the newcomer in a project, as that builds up, it can go from information firehose to ocean, terrifying those newcomers floating on top. This is because written word is something to react *to*, rather than something to interact *with*. It can prove to be really efficient and invaluable for reporting, but REALLY poor for efficiency of interaction. ### Chat frequently Since asynchronous communication can be very inefficient for things like conversational problem solving, open source project teams have also been unafraid to pick-up the phone, jump on Skype or Google hangouts, or hop in a chat room. One of the biggest lessons I've learned from open source (and from working at Lullabot) about doing distributed teams well is paying attention to, and being intentional about asynchronous vs synchronous communication. While it's nice to think that everyone can get more done if we avoid calls and meetings and just keep everyone else up to speed via email, basecamp, github, or \[insert your project management tool of choice here\], reality is a bit different. Every person is different in the way they learn, think, process, and produce. Some are visual, some are auditory, some are kinesthetic and almost all are a mix of those in one way or another. Even visual learners often benefit greatly from simply talking things out. At times, assessing problems and reaching solutions can happen MUCH more quickly through synchronous conversation. Being intentional about synchronous communication in a distributed team means putting regular time into your team's schedule for it. You can't rely on poking your head into someone's office and getting your quick answer to keep your work moving along, you need to create those spaces. ***Tip:** As teams get larger, things like phone calls can be more challenging to manage. Years ago Lullabot developed a fun method for creating clarity on our larger team calls. When a team member has finished with their turn, they simply say "tada!" This lets the call leader know that they're done so that they can call on the next person, avoiding the accidental talking over people that is so common on larger calls.* Synchronous communication can also happen in the form of written word (not just spoken) via internet chat. Whether you use [IRC](https://en.wikipedia.org/wiki/Internet_Relay_Chat), [Slack](https://slack.com), Google chat, AIM, or some other tool, internet chat can help create that ambient availability and accountability that exists for collocated teams. It lets you know who's "in the office" right now, and also gives you a way to quickly poke your head into someone's office to ask them a question or just say hello. Developing some ground rules and etiquette around this is key in order to protect productivity. Most designers, for example, appreciate not being stopped every 20 minutes while they're working on particular task types. Simple things like pinging someone and awaiting a pong provide people the space they need to get things done. In many ways, this sort of thoughtful approach to ambient availability could help most collocated teams. Poking your head into someone's office becomes awkward if they need ten more minutes to stay heads down in whatever they're trying to complete. There's nothing awkward about pinging someone via web chat and waiting ten minutes for them to respond. Again, it's important to have a solid understanding of the advantages and shortcomings of each mode of communication so that you can make good choices. Brainstorming or creative collaboration, for example, requires synchronous communication, however internet chat often is not a great tool for this. When you need to think out a problem or generate energy around some ideas, setup a time to hop on the phone or some form of video conference. Also, emoticons aside, written word lacks tone of voice. For thorny conversations, use things like email and chat to schedule a call, not work through the problem. ### Congregate occasionally Even open source communities have regular or at least occasional events where most or all of the community gets together in the same location. People fly from all over the world each year to attend DrupalCon. It's worth noting that the really important work that often happens at these events is the *strategic*, and larger problem-solving work. Initiatives that have a clear strategy and plenty of energy can be tactically carried out by a distributed team using the means and methods described above, but there are other HUGELY important things that simply don't happen easily without people together in the same room. Some examples include ... - **Assessing and analyzing problems** - **Developing strategy** - **Building alignment around a singular vision** - **Building relationships** - **Resolving difficult conflicts** - **Energizing and/or re-energizing a team** For these kinds of tasks, there are great advantages to getting people together in the same room. At Lullabot, we wind up flying groups of people to the same place quite often. Some of the examples of things that we do this for include ... - **Kicking things off** (At the beginning of each project we fly the project team to the client location for about a week of workshops.) - **Working on our work** (Focused teams within Lullabot like admin, managers, or design and development have annual working retreats to grease the gears, develop strategies, improve processes, and build relationships.) - **Working on our team** (Once a year we fly the entire company to one place for a week to enjoy each other, work on the company as a whole, and simply have fun together.) - **Working on our business** (Once a year Lullabot's top leadership get together for a week of strategic thinking, vision casting, and unity building.) No matter how far technology takes us, humans are social animals and made to be together. Non-verbal communication is immensely important for creating and nurturing relationships. While distributed teams and companies can be extremely effective while apart much of the time, those times together in-person are invaluable! Published in: - [ Business ](/topics/business) - [ Community ](/topics/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: "And The Emmy Goes To…" url: "/articles/and-the-emmy-goes-to" type: article date: 2014-08-26 updated: 2023-05-09 --- # And The Emmy Goes To… # And The Emmy Goes To… Lullabot Project Wins Emmy Award By [ Jeff Robbins ](/about/jeff-robbins) August 26, 2014 Last year, Lullabot helped NBC create the digital experience for [The Tonight Show Starring Jimmy Fallon](https://www.nbc.com/the-tonight-show). We're happy to announce that the project has won the 2014 Primetime Emmy in the category of [Outstanding Interactive Program](https://www.televisionacademy.com/awards/nominees-winners/2014/outstanding-interactive-program)! We couldn't be prouder. We'd like to congratulate everyone who worked on the project, including the team at [NBC](https://www.nbcuniversal.com/), [Crispin Porter + Bogusky](https://www.cpbgroup.com/), and [Four Kitchens](https://www.fourkitchens.com/). One note for those interested: The Tonight Show website uses a decoupled architecture to help create a stable backend content repository while allowing for ultimate flexibility on the front-end. Lullabot's Andrew Berry presented about this project at DrupalCon Austin back in June. You can find [a video of that presentation here](https://www.youtube.com/watch?v=6eJj5UrUUpU). 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 the front end with CasperJS" url: "/articles/testing-the-front-end-with-casperjs" type: article date: 2014-03-12 updated: 2014-10-22 --- # Testing the front end with CasperJS # Testing the front end with CasperJS Keep your website rock solid by testing it with CasperJS, a JavaScript utility that makes writing tests a joy By [ Juampy NR ](/about/juampy-nr) March 12, 2014 ## Background After the first few releases of [MSNBC](https://www.ms.now/), we found we needed a way to check that the core parts of the system were working as expected, and that no [regression bugs](https://en.wikipedia.org/wiki/Software_regression) crept in to the system. We started writing tests to cover the most critical and complex parts of our codebase. Ideally we'd have been writing tests from day one, but sometimes we have to wait until the project matures to justify it to the client. ## Choosing a testing framework We agreed on the following requirements for a testing framework: - We wanted everyone in the team to write tests for the tickets they were working on. - We did not want the learning curve to be too steep. - We wanted to focus on testing the front end (we have a [considerable amount of logic decoupled with AngularJS](https://www.lullabot.com/articles/move-logic-to-the-front-end-with-angularjs)), although we also needed to test the back end. - Our tests would simulate a user navigating the site ([functional testing](https://en.wikipedia.org/wiki/Functional_testing)). We wouldn't write [unit tests](https://en.wikipedia.org/wiki/Unit_testing). At least, not in the short term. - Tests would run on local, development and testing environments. These environment's databases get wiped out daily with a [sanitized](https://en.wikipedia.org/wiki/Sanitization_(classified_information)) copy of the production database, so it is safe to run tests there. ## First attempt: Behat Given those requirements, we discarded Drupal's [Simpletest](https://drupal.org/simpletest) because it is not the best tool to test JavaScript. Our first choice was [Behat](http://behat.org) + [Mink](http://mink.behat.org). We started writing [features](http://docs.behat.org/quick_intro.html#define-your-feature) and custom steps; and configured our Jenkins server so it would run tests against a headless Chrome browser when a GitHub Pull Request was merged in. *Headless* comes from the fact that there is no display since the browser runs in a server. Using Behat achieved some momentum for a few weeks but we found the following pain points: - Some feature steps were difficult to write. specially the ones involving JavaScript. Having to write JavaScript code embedded in PHP code was a tricky task. - Tests would take a very long time to complete and sometimes fail because of [race conditions](https://en.wikipedia.org/wiki/Race_condition). We had to add `wait(10000)` statements everywhere to make sure something like an image was loaded at a certain point while a test was running. - The team struggled to write Behat features and custom steps. ## Meet CasperJS [CasperJS](https://wallpapers.com/cartoon) is *a navigation scripting & testing utility for [PhantomJS](https://phantomjs.org/) and [SlimerJS](https://slimerjs.org/) written in Javascript*. The fact that seemed most appealing to us was that **tests were written in JavaScript**. For us, it felt just like opening the browser console and typing JavaScript commands to check if a variable is present, or a CSS3 selector returns the right result, or a triggered event would return the right data. We also found out that it was [very easy to install](https://wallpapers.com/cartoon). We starting by porting our Behat tests to CasperJS and immediately realized that PhantomJS (the browser that CasperJS uses) was way faster and that running these tests in a Continuous Integration fashion required a simpler setup at our Jenkins server. Even more important, both our front end and back end developers got excited with this technology and started to write tests, making suggestions along the way about how to structure them better. They found in CasperJS a new challenge to dive deeper into their JavaScript skills. ## A test example Here is the test that we wrote for the homepage. It checks that the header (AngularJS powered) has loaded properly and that there are 10 articles listed: ``` /** * homepage.js - Homepage tests. */ casper.test.begin('Tests homepage structure', 7, function suite(test) { casper.start('http://www.msnbc.com', function() { // Verify that the main menu links are present. test.assertExists('a.j-signin-label', '"Sign in" link is found.'); test.assertExists('a.j-register-label', '"Sign up" link is found.'); test.assertExists('li.main-nav__link--explore a', '"Explore" link is found.'); test.assertExists('li.main-nav__link--watch a', '"Watch" link is found.'); test.assertExists('li.main-nav__link--join-in a', '"Join In" link is found.'); test.assertExists('li.main-nav__link--speak-out a', '"Speak Out" link is found.'); // 10 articles should be listed. test.assertElementCount('article', 10, '10 articles are listed.'); }); casper.run(function() { test.done(); }); }); ``` That's it. Simple, huh? This test will ensure that our homepage works. In order to run it, save it anywhere, make sure that CasperJS is installed and run the following command: ``` $ casper test homepage.js Test file: tests/homepage.js # Tests homepage structure PASS "Sign in" link is found. PASS "Sign up" link is found. PASS "Explore" link is found. PASS "Watch" link is found. PASS "Join In" link is found. PASS "Speak Out" link is found. PASS 10 articles are listed. PASS 7 tests executed in 5.064s, 7 passed, 0 failed, 0 dubious, 0 skipped. ``` That's it. You can see above that all of our assertions above passed. The sky is the limit when you are about to define how thoroughly you want to test a particular page. Our advice is just to test what is critical or what has many dependencies and it is therefore more prone to fail. Then slowly build up your test suite as you go by, using a test to affirm that a logged bug actually exists, and then fix the code so that the test passes. ## Next steps We created a repository with the basics of how to install, configure and structure tests within your project. It also contains some useful links to help you ramp up on how to navigate pages and how to run assertions in your CasperJS tests. [You can find it here](https://github.com/Lullabot/casperjs_foundation). Happy testing! Published in: - [ 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: "Setting Up My Mac Without MAMP" url: "/articles/setting-up-my-mac-without-mamp" type: article date: 2013-09-25 updated: 2014-12-25 --- # Setting Up My Mac Without MAMP # Setting Up My Mac Without MAMP Building on OS X's Standard Services for Local Development By [ Karen Stevenson ](/about/karen-stevenson) September 25, 2013 I recently got a new Mac and needed to configure it as a local web server for the many Drupal sites I work on. I used to use MAMP for this, but lately have been using the built-in functionality that comes on a Mac instead. MAMP is easy to install, but it creates a duplicate version of PHP and a duplicate version of Apache. That takes up space on my machine and occasionally causes trouble when some operation uses the wrong version of PHP because of confusion about which installation should take precedence. Setting up a Mac without MAMP used to be sort of complicated, but it's been getting easier and easier with every version of Mac OS, and it's not that hard any more. I thought I'd share the process I'm using now. ## Helper Apps and Terminal Window To start with, I install a couple of apps that make all of this much easier to do. [Pathfinder](https://cocoatech.io) is a replacement for Mac's Finder that adds a number of nice improvements. The most important feature is that you can view invisible files with it. Once installed, go to the **View** option in the menu bar at the top of the page, and check the option to view invisible files. The other handy app I depend on is [Text Wrangler](https://www.barebones.com/products/textwrangler/). The thing I especially like about that is that it makes it easy to edit protected files. When I try to edit a protected file using Text Wrangler it asks if I want to unlock the file. If I say yes, the file is unlocked while I make my changes, then reset to its previous permissions. Without this tool I would either have to edit it in my terminal window using sudo, or keep changing the permission on the file before I edit and then change it back again afterward. You need to use the terminal window for many of these steps. To find that, click on the **Launchpad** icon in the dock, then choose **Others** and then **Terminal**. ## PHP Next install PHP. This is easy, it's already installed! To confirm that type the following to see where it is located: ``` which php ``` And type the following to see what version is installed: ``` php --version ``` Set up php.ini, if it doesn't already exist, by copying php.ini.default: ``` sudo cp /etc/php.ini.default /etc/php.ini ``` You may need to edit it to do things like increase memory. ## Apache Apache is also already installed. There is a default web root located at ``` /Library/WebServer/Documents/ ``` You can put a web root in other places, but that makes configuration more complicated, so I've been leaving that alone. To start Apache, using the terminal type: ``` sudo apachectl start ``` To stop Apache: ``` sudo apachectl stop ``` To restart Apache: ``` sudo apachectl restart ``` To test this, start Apache and go to **http://localhost** in a browser. You should see **'It works!'** You'll be dropping our Drupal files in the web root, so add a bookmark to that using Pathfinder. Go to ``` /Library/WebServer/Documents/ ``` Then choose **Go** from the menu at the top, then **Favorites** and **Add to Favorites** Now you have a quick bookmark to the web root. Go to that location to add the Drupal files for your web site. There are a couple final tweaks to Apache. One is to enable it's handling of PHP by enabling the PHP module. Using Pathfinder, navigate to ``` /private/etc/apache2/httpd.conf ``` Using Text Wrangler, uncomment the line in that file by removing the '#' in front of it: ``` LoadModule php5_module libexec/apache2/libphp5.so ``` If you want to use Virtual hosts, set them up in ``` /private/etc/apache2/extra/httpd-vhosts.conf ``` Edit http.conf and remove the '#' in front of the following line so the Virtual hosts get used: ``` Include /private/etc/apache2/extra/httpd-vhosts.conf ``` Finally, to get clean URLs working find and change the following in http.conf. Find: ``` …. AllowOverride None …. ``` And change it to: ``` …. AllowOverride All …. ``` ## Homebrew The easiest way to do the remaining tasks is to install [Homebrew](http://mxcl.github.com/homebrew/). In a terminal window type the following: ``` ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)" ``` Once it's installed, keep typing `brew doctor` until all errors are fixed. It should be pretty self-explanatory. ## MYSQL The dead easy way to install MYSQL is with Homebrew. Once you have that, just type: ``` brew install mysql ``` Check where it's located and which version you have: ``` which mysql mysql --version ``` Confirm that it's working: ``` mysql ``` MYSQL is set up without a my.cnf file. You may want to create one at /etc/my.cnf with your preferred configuration settings. MYSQL is set up without a root password by default. You should set a MYSQL root password using your terminal window, where 'ROOT\_PASSWORD' is whatever password you want to use for this: ``` cd /usr/local/share/mysql mysqladmin -u root password 'ROOT_PASSWORD' ``` Once last step. Some programs expect to find the mysql.sock file at /var and it isn't there by default. Using the terminal window: ``` sudo mkdir /var/mysql sudo ln -s /tmp/mysql.sock /var/mysql/mysql.sock ``` ## PHPMyAdmin Use Homebrew to Install phpmyadmin, which requires some additional dependencies. In the terminal window type: ``` brew tap homebrew/dupes brew tap josegonzalez/homebrew-php brew install phpmyadmin ``` Once that's installed, in the terminal type: ``` sudo cp /usr/local/share/phpmyadmin/config.sample.inc.php /usr/local/share/phpmyadmin/config.inc.php ``` Use Pathfinder to navigate to the Apache http.config file at: ``` /etc/apache2/http.config ``` Edit it using TextWrangler. Add the following to the bottom of http.config: ``` Alias /phpmyadmin /usr/local/share/phpmyadmin Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny Allow from all ``` **Update:** Note that the above only works in versions prior to Yosemite, in Yosemite change ``` Order allow,deny Allow from all ``` to ``` Require all granted ``` Restart apache. Now navigate to http://localhost/phpmyadmin in a browser and you should see a place to log into PHPMyAdmin. To change the way the login works, using PathFinder go to: ``` /usr/local/share/phpmyadmin/config.inc.php ``` Edit config.inc.php using TextWrangler. If you don't want to have to log in each time, add the following to the Server config: ``` $cfg['Servers'][$i]['user'] = 'root'; $cfg['Servers'][$i]['password'] = 'ROOT_PASSWORD'; $cfg['Servers'][$i]['auth_type'] = 'config'; ``` Change the following to bypass the root password. You can do this if you want to change the root password or if you can't get logged in. Change the root password using the UI in PHPMyAdmin, then reset it to false to go back to using the password: ``` $cfg['Servers'][$i]['AllowNoPassword'] = true; ``` Once logged in, set up the users and databases you need. ## Git You will probably want to be able to use Git to checkout projects and files. It should already be installed. To confirm that, and see where it is and what version you have, in the terminal type: ``` which git git --version ``` ## Drush That's enough to get a local version of a web site working, but for Drupal you'll probably also want to install Drush, which makes management of a Drupal site much much easier. To do that, ``` cd /usr/local/library git clone drush sudo chmod u+x /usr/local/Library/drush/drush sudo ln -s /usr/local/Library/drush/drush /usr/bin/drush ``` Go to your home location, /Users/YOURNAME. Look for a file called **.profile** (the name starts with a dot, it is a protected file). If it doesn't exist already, created it. Edit .profile with TextWrangler, and add this line: ``` alias drush="/usr/bin/drush" ``` ## Done! That's it, everything necessary for a local installation of your web site should be working at this point. It's a little bit of work, but not terrible. Even installing MAMP requires a few extra steps that will send you to a terminal or require that you have a way to find hidden files and edit them, so this process is not a huge amount of extra effort. 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: "Wireframing in Illustrator" url: "/articles/wireframing-in-illustrator" type: article date: 2015-01-07 updated: 2015-01-08 --- # Wireframing in Illustrator # Wireframing in Illustrator A Strategy For a Team Approach By [ Jen Witkowski ](/about/jen-witkowski) January 7, 2015 Designing as a team can be challenging. This is especially true with larger and more complex projects as well as with larger teams. At Lullabot, our design team has encountered many of these challenges, and as our team continues to grow, I’m sure we’ll encounter many more. If you've ever tried to produce and maintain wireframes with several other people, you know the pains of sharing assets and making sure everyone has the most up-to-date version of what they’re working on. Is everyone using the same icon set or the latest logo? What screen sizes should we wireframe for? Is asset consistency across wireframes important? As a team, these are the questions that we’re constantly asking ourselves. It’s important to be proactive in sharing assets and communicating updates and changes. With larger teams, this can sometimes seem unmanageable and frustrating. You can easily spend more time checking for updated assets, than working on the design itself. In the past few months, the Lullabot design team has grown considerably. This growth has led us to look for ways to improve our processes and work more efficiently together. We began by tackling one of the more basic and early parts of a design project, wireframing. ## A common starting point When we start the process of wireframing, it’s a fast-paced process of creating pencil and paper sketches. The goal is to collaboratively produce lots of ideas for ways to solve the user problems. ![Pencil Sketches](/sites/default/files/styles/wide_xs/public/field_regular_upload/wireframe1.jpg.webp?itok=fWuqo-h5 "wireframe1.jpg")**Rough pencil sketches from a UX workshop.** Iterating through ideas on paper seems to jump-start creative collaboration. We typically try to do this rapid ideation work together in one place during a UX workshop. Being together provides creative energy, and also makes it much easier to brainstorm at a rapid pace. After the workshop, we begin refining the rough sketches into more formal ideas. We also begin testing those ideas further by increasing content fidelity. It's at this point where we begin to be able to divide and conquer, with each designer taking on a different wireframe to start exploring UX ideas with the goal to produce a more formal document that we can review as a larger team and perhaps turn into a prototype. With our team now working in different places on different files, how do we quickly start to produce refined wireframes with consistency? On a recent project, we chose to use Adobe Illustrator to refine our early sketches due to our tight timeline and the team’s familiarity with the tool. It was an experiment. We wanted to establish an environment of rapid exploration; static wireframes will eventually be tested in browsers as prototypes, but we often rework or reject rough ideas before investing time in building those prototypes. Working in Illustrator has helped our design team strike a balance, pretty easily achieving high content fidelity while allowing for quick iteration. These wireframes produced in Illustrator also feel "cheap" enough that the team is unafraid to discard ideas that don't work or refine until they do. Our team agreed that one thing that could be extremely helpful as we manage several people creating these wireframes would be to have a common starting point. We needed a boilerplate or template of sorts. This would ensure that we would all be using the same typography, symbols, and components—and save time normally spent searching, assembling and importing everything when starting a new wireframe. A custom AI template seemed to be the most direct answer; they're flexible, and can change and grow as the team discovers new needs. They can also be customized for specific projects. We use our template as a launching point, then add in project-specific assets as needed. We set up a very basic blank template. From there, we linked to Illustrator files for global assets that we needed to share such as logos, approved navigation, header and footer assets, etc. Disclaimer: this isn’t a proven solution. Heck! We might even change as we explore new tools to help improve our processes as we grow. But it’s a place to start, and that’s what we needed. ## What’s in our template? We began by creating a custom AI template and saving it in a place where everyone on the team could access it (in our case a project dropbox). We basically removed all the default graphic styles—colors, patterns, etc and replaced them with starter assets our team needed for wireframing. What’s in our starter template you ask? The bare minimum so we can very quickly customize as needed. - A few different-sized art boards to demonstrate breakpoints ![art boards](/sites/default/files/styles/wide_xs/public/field_regular_upload/artboards.jpg.webp?itok=tPVJFi6C "artboards.jpg")**The art board setup in the template. This should change depending on the needs of the project.** - Color palette - Basic components used as symbols ![symbols and color palette](https://www.lullabot.com/sites/default/files/field_regular_upload/color_palette.jpg "symbols and color palette") *The color palette and symbols. For colors we stuck with basic grays and added a highlight color.* - Paragraph styles - Graphic styles to use for buttons ![graphic and paragraphic styles](https://www.lullabot.com/sites/default/files/field_regular_upload/paragraph.jpg "graphic and paragraph styles") *We set up paragraph styles for a few common elements that needed a consistent type style between wireframes. We also set up a couple of graphic styles for buttons.* - Possible grid setup - Line work for browser window, tablet and smartphone saved as symbols ![line work template](/sites/default/files/styles/wide_xs/public/field_regular_upload/device.jpg.webp?itok=oFWlrS2D "device.jpg")**Basic outlines for different devices. This can sometimes help put UX layouts in context for the client.** We also set some basic preferences. It’s not always mandatory, but we found that setting up the preferences of the template can help avoid small mistakes (CMYK vs RGB for example) and can save everyone just a little time. ## Using global assets Creating global assets can help save time and ensure that everyone working on the wireframe is using the most up-to-date version of a shared component. You may want to think about creating global assets for items used across wireframes such as navigation and footer information. We thought that Illustrator might have streamlined this process with their new library panel, but it seems like sharing assets among team members is still pretty limited. To create a global asset, create an AI file of the specific component and save it in a place like Dropbox where everyone in your team can access it. Have your team members import the AI file into their wireframes and place where needed. If there are any updates that need to be made to the global asset, all you have to do is open the original file, make the changes and save. It will then update in all of the files that the asset was imported to. ## Communication is key It’s important that the team working together is in constant communication. Assets, requirements, and components constantly change, and it’s important that the team working on a project is communicating these changes to one another. It ensures that everyone is using the correct logo, or understands a requirement update that might impact several wireframes. It’s a little more of a challenge for our team because Lullabot is a distributed company. Walking across the cube farm and tapping someone on the shoulder isn’t possible. Nevertheless, things like brief daily stand-up calls (we use Google hangout for these) and a project basecamp for communication seem to work very well for keeping us all on the same page. [Basecamp](https://basecamp.com/ "Basecamp") is a great tool for documenting requirements and functionality. We also use it to directly communicate with our clients and to post questions and comments that we want to open for discussion amongst our team. We also keep each other up to date on work in progress. We’ve found that the quickest and most efficient way to share unfinished work is to hop on a Google Hangout and share screens. [InVision](https://miro.com:443/) is another tool that we use to share both work in progress with our team and our finished wireframes with the client. InVision allows you to leave comments and even "live share" static, visual assets. When you live share a wireframe or design, everyone can see and follow everyone else's cursor and even draw on and write on things together. Live shares are a great way to create a studio environment for group critiques for a distributed design team. ## Conclusion Wireframing together as a team can be very rewarding. Team collaboration can help elevate the best ideas and create a supportive environment for creative experimentation. While the early ideas for structure, layout and functionality of a design system need to be formed collaboratively, the honing and refinement of that design system can be achieved much more quickly by a team that divides and conquers, as long as there's a system in place for constant communication and sharing of assets. A template is a great way to ensure that your team members are all starting off on the same place, and can save you the time of searching for and importing assets. Making sure the team is communicating can help reduce frustrations with updated information and assets sharing, and can lead to a more productive, happy UX experience for both you and your client. 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: "Manage Your Drupal.Org Projects and Sprints with a Kanban Board" url: "/articles/manage-your-drupalorg-projects-and-sprints-with-a-kanban-board" type: article date: 2015-01-09 updated: 2015-01-09 --- # Manage Your Drupal.Org Projects and Sprints with a Kanban Board # Manage Your Drupal.Org Projects and Sprints with a Kanban Board By [ Sally Young ](/about/sally-young) January 9, 2015 I'm not very good with managing my tasks through a simple list—despite my best efforts, the list always seems to keep growing. I prefer to use a [Kanban Board](https://en.wikipedia.org/wiki/Kanban_board), a popular method of arranging lists that highlights the current status of each task. It's nice to see that I actually *do* get things done, after all! Kanban boards are also fantastic for collaborating on projects with other people. They give an instant overview of the team's current status, and you often see them used in [Agile software projects](https://www.lullabot.com/articles/a-clients-guide-to-agile). There are several popular services out there that provide simple boards, including [Trello](https://trello.com/) and [Huboard for Github tasks](https://huboard.com/). Of course, you could also make one with post-it notes and a whiteboard. Recently, I discovered that drupal.org had enabled the [RestWS](https://www.drupal.org/project/restws) module exposes a [RESTful API](https://www.drupal.org/api) for project issues. Naturally, I thought it would be a jolly good idea to build a Kanban Board on top of it! ![drupal.org issues kanban board](https://www.lullabot.com/sites/default/files/entry_image/drupal.org_issues.png) The board is built in [AngularJS](https://angularjs.org/), and can be found here: . Just type in your project name and any tag, version or assigned user you want to filter by. If you'd like to contribute to the project, do drop by the board's [issue queue](https://github.com/Lullabot/drupalorg-issues) as well! Published in: - [ JavaScript ](/topics/javascript) - [ Community ](/topics/community) - [ 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: "Getting Hired By Lullabot, a Distributed Company" url: "/articles/getting-hired-by-lullabot-a-distributed-company" type: article date: 2015-01-22 updated: 2018-08-26 --- # Getting Hired By Lullabot, a Distributed Company # Getting Hired By Lullabot, a Distributed Company Some Assembly Required, Part 1 By [ Marissa Epstein ](/about/marissa-epstein) January 22, 2015 I’ve been a 'Bot' for about two months now. Looking back on it, it seems to have gone by fast. Like one of those really awesome parties you threw in college. It feels even shorter still because so far, I’ve spent almost as much time trying to get hired by Lullabot as I have actually working for them. Let me share a little bit about both phases of starting my career with Lullabot, over a series of articles. ## Getting in Touch I had learned about the company a few years ago from a good friend, then had the pleasure of seeing [Jared Ponchot](https://www.lullabot.com/about/jared-ponchot) speak at [Artifact Conference](https://artifactconf.com/) last year. I pored over the Lullabot site, learning more about the company, and of course, the [open positions](https://www.lullabot.com/jobs). After lurking for a while, I filled out the extensive application. As a slightly obsessive person, I spent a lot of time writing my application answers. Though it was optional, they requested I include a two-minute video about myself— to help them get to know me and my personality better. I outlined what I wanted to say before shooting many takes for my video. I definitely got the impression that Lullabot was seriously weeding out those who send generic applications to every job post. I threw my hat in the ring in July, and the excessive email checking began. ## The Interviews Over the next two months, I had a series of calls with the friendliest [HR gal](https://www.lullabot.com/about/esther-lee) there ever was, [the creative director](https://www.lullabot.com/about/jared-ponchot), a [development manager](https://www.lullabot.com/about/sean-lange), and eventually the rest of the design team. We discussed the job expectations in general, company culture, design experience, and process, and even enjoyed a great tangent about what music we were into. Between each call, there was just enough of a lull (and just enough time for self-doubt) to make me wonder if I was still in the running. At one point, I thought I must be getting close to the end. Instead, we started a trial freelance project that would help Lullabot in their decision. I was hired to redesign the homepage for one of Lullabot’s own projects. As it turns out, Lullabot often uses these trial projects as a way to work with potential applicants in real-world scenarios. ## Trial Project [Yonder](http://yonder.io/) is a small, round-table conference put on by Lullabot, for owners of distributed companies. They had their first event the year prior, and were gearing up for 2015. I designed a simple, one-page site to promote it and encourage people to request an invitation. I did a little research, then shifted into my typical design process. I obsessively worked over the Labor Day weekend, posting progress online as I went. I created a mood board of pulls and inspiration and sketched out a wireframe of the layout on my iPad. After this phase, I dove into the Photoshop file from last year's site, to create a desktop-sized mockup. ![yonder design](/sites/default/files/styles/max_900/public/assets/2015-07/yonder-laptop.png.webp?itok=lcBxALHE "yonder-laptop.png") Some of the Lullabots posted suggestions and changes online, plus discussed options further on a few calls, and so I refined the design after each discussion. I got great feedback, with the CEO even chiming in, and felt challenged by the team to do my best work (which goes a long way in helping me actually do that work). I was proud of the result and felt that the team's input made the page much better. I think it showed in the work how much I had enjoyed the experience of a project with Lullabot. The project reminded me how much fun I could have with web design, fun I wasn't particularly having at my position at the time. ## The Job Offer As I wrapped up the Yonder design and handed off files, Jared sent me an award-winningly-vague note, that I proceeded to read a few too many times, to too many people: "You should hear some more from Esther soon as well in terms of an update on the process for you too." The day I was offered a place on the design team (ten weeks after first submitting my application), I literally jumped for joy. Minutes after my significant other said not to expect an email until Monday (because who sends job offers at 5 on a Friday?), Esther sent me an email titled "Come Rock with Lullabot". Attached was a thorough, personalized job offer. They were very open to my adjustments and questions, and soon it was signed. I was officially a Bot! ## tl;dr: Finding a Good Fit Takes Time In total, this hiring process was the most exhaustive I have ever experienced. I'm not complaining, I swear—I just talk like this. I totally understand why Lullabot hires this way, and respect it. (Even if I did drive myself insane filming that silly video over and over, trying to get it in one take…) Sorry. Let me start again. Leaving Lullabot’s reasons aside for a moment, the process was very exciting, refreshing and fun *for me*. I loved hearing Jared chat about his hopes for the design team, and it was a blast working on the test project for the Yonder site. And of course, hiring goes both ways. Lullabot gave me the opportunity to ask my questions and interview *them* until I was even more confident about the position and my chemistry with the team. Previously I had worked at a studio that “went grocery shopping hungry”, so to speak, often hiring candidates after a call or two. It was driven by immediate need, so the process was too fast for either the company or the candidate to be sure about the fit. In contrast, I believe that Lullabot hires the way it should be done, really taking their time, being very upfront about this at the start, and making confident decisions. After I received the job offer, I felt I had earned it. Starting work at Lullabot, I had instant confidence in and mutual respect for my team—every one of them had earned it, too. This concept was really driven home a few weeks later when I spoke to my Lullabuddy, [Carwin](https://www.lullabot.com/about/carwin-young) (seriously, you get assigned a buddy!). When I asked him if there was anything he wished he'd been told when he started, he told me: “Don't feel like you have to prove yourself, because you're already here, and already accepted.” [Subscribe to the Lullabot mailing list](http://lullabot.us1.list-manage.com/subscribe?u=579cc4bca784b8844042fea50&id=7a64ff23fe) to be notified when Part 2 comes out. 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: "Three Steps to Better Interviews" url: "/articles/three-steps-to-better-interviews" type: article date: 2015-06-25 updated: 2015-06-25 --- # Three Steps to Better Interviews # Three Steps to Better Interviews Improving your interviews is the first step to a better team By [ Andrew Berry ](/about/andrew-berry) June 25, 2015 Though a real-time interview is just one part of a well-rounded hiring process, it's an important one. A good interview isn't about checking off a list of qualifications—there are more efficient ways to do that. Rather, a good interview gives a candidate a platform to show their unique skills and personality. I've had the privilege of being involved in interviews both at Lullabot and at previous companies, and I've picked up a few guidelines along the way that help me conduct and evaluate an interview. ## The Reasonable Truth Interviewing for a position is hard for both the applicant and the interviewers. Many interviews walk a fine line between what questions are asked and what answers are reasonable to expect from a candidate. Sharing work products (such as, project plans, marketing materials, or code) give a real insight into what a person is best at. For candidates, this can be difficult when they are subject to client NDAs. Some may work at restrictive organizations that don't allow code contributions. Communication and personal interaction questions can have the same issues, where too much detail could reveal identities that should be kept private. When I feel like something is missing, my mind begins to race with uncertainty. Were details omitted because of NDAs, or is the story being modified at the compromise of honesty? I like to ask myself, "Did the person tell the truth with the right amount of [truthiness](https://en.wikipedia.org/wiki/Truthiness)?" I want any lessons learned to be communicated by the candidate with authority and integrity. I don't want all the details. In fact, I appreciate the candidate respecting the confidentiality of their prior engagements. But what I need is an authentic and sometimes vulnerable conversation to get a glimpse of their character and what defines them. A candidate should be able to tell an honesty story about work they've done in the past even if they have to redact some specifics. If they play the confidentiality card to the exclusion of any insight about them personally, I'm left with too much uncertainty to recommend them. ## The Three E's of Interviewing I want to know if the candidate shows **empathy** towards their coworkers and their clients. I can't emphasize just how important this is. Empathy is our first defense against stress and discord, especially when projects march toward fixed launch dates. It's too easy for agencies and clients to start blaming each other when timelines, budgets, or functionality start to change to meet launch deadlines. Hiring for empathy gives our entire team the ability to handle more projects with less burnout. I also like to get a chance to evaluate how a candidate approaches **education**. I don't just mean education in the strict institutional sense, but how they learn day by day, apply new lessons to their work, and share their experiences with their colleagues and the world. Seeing how a candidate writes and shares their knowledge provides insight into how they will share the same knowledge when working with clients. Finally, though perhaps a bit unconventional, I like to get an idea of how **entertained** a candidate is by the work they do. I don't mean that someone finds their work to be "fun", to the detriment of balance in their life. I want to know if a candidate finds the humor of the crazy client and technical situations we sometimes end up in to be a positive, entertaining part of the work we do. This value isn't just about immediate team and client interactions. I've found that people who have this quality can both break the ice in tense situations and are more resilient against burnout. ## A place to grow In the agency world, we often are hiring to fill a specific role on a specific project. Unlike other tech industries (like entertainment and video games) that go through boom and bust hiring cycles, our goal is to consistently have low turnover with our staff. When considering a candidate, I like to ask myself **"Can this person be a contributor immediately, and a leader in 6 months?"**. Every person we hire should be immediately useful to the team so that they can feel valued and important. I feel like it's not fair to assume a new hire becomes a "leader" instantly. Every person needs to find their niche and to find what drives them when tackling a new job. But what I want to know when we hire someone they have the potential to become a leader in something. This is rarely about managerial roles and responsibilities; instead, it's about empowering everyone to become experts in their own way. Lullabot needs leaders because of the way we work. Most of the team works directly with our clients. As we're a distributed company, we need proactive and deliberate communication. You can't hide in a cubicle here. Hiring for leadership helps ensure that those we do hire have the best chance of succeeding. Like coding, an interview process is never done or complete. I'm sure there are guidelines I've missed. What are yours? 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: "Understanding JavaScript behaviors in Drupal" url: "/articles/understanding-javascript-behaviors-in-drupal" type: article date: 2015-02-03 updated: 2021-01-12 --- # Understanding JavaScript behaviors in Drupal # Understanding JavaScript behaviors in Drupal Make your JavaScript more efficient by mastering Drupal behaviors. By [ Juampy NR ](/about/juampy-nr) February 3, 2015 I can barely remember the first time I added JavaScript to a Drupal page using a custom module. I'm sure looked at the documentation, took the example snippet, tweaked it, tested it, and moved on to something else. It was only later, using a debugger, that I saw how Drupal's "behavior" system worked and realized that my code was not being executed as I expected. In this article, we'll cover the key facts about Drupal behaviors, then look at a real Drupal project to inspect its behaviors and optimize them. ## Unraveling Drupal behaviors [Drupal’s official JavaScript](https://www.drupal.org/node/756722) documentation suggests that modules should implement JavaScript by attaching logic to `Drupal.behaviors`. Here is an example taken from that page: ``` Drupal.behaviors.exampleModule = { attach: function (context, settings) { $('.example', context).click(function () { $(this).next('ul').toggle('show'); }); } }; ``` Drupal core will call attached behaviors when the [DOM](https://en.wikipedia.org/wiki/Document_Object_Model) (the object representation of the HTML) is fully loaded, passing in two arguments: - context: which contains the DOM. - settings: which contains all the settings injected server side. We can confirm this at the following snippet extracted from Drupal core’s `misc/drupal.js`: ``` // Attach all behaviors. $(function () { Drupal.attachBehaviors(document, Drupal.settings); }); ``` *NOTE: `$(function ()` is a shorthand for `$(document).ready()`. See for further details.* Now here is the point where I had that “aha!” moment the first time I watched the code execute in a debugger: `Drupal.attachBehaviors()` may be called more times under different circumstances after the DOM is loaded. For instance, Drupal core will also call `Drupal.attachBehaviors()` in these scenarios: - After an administration overlay has been loaded into the page. - After the [AJAX Form API](https://www.drupal.org/node/752056) has submitted a form. - When an AJAX request returns a command that modifies the HTML, such as [ajax\_command\_replace()](https://api.drupal.org/api/drupal/includes%21ajax.inc/function/ajax_command_replace/7). Furthermore, modules may call `Drupal.attachBehaviors()` as well. Here are some examples: - CTools calls it after a modal has been loaded. - Media calls it after the media browser has been loaded. - Panels calls it after in-place editing has been completed. - Views calls it after loading a new page that uses AJAX. - Views Load More calls it after loading the next chunk of items. - JavaScript from custom modules may call `Drupal.attachBehaviors()` when they add or change parts of the page. The first time that `Drupal.attachBehaviors()` is called, the `context` variable contains the `document` object representing the DOM, but for the rest of the calls `context` will hold the affected piece of HTML. This is often overlooked by developers, leading to inefficient code that either causes tricky bugs or stresses the browser. How can we make sure that existing Drupal behaviors take this into account? In the following section we will debug and analyze them in a real Drupal project. ## Navigating through Drupal behaviors While working on a Drupal project, I like to open a page and debug which behaviors are executed to make sure that they are efficient. I set a breakpoint at `Drupal.attachBehaviors()`, then open a page and follow the code of each behavior to see what it is doing. Let’s take the [Syfy](https://www.syfy.com/) project as an example, where Lullabot helped with its redesign and launch. Using [Google Chrome Developer Tools](https://developer.chrome.com/devtools), we will open Syfy’s homepage and set a breakpoint at the following line of core’s `misc/drupal.js`: ![Google Chrome Debugging Console](/sites/default/files/styles/max_900/public/assets/2015-09/attach.png.webp?itok=4-vJKbkI "attach.png") After reloading the page, the debugger will kick in and stop code execution—waiting for us to make a decision on what to do: ![Debugger started](/sites/default/files/styles/max_900/public/assets/2015-09/debugger.png.webp?itok=q78JtndN "debugger.png") If you do not have experience using a debugger, have a look at the [Debugging JavaScript](https://developer.chrome.com/docs/devtools/) section for Google Chrome. A debugger lets you follow the code step by step inspecting the current variables and letting you write statements against the current context. We said before that "Drupal will call all attached behaviors when the DOM is fully loaded, passing as arguments the DOM and all the settings injected server side.” Let’s verify this in the debugger. Here we can see the `context` and `settings` variables at the `Scope Variables` panel: ![Scope Variables Panel](/sites/default/files/styles/max_900/public/assets/2015-09/variables.png.webp?itok=-ucyIoAn "variables.png") Most custom behaviors are to be executed just once, so on this first loop things are normally fine. The tricky bit comes later, once all behaviors have been processed and something calls `Drupal.attachBehaviors()` again. Let’s see an example. ## Testing behaviors on subsequent calls Syfy’s homepage has a View of tiles that uses Views Load More module to load extra items at the bottom. We will now scroll down and click on Load More to see what happens: ![Load More Button](/sites/default/files/styles/wide_xs/public/assets/2015-09/load_more.png.webp?itok=cbpHbHew "load_more.png") Drupal calls `/views/ajax` to load the next list of tiles and once it has appended them to the DOM, it executes `Drupal.attachBehaviors()` with a subtle difference that we can discover when we inspect its variables in the debugger: ![Gotta love context](/sites/default/files/styles/wide_xs/public/assets/2015-09/context_aha.png.webp?itok=-7bTiPn2 "context_aha.png") The `context` variable does not contain the full HTML document but the updated view. This difference is huge. Drupal behaviors that take the `context` variable into account when selecting portions of the DOM such as `$('#some-id', context)` will be quickly skipped since they won’t find what they are looking for. Unfortunately, it is a common mistake to overlook this and not use the `context` variable in jQuery selectors. Here is a behavior that I found while debugging behaviors in this second loop: ``` /** * Hide menu when Esc is pressed. */ Drupal.behaviors.syfyGlobalHideMenu = { attach: function (context, settings) { $(document).keyup(function (e) { if (e.keyCode == 27) { $('.nav-flyout', context).removeClass('js-flyout-active'); } }); } }; ``` It sets a listener at the `document` level to check if the pressed key was Esc in order to gracefully hide the main menu, which is expanded in the following screenshot: ![Syfy's menu expanded](/sites/default/files/styles/wide_xs/public/assets/2015-09/menu.png.webp?itok=bEIE_egw "menu.png") What is the problem here? It uses the `context` variable to find the menu at `$('.nav-flyout', context)`, but it does not use `context` when it sets the keyup listener at `$(document).keyup(function(e)`. This means that every time Drupal behaviors are processed, it will add a new `keyup` listener. ## Making behaviors behave There are a few ways to fix the above behavior so it sets a listener just once. Let’s see each of them and alter the above behavior accordingly. Here they are. ### Using jQuery Once This is the recommended approach at the [Drupal.org JavaScript documentation](https://www.drupal.org/node/756722#jquery-once). jQuery Once makes sure that something is processed just one time by adding a class on a DOM element after the code has run. ``` /** * Hide menu when Esc is pressed. */ Drupal.behaviors.syfyGlobalHideMenu = { attach: function (context, settings) { $('.nav-flyout', context).once('remove-modals', function () { $(document).keyup(function (e) { if (e.keyCode == 27) { $('.nav-flyout', context).removeClass('js-flyout-active'); } }); }); } }; ``` The above code will add a class `remove-modals-processed` the first time it runs. The next time that `Drupal.attachBehaviors()` is called, `.once()` will find the class and skip. ### Using the context variable in the jQuery selector Not using the context variable in jQuery selectors is the source of most of headaches related with debugging buggy JavaScript behaviors. Here is an example where we make use of the context variable in our selector: ``` /** * Hide menu when Esc is pressed. */ Drupal.behaviors.syfyGlobalHideMenu = { attach: function (context, settings) { $(document, context).keyup(function (e) { if (e.keyCode == 27) { $('.nav-flyout', context).removeClass('js-flyout-active'); } }); } }; ``` The above approach is simple and effective. jQuery will find the `document` object in the context variable only the first time that `Drupal.attachBehaviors()` is called. Note, however, that if you have custom code that calls `Drupal.attachBehaviors(document);` then the condition will be met and another listener will be bound. ### Going our own way Our last alternative is ignoring Drupal's behavior system and simply waiting for the `document` object to be ready: ``` /** * Hide menu when Esc is pressed. */ $(function () { $(document).keyup(function (e) { if (e.keyCode == 27) { $('.nav-flyout').removeClass('js-flyout-active'); } }); }); ``` The above code will work as expected, and If you don't need access to the niceties of Drupal behaviors, it's a perfectly valid approach. ## Conclusion The main concept that we should take into account is that Behaviors will be called first when the DOM is loaded, and may be called more times with a context representing new additions or changes to the DOM. Our code has to be crafted so it kicks in only when it is needed. Did this article spark your curiosity to open a project and navigate through its behaviors? I hope it did. Open a page, dive through each behavior and ask yourself questions like when should this piece of code run? should it run when the DOM is ready or after some AJAX request completes? This mindset will help you to write sharp and efficient JavaScript. 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: "Adjusting to Lullabot's Culture" url: "/articles/adjusting-to-lullabots-culture" type: article date: 2015-02-24 updated: 2018-08-26 --- # Adjusting to Lullabot's Culture # Adjusting to Lullabot's Culture Some Assembly Required, Part 2 By [ Marissa Epstein ](/about/marissa-epstein) February 24, 2015 Last month, I talked about my experience [being hired by Lullabot](https://www.lullabot.com/articles/getting-hired-by-lullabot-a-distributed-company). Now, I'd like to tell you about my first few months as a UX Designer. I am still learning about my coworkers and finding my groove, but can already look back on where I started and see my progress. ## A Well-Defined Process As soon as I was hired, I realized what a well oiled-machine I was a part of. I'm a huge geek for process, and it’s obvious that other bots are, too. Once I accepted the job offer, I received a monster email explaining all of the accounts to log into, apps to set up, and paperwork to fill out. It also included a link to a detailed company handbook — the process geek in me exclaimed, "Hooray, there's a handbook!" ## Tossed into the Deep End In the weeks that followed, I heard the phrase “drinking from the firehose” a lot. I expected it to be worse, but everything I needed to know was carefully documented and explained. I think this excerpt from the handbook summarizes the Lullabot approach perfectly: > “We know that you are human… and that we've tossed you into the deep end. We don’t expect you to be an Olympic swimmer right away. We fully expect that you're going to choke and cough a bit—perhaps submerge completely. It’s going to be a little overwhelming at first. We understand that. The rest of the team is around to help. We can pull you out of the deep end. We can even give you a swimming lesson. But we also know that you're capable of great things. There is no shame in asking for help. We're actually more worried when new people don’t ask for help. We want you to learn. We want to teach you. We know that you'll eventually get the hang of things and swim like a fish!" I loved that the handbook wasn't just about nitty-gritty rules and regulations. I learned a lot about the culture and values of Lullabot, and found the personal tone to be just what I needed. Lullabot had carefully considered what new hires would go through and calmed those butterflies. It felt like I had a life raft, support as I went into that "deep end." ## The Onboarding A few days before I started, I cracked my knuckles, read through everything, and made a simple checklist to keep track of everything that needed doing. I received signup emails and instructions for the services I'd be using to communicate with the team, share files, and track my time and expenses. My Dropbox synced for hours, and downloading the Adobe Creative Cloud applications took almost as long. Besides tinkering with my email account and filtering, I spent the most time getting up to speed with IRC, our go-to messaging system (edit: these days, we have swapped IRC for Slack). I dove into the paperwork and forms (filling out health insurance and 401k information takes time), carefully wrote my bio for the website, and took a temporary headshot photo. The HR team kept things personal and friendly throughout the process. I had lovely sync calls with [Kris](https://www.lullabot.com/about/kris-konrady), who walked me through a variety of admin items, gave me ample opportunity to ask my questions, and made sure that I hadn’t overlooked anything. That sort of help was what I'd expected, or at least hoped for. What I hadn't anticipated was the amazing, supporting community created by *all* of the Lullabots. Even people I wouldn't necessarily work with on my design projects took time out of their own busy projects to welcome me. As I mentioned in my [last article](https://www.lullabot.com/articles/getting-hired-by-lullabot-a-distributed-company), I was assigned a Lullabuddy, a peer-level coworker who could answer any questions I had. Hopping onto hangouts with [Carwin](https://www.lullabot.com/about/carwin-young) during my first few days helped me get a better handle on the culture of Lullabot, and I'm thankful for that help. I didn't have too many questions, but it was great to know he was there if I had something weighing on my mind and didn't want to reach out to my director or HR. ## Setting Up My Space Even though the process was smooth, there *was* a lot of adjustment for me in the beginning. I spent my first few days as a Lullabot in Atlanta, meeting and working with my project team. That meant that I was booking my flights, getting ready for my trip, buying a laptop and getting it set up, all while completing the onboarding steps I've been discussing! I received an invite to the Basecamp, and saw pages of research that colleagues had already begun. Cool. After I got back from my trip, the adjustments in my day-to-day routine started. Because Lullabot is entirely distributed, there's no desk waiting for you on day one: you can set up your workspace at home (or the coffee shop) however you'd like. I decided to rework my existing desk to make the most of my small apartment. (I upgraded to an adjustable standing desk!) I had been able to work from home now and then at my previous job, but had never worked remotely full time. I'd dreamed of working from my home office, on my own schedule, but I'd always had the safety net of going into the office the next day — so I was a little surprised by what made it difficult. It's a weird feeling when you realize that you haven't left the house all day. On quiet days when I felt stir crazy, I’d chat my boyfriend's ear off as soon as he arrived home. Once, I was horrified to realize I'd been so caught up in my project that I'd simply forgotten to eat lunch! Now, I try to pay attention, set reminders, take breaks. Deliberately getting out of the building or down to the gym helps me stay active and socialize. ## Totally On Board I've been at Lullabot for almost five months now, and can safely say I've progressed past the onboarding phase. I'm comfortable with my remote office and schedule and I have set up all the accounts and programs I need. I've been lucky enough to collaborate on some great design projects with my team, both for our clients and ourselves. I'm excited to get to know the bots even better! Of course, there is always room to grow, but already I feel like a part of the Lullabot family. *[Subscribe to the Lullabot mailing list](http://lullabot.us1.list-manage.com/subscribe?u=579cc4bca784b8844042fea50&id=7a64ff23fe) to be notified when Part 3 of this series on is published.* 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: "A Lullabot’s Guide to Successful Meetings" url: "/articles/a-lullabots-guide-to-successful-meetings" type: article date: 2015-03-04 updated: 2015-03-04 --- # A Lullabot’s Guide to Successful Meetings # A Lullabot’s Guide to Successful Meetings Learning to love the “touch-base” By [ Andrew Berry ](/about/andrew-berry) March 4, 2015 One of the core skills of our client services team is the ability to communicate clearly, efficiently, and humanely to each other and to our clients. It’s this communication that gets us through gnarly project roadblocks and beyond the purely technical solutions. Unfortunately, this can lead to the dreaded wave of “calls”, “syncs”, “touch-bases”, and “meetings” which eat up our calendar hours. As much as these terms can make our stomachs collectively turn, they are a critical component of the success of our projects. How do we turn this communication from something we avoid into something we look forward to? ## Min-maxing participants and empowerment The first step is to define the participants in a meeting. Each participant should be able to provide a unique perspective to the topic at hand. On many projects, there are multiple developers, designers, testers, and project managers, and in many cases only one person from each group is actually required for the weekly syncs. It’s easy to default attendance to whoever has “senior”, or “lead”, or “manager” somewhere in their title. But in a balanced and capable team, it is often better to share the load of decision making among the greater team, especially as projects move on and individuals develop areas of expertise. In some cases, it can work well to simply round-robin participation from different subgroups. This helps to prevent one person from being overloaded with calls and meetings while giving the whole team a chance to drive the project direction. A meeting should end up with a small group of individuals attending. I find it works best with no more than 4 people. Each person should have distinct contributions to make, and together the group should be empowered to make decisions. Sometimes discussion reveals further dependencies to resolutions, but ideally a meeting is a chance to show your team that you trust them and their expertise. ## Action items and responsibilities “What did we decide on the last call?” is one of the most frustrating ways for teams to start a meeting. Rehashing discussions and decisions leaves participants feeling like their time was wasted and that no progress was made. With a bit of guidance (typically from project managers), meetings can be made more effective with some documentation. I find the best meeting notes are those that are distributed to the whole team and indexed, such as in an app like Basecamp or Google Groups. Each note should contain the participants, one-line summaries of the discussion, and action items at the end. Every action item should have a clear owner and notes of any dependencies, along with any external constraints. For example: > “Investigate why drush is broken.” is significantly less useful than > “Angus will talk to the multifield maintainer on why drush updatedb broke on the last build. This needs to be fixed before Tuesday so we can do our next production deployment.” Having action items and clear responsibilities documented solves a whole host of communication problems, while keeping the rest of the team up to date. Reading is so much faster than listening and speaking, so it allows the rest of the team to keep up to date without the same time commitment. ## Iteration, Evaluation, and Communication Standing meetings can be useful to force communication, but beyond daily standups, they should only be used as a last resort. Early in a project they can really help with architectural discovery, or in bootstrapping new teams. As time goes on, standing meetings can often become longer and have more and more people invited to them. It’s important to continually evaluate a meeting, and to willingly burn it to the ground to refocus and rebuild. Every meeting participant should be empowered to call the existence of a meeting into question. When team members start grumbling about a recurring call, they should be able to discuss their feelings openly and honestly. Just as we never stop aiming to make ourselves better developers, as teams we must never stop trying to improve and optimize our communication. By following these steps, your teams will be well on the path towards meeting nirvana. 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: "Form API #states" url: "/articles/form-api-states" type: article date: 2015-03-26 updated: 2019-07-09 --- # Form API #states # Form API #states Dynamic forms without the custom JavaScript By [ Angus Mak ](/about/angus-mak) March 26, 2015 Drupal's Form API helps developers build complex, extendable user input forms with minimal code. One of its most powerful features, though, isn't very well known: the #states system. Form API #states allow us to create form elements that change state (show, hide, enable, disable, etc.) depending on certain conditions—for example, disabling one field based on the contents of another. For most common Form UI tasks, the #states system eliminates the need to write custom JavaScript. It eliminates inconsistencies by translating simple form element properties into standardized JavaScript code. The syntax of the #state property is: ``` #states' => array( 'STATE' => array( JQUERY_SELECTOR => REMOTE_CONDITIONS, JQUERY_SELECTOR => REMOTE_CONDITIONS, ... ), ), ``` Each form field consist of states and remote conditions that define the properties of the field. A state is a property that can be applied to a form element (i.e. enabled, disabled, checked, unchecked), while a remote condition is the state of an element to trigger a change in different element. All the available states and remote conditions are defined in the [drupal\_process\_states()](https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_process_states/7). [Wunderkraut](https://mearra.com/) also has a great [article](https://mearra.com/), quoting our Co-Founder and CEO [Jeff Robbins](https://www.lullabot.com/about/jeff-robbins) breaking down the states into two categories, "the ones that trigger change in others" and "the ones that get applied onto elements". Starting with a simple form, let's look at a few examples. Here is a simple form to collect some basic personal info from a user. ![Simple form](/sites/default/files/styles/max_900/public/assets/2015-07/simple_form.jpg.webp?itok=92Cn2V6p "simple_form.jpg") We would like the name field and anonymous checkbox to work together. We can simply use the invisible state to hide the name field when the anonymous checkbox is checked, and conversely keep the anonymous checkbox unchecked if the name field is filled. ``` // Hide name field when the anonymous checkbox is checked. $form['name'] = array( '#type' => 'textfield', '#title' => t('Name'), '#states' => array( 'invisible' => array( ':input[name="anonymous"]' => array('checked' => TRUE), ), ), ); // Uncheck anonymous field when the name field is filled. $form['anonymous'] = array( '#type' => 'checkbox', '#title' => t('I prefer to remain anonymous'), '#states' => array( 'unchecked' => array( ':input[name="name"]' => array('filled' => TRUE), ), ), ); ``` We can also define multiple conditions to show the email field, only when the name field is filled and email is the preferred method of contact. ``` // Show the email field when the name field is filled // and 'email' is selected for the preferred method of contact field $form['email'] = array( '#type' => 'textfield', '#title' => t('Email'), '#states' => array( 'visible' => array( ':input[name="name"]' => array('filled' => TRUE), ':select[name="method"]' => array('value' => 'email'), ), ), ); ``` For OR conditions, use an non-associative array by wrapping each group of conditions in the array() function. ``` // Show the email field when either // * the name is filled and the method is email, // * or anonymous is checked and method is email. $form['email'] = array( '#type' => 'textfield', '#title' => t('Email'), '#states' => array( 'visible' => array( array( ':input[name="name"]' => array('filled' => TRUE), ':select[name="method"]' => array('value' => 'email'), ), array( ':input[name="anonymous"]' => array('checked' => TRUE), ':select[name="method"]' => array('value' => 'email'), ), ), ), ); ``` There is also a special syntax for the exclusive or (XOR) operator, which one or the other condition is true, but not both. While the OR operator is implied by using an non-associative array, the XOR operator needs to be explicitly defined with an array item with the string 'xor'. Let's change the email field to accommodate users that prefer to be anonymous. ``` // Show the email field when either condition is true, but not both // * the name is filled and the method is email, // * anonymous is checked and method is email. $form['email'] = array( '#type' => 'textfield', '#title' => t('Email'), '#states' => array( 'visible' => array( array( ':input[name="name"]' => array('filled' => TRUE), ':select[name="method"]' => array('value' => 'email'), ), 'xor', array( ':input[name="anonymous"]' => array('checked' => TRUE), ':select[name="method"]' => array('value' => 'email'), ), ), ), ); ``` ## Conclusion Form API #states is a great way to generate consistent JavaScript code for simple form interactions. Although custom JavaScript might be necessary for more complex requirements, the #states system gives us a very good start in creating centralized and standardized frontend code. I find the #state system generally much cleaner in terms of code maintenance. Compared to custom JavaScript, it's also less prone to bugs and accessibility issues. ## Further reading - [Drupal API](https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_process_states/7) - [form\_example\_states\_form](https://api.drupal.org/api/examples/form_example%21form_example_states.inc/function/form_example_states_form/7) - [Using the Drupal 7 Form API states system to create conditions between form elements](https://mearra.com/) - [Drupal 7 Form API: Using #states with multiple conditionals (AND, OR and XOR)](https://www.metaltoad.com/blog/drupal-7-form-api-using-states-multiple-conditionals-and-or-and-xor) 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: "Getting Together When You Work Apart" url: "/articles/getting-together-when-you-work-apart" type: article date: 2015-04-02 updated: 2018-08-26 --- # Getting Together When You Work Apart # Getting Together When You Work Apart Some Assembly Required, Part 3 By [ Marissa Epstein ](/about/marissa-epstein) April 2, 2015 *This is the final article in my series on being a new Lullabot, where I focus on what it’s like when we get together in person. Catch up with [Part 1](https://www.lullabot.com/articles/getting-hired-by-lullabot-a-distributed-company) and [Part 2](https://www.lullabot.com/articles/adjusting-to-lullabots-culture) to learn more about what it’s like working apart.* ## Coming Together For Projects As I mentioned in [Part 2](https://www.lullabot.com/articles/adjusting-to-lullabots-culture), my first week was spent on-site with my project team in Atlanta, working alongside designers and developers. It's funny, I’ve dreamed of working remotely on a distributed team, yet I cherish these “face time” days together most. ![Working together](/sites/default/files/styles/wide_xs/public/assets/2015-07/project.jpg.webp?itok=r5mY276u "project.jpg") With scheduled virtual meetings, it’s easy to focus on the task at hand and forget to enjoy each others’ company. Meeting face-to-face fills in the blanks. I see expressions and mannerisms. And it’s just so fun! While sketching ideas for the new [GRAMMY.com](https://www.grammy.com/) side-by-side at a huge conference table in our Atlanta hotel, we cracked jokes, shared music, and fed off each other's energy. Our sketch sessions were inspired. Each night brought opportunities to bond as a team. I left with a new love of sour beers—Duchesse de Bourgogne in particular—and a backlog of design apps bookmarked or downloading on my iPhone. ## The Team Retreat Various Lullabots connect in person several times a year, between onsite meetings for client projects, conferences, and other organized events. Once a year, however, the whole company puts client work on hold for a week to be together. This year, we went to [Smoke Tree Ranch](https://smoketreeranch.com/) in Palm Springs. I remember a co-worker saying it "feels like Christmas, with the retreat only weeks away". I thought she was teasing. Now I know better. Smoke Tree Ranch is not a rugged sort of ranch. National garden tours make pilgrimages here to check “immaculately landscaped desert oasis” off their list of things to see. Our cottages were cozy, the weather was perfect, and the grounds were gorgeous. ![Smoke Tree Ranch](/sites/default/files/styles/wide_xs/public/assets/2015-07/smoketree.jpeg.webp?itok=026eHb8k "smoketree.jpeg") While staying at the ranch, we enjoyed a mixture of group discussions, activities, and one-on-ones. Directors gave passionate presentations, the team partook in thought-provoking round-table discussions, and we filled the dining hall with conversation at mealtimes. Because the majority of the team traveled West, impromptu morning hikes and yoga sessions began at dawn. There was time set aside to chill out each day after lunch that we could freely spend together or alone. Each afternoon at the golden hour, with the sun setting in pink and orange, we’d break into groups of about six people for circles, where we were encouraged to share our feelings and experiences without judgement. Some of the things my coworkers said were profound, vulnerable, or both, and I was thankful to learn so much about them. ## Nights at Smoke Tree Ranch Evenings were my favorite. We’d crack open beers and enjoy some new way to share each night. We started with a trivia night, focused on facts about team members that helped us get to know each other. There was even a question about me! It was sort of a trick question; Jeff asked which Lullabot is dating Will Farrell—that is, my developer boyfriend, not Will Ferrell, the actor. The next night was dedicated to Ignite talks, hilarious five-minute presentations comprised of twenty auto-advancing slides. [Juan Pablo Novillo Requena](https://www.lullabot.com/about/juampy-nr), a Spaniard affectionately known as “Juampy”, collected weird English idioms he’d heard from co-workers for months—from whoopee to mongongous—then shared his findings. [Greg Dunlap](https://www.lullabot.com/who-we-are/greg-dunlap) taught us about the insane Swedish tradition of burning the Gävle goat on Christmas Eve. Somehow, we all ended up chanting, “Burn the goat!” Subsequent evenings brought the talent show and storytelling night: brave ‘Bots (myself not included) sang, played instruments, or told stories, a mix of funny, suspenseful, and heart-warming. ![Sunset](/sites/default/files/styles/wide_xs/public/assets/2015-07/sunset.jpg.webp?itok=kSTCDVF- "sunset.jpg") Heading into the retreat, I was daunted by the prospect of getting to know the more than 50 people I hadn’t met in person before. Not only did I meet everyone, I formed deeper connections than I expected. I was able to see firsthand the amazing dynamic of the team, which is balanced between professionalism and fun. The dining hall (and the hours to follow) felt like a party with friends every night—oh yeah, and the final night brought an actual Lullabot party poolside with Mariachi band. Coworkers let down their hair and goofed off. That night, I played Cards Against Humanity and watched as a cadre of developers tossed a very tall C-level exec into the pool. ## Welcome Home As we wrapped up the retreat, I felt like part of the Lullabot family, and am honored to say so. Both on and offline, I have experienced the culture of the company, and I love what it stands for. I am getting into the rhythm of the schedule and specifics of the work. For those remaining things I'm still figuring out, I know that I am not alone. I still have a long way to go, especially as I look around at all of those who have been at Lullabot for many years. There's always more history to learn, and processes to explore, but I'm comfortable with whatever my Lullabot experience will bring. I know I’ll have my team behind me. I am so proud of them, their accomplishments and their passions, and am proud to be a part of such a supportive, flexible team. ## Together Apart Overall, our time together was informative, thought-provoking, productive, and I can't think of the last time I had so much fun. Between heckling and joking, the team kept me laughing all day. We came to learn more about our company and make it better, but still found time to soak up the sun by the pool with breathtaking mountain views. ![Town hall](/sites/default/files/styles/wide_xs/public/assets/2015-07/townhall.jpg.webp?itok=_AqHM3V1 "townhall.jpg") On one hand, I wish there were more weeks like these, because I enjoyed the company of my coworkers so much. I’ll miss my team until I see them again. Nevertheless, this retreat was special because it’s not the norm. Since we don't interact in an office every day, we can stretch beyond our comfort zones to connect with one another and really make the most of our time. Most of us chose to work at a distributed company because we enjoy uninterrupted spaces of concentration to do great work. We’re recharged by solitude. We like our own space. 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: "Choosing the Right JavaScript Framework for the Job" url: "/articles/choosing-the-right-javascript-framework-for-the-job" type: article date: 2015-04-09 updated: 2023-11-20 --- # Choosing the Right JavaScript Framework for the Job # Choosing the Right JavaScript Framework for the Job Weighing the pros and cons of popular front-end frameworks By [ John Hannah ](/about/john-hannah) April 9, 2015 If you’ve been following web development over the past few years, you will no doubt have noticed that JavaScript frameworks are an increasingly popular way to build web applications. Although there are many frameworks out there, four of them stand out: Backbone, AngularJS, Ember and React. Perhaps you’ve had a chance to experiment with one or two of these frameworks, but are still a little unsure about the best one to commit to mastering. Web development, particularly front-end development, has been moving at a blistering pace and there is constant pressure to add valuable new skills to your repertoire. Developers have to make tough choices about what they are going to focus on, and the idea of spending months learning a new framework that ultimately doesn’t pay off isn’t very appealing, not to mention if the result is a production application you are then committed to maintaining. What follows is a high level look at these frameworks. It’s the result of my experience with them over the past couple of years. Some of them I’ve dug into more deeply than others, but I tried to understand the benefits and concerns with each so that I could make an informed choice about which has the most to offer. ## The View from 35,000 Feet Before we discuss the individual frameworks, let’s take a moment to get a handle on the big picture. Why would we want to use one of these frameworks in the first place? Why not just stick with a traditional server-side application? The very short answer is that they offer a more responsive user experience. For example, when a user clicks a button, instead of waiting for the entire page to reload as in a traditional server-side web application, JavaScript frameworks only load in portions of the page as the user interacts with them, thus speeding up the responsiveness of the user interface. It can have the effect of creating a UI that feels as snappy as a native mobile app. Of course, you can do similar things with a traditional app and a bit of jQuery, but if you’ve ever tried that then you know how quickly you can run into trouble. Unless you’re dealing with something simple, code management with jQuery quickly becomes a challenge, often leading to [“spaghetti” code](https://en.wikipedia.org/wiki/Spaghetti_code). Modern JavaScript frameworks offer a way around the problem of code management by providing well-defined application architectures (often using the [MVC design pattern](http://en.wikipedia.org/wiki/Model%C3%A2%C2%80%C2%93view%C3%A2%C2%80%C2%93controller), which jQuery lacks) that can greatly ease development. So in using one of these frameworks, we get highly responsive user interfaces along with well structured and maintainable code, which can be a huge time saver in the long run. Now that we have a general idea of why these frameworks are getting so much attention, let’s look at each of them in turn and see what they have to offer. ## Backbone.js As one of the oldest of the JavaScript frameworks in this review, Backbone has lost much of its initial buzz, but that shouldn’t dissuade you from giving it serious consideration. First released in 2010 by Jeremy Ashkenas, Backbone is lightweight. Coming in at just 6.3KB when minified and compressed for production and with only one dependency (Underscore.js), it’s a highly versatile and minimalistic [MVC (Model-View-Controller) framework](http://en.wikipedia.org/wiki/Model%C3%A2%C2%80%C2%93view%C3%A2%C2%80%C2%93controller) that powers a lot of sites you may be familiar with: Twitter, Hulu, Pinterest and my personal favorite, Pandora Radio. ### Benefits Aside from the compact file size, Backbone’s great strength is its versatility. Unlike the other frameworks, Backbone isn’t highly opinionated. For example, it doesn’t come with it’s own templating engine (aside from the basic one included in Underscore), leaving developers to choose whatever works best for them on a given project. Because it’s so lightweight, Backbone shines brightest when used in simpler projects where speed is a priority - think single page apps (Twitter, Pinterest) or a widget that is part of a traditional web application. ### Concerns Backbone may be better suited for more advanced JavaScript developers. It’s minimalism can be both a blessing and a challenge depending on the experience level of the developer working with it. There are a lot of different libraries and plugins that can be mixed and matched when building a Backbone application, and while many developers love this extensibility, it can be challenging for newcomers. There are also concerns about the need for excessive “boilerplate” code in order to use the framework successfully in a project. These complaints are often dismissed by more experienced developers who suggest those who are writing a lot of boilerplate on each project aren’t using Backbone correctly. Another significant concern—not unique to Backbone by any means—is a lack of server-side rendering. For a more in-depth discussion of this, I recommend reading Tim Kadlec’s [excellent post](https://timkadlec.com/2015/02/client-side-templatings-major-bug/) on the topic. He writes, “if your client-side MVC framework does not support server-side rendering, that is a bug. It cripples performance.” I agree with Kadlec on this and I would add that another concern about a lack of server-side rendering is SEO. Most search engine bots are unable to parse JavaScript, and when they happen upon a site that is using a framework that doesn’t support server-side rendering, they don’t receive the content. There are workarounds to the problem, but it’s definitely something to keep in mind. Few projects can afford to lose large amounts of organic search traffic. ### Bottom line Backbone is a framework that shines brightest in the hands of experienced developers building single page applications (SPAs) and widgets. If you’re interested in using it beyond that scope, be sure to look into options for server-side rendering and do research on additional libraries and plugins you may need in order to build your application. ## AngularJS It may surprise you to learn that AngularJS is actually an older framework than Backbone, first released in 2009 by Brat Tech LLC. But the reason Angular is typically seen as the more recent entry in the field is that it didn’t take off until it came under Google’s patronage. A quick look at Google Trends show Angular (the red line in the graph below) gaining traction in early 2012 and exploding in popularity over the next few years. ![Angular google searches](/sites/default/files/styles/wide_xs/public/assets/2015-07/angular-google-searches.png.webp?itok=k0VTxesY "angular-google-searches.png") Of course this graph doesn’t tell the whole story of the relative popularity of these frameworks (it greatly undercounts React, for example), but it does give an idea of the level of attention Angular has received. This prominence has led to a robust community of contributors, but also a lot of pointed criticism that Angular fails to live up to the hype surrounding it. Coming in at 36KB when minified and zipped for production, Angular is often called a Model-View-Whatever framework because it doesn’t adhere to the typical MVC design pattern. It’s beefier than Backbone, but you also get a lot more built-in functionality, as we’ll see below. Some notable sites built with AngularJS include: VEVO, The Weather Channel and [MSNBC](https://www.lullabot.com/our-work/msnbc). ### Benefits Two-way data binding is a much loved [feature](https://docs.angularjs.org/tutorial/step_04) of AngularJS that describes the condition where data is bound to an HTML element in the View and that element has the ability to both update and display that data. In Angular, both the Model and the View can update the data, thus the “two-way” descriptor. Angular’s implementation of this form of data binding allows for a reduction in the amount of code required to create dynamic views. Another popular feature is [directives](https://www.sitepoint.com/practical-guide-angular-directives/), which allow developers to extend HTML by attaching special behaviors to parts of the DOM. For example, ng-repeat is a directive that allows developers to repeat an element, making it very handy for doing things like printing an array of items to the page. In addition to the directives that come with Angular, you can create your own, allowing for a great deal of flexibility in crafting behaviors for the UI. [Dependency injection](https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection) is another great feature of Angular that allows developers to easily include services in their modules. For example, if a developer is writing a function and wants to use the $location service (a service that parses the URL in the browser address bar), all that’s required is to include it as a parameter of the function and Angular will make sure that an instance of that service is available to that function. This is also useful for injecting mock data into components which is a feature that helps make Angular highly testable. Finally, having Google as a sponsor helps. Many enterprises (and developers) have taken the plunge on Angular because they have seen Google’s involvement as a proxy for stability, which is often a critical consideration. ### Concerns Two-way data binding! Yes, two-way data binding makes both the list of benefits and concerns. Although it does make it easier to build with Angular, people have criticized the two-way data binding because it complicates debugging and hurts performance. The fact that Angular [can be slow](https://medium.com/@jeffwhelpley/is-angularjs-fast-enough-98dcf96406c8), particularly in larger, more complex apps, undermines a major reason for using a JavaScript framework in the first place. Sluggish performance is typically seen when an application implements a complex UI, although in skilled hands this challenge can be mostly overcome. Also of concern, the upcoming Angular 2 will be a complete rewrite of the framework—no backwards compatibility. Many see this as a tacit acknowledgement on the part of the Angular team that the initial approach was flawed. It also may have undermined the perceived stability of the project among enterprises. For a very useful and informative critique of Angular, I recommend [this post](https://www.quirksmode.org/blog/archives/2015/01/the_problem_wit.html) by Peter-Paul Koch. It provides a lot of context and detailed analysis that is outside the scope of our present discussion. One final note, Angular, like Backbone, lacks server-side rendering, though workarounds exist. ### Bottom line Angular works in a wide range of use cases from small projects to enterprise applications. Nevertheless, if you’re planning a large, complex application, having skilled developers on hand who can tackle any performance issues that arise, is critical. Given the transition from version 1 to version 2, Angular doesn’t seem like a great choice at this point in time. Learning the current version of Angular may only position a developer to work on legacy applications and still require learning the upcoming version as well. Tall order! Whether it will be a good choice to take up Angular 2 remains to be seen. ## Ember Ember bills itself as, “A framework for creating ambitious web applications”, and it has an interesting pedigree. It was created in late 2011 by Yehuda Katz, who is also a member of the jQuery and Ruby on Rails core teams. Just as AngularJS feels like a framework written by Java developers (because it was), Ember is often said to be reminiscent of Rails, no doubt due to Katz’ intimacy with that project. Ember, quite proudly, does not have a corporate sponsor. It is built by a robust community of developers “scratching their own itch”. Coming in at 95KB minified and compressed for production, it is one of the heftiest of the four frameworks under discussion (jQuery and Handlebars are required dependencies that will add to that total). But with the extra size, you get a lot of built-in functionality. Websites built with Ember include: Qualcomm, Chicago.com, Nest, Vine and NBC News. ### Benefits There is a general principle within the Ember community: “convention over configuration”. When working with Ember, you should do things the “Ember way”. Pretty much everything you need to write a web app is built-in, including a templating library, routing, and tons of other things that are intended to free developers from routine and mundane reinvent-the-wheel tasks, allowing them to focus on the larger problems that are unique to their project. One interesting thing about Ember is the [Ember CLI](https://cli.emberjs.com/release/). Although it’s not required in order to work with Ember, it’s a useful command line tool that handles a lot of things people commonly use Grunt or Gulp to do—compile Sass and minify CSS and JS, for example. Maybe you don’t want to mess with your current build system, but if you don’t have one in place, this is a tool that can get you started with minimal hassle. The other thing that many developers seem to love about Ember is the fact that it doesn’t have a corporate patron and that the team behind it is deeply committed to open source software. There are some developers who look at the corporate sponsorship of other frameworks a bit warily, so this commitment from the Ember team will be a factor for those folks. ### Concerns The biggest concern with Ember is the need to do things the “Ember way”. Now, to some extent, this is similar to Angular, but Ember takes it further. Ember aspires to provide a complete solution, soup to nuts. It’s certainly a marked contrast with Backbone that allows you to mix and match things more or less as you see fit. There is also a lot of generated code with this approach that can lead to difficulty with developers understanding exactly what’s going on. When you have a large framework, with so much built-in functionality, the learning curve is steep. Ember also uses two-way data binding, although it uses a different implementation than Angular. Perhaps in response to community concerns, the Ember team has announced they will be moving away from two-way data binding in the future. ### Bottom line Ember is a good framework and in areas of weakness, there seems to be a lot of effort to improve. For example, like Angular and Backbone, it doesn’t support server-side rendering, but it has been announced that it will have that support soon, which is great. Overall, I think Ember may be best suited for teams working on medium to large projects. It is highly opinionated, so if you like to roll your own, it may not be for you. ## React The new kid on the block is also currently the hottest, stealing much of Angular’s buzz. Released in 2013 by Facebook, it takes a different approach from the other frameworks we’ve been discussing. Backbone, Angular and Ember are often referred to as client-side MVC frameworks. That doesn’t accurately describe React, however. Facebook says that React is more of the V in MVC—the View part. The rest of the pattern is flexible with React, but Facebook uses the [Flux architecture](https://facebook.github.io/flux/) to fill it out, which is a pattern best suited for larger applications. As a practical matter, plain old React will do just fine for a lot of applications. It comes in at 120KB when minified and compressed for production, making it the largest framework in terms of file size, although it doesn’t have any required dependencies. Sites using React include: Facebook, Instagram (basically one large React app), Flipboard, BBC and Netflix. ### Benefits It’s very fast - the fastest of the bunch. If you’re interested in where React gets its speed, I recommend learning more about its implementation of a [virtual DOM](https://www.youtube.com/watch?v=-DX3vJiqxm4) and [synthetic events](http://legacy.reactjs.org/docs/interactivity-and-dynamic-uis.html). Another thing developers love about React is that it’s easy to learn. Angular and Ember both have a lot of what’s called “domain specific language.” It’s part of what creates the relatively steep learning curves for those frameworks. React has significantly less of this, making it easier for developers with JavaScript experience to get a handle on. React has a component-based approach that will come quite naturally to those familiar with creating [CommonJS](https://egghead.io/lessons/nodejs-what-are-commonjs-modules) modules. Each React component represents a part of the UI - a form element, a page title, etc. These components can then be mixed and matched thus allowing for maximum code reuse. One very cool benefit of React is that you can use it to create mobile applications. That’s right, [React Native](https://reactnative.dev/) allows developers to learn React once, and use it to write both web and iOS apps (Android is coming soon). To me, this is a truly amazing feature. React also supports server-side rendering. This goes a very long way to solving the performance and SEO problems that have plagued client-side frameworks since their inception. Once the initial page load of the server rendered content has occurred, React on the client-side takes over and updates the UI based on user interaction. Having that first render done on the server eliminates the risk that search engines won’t be able to index the site while also providing faster page loads. ### Concerns The most controversial aspect of React is the absence of templates and the use of Components to generate the UI. What this essentially means is that your HTML lives inside your JavaScript. For a lot of developers this just seems…wrong. At least initially. Many come around when they realize that everything concerning a given component is located in a single place. The advantages then become clear - easier debugging, code reuse and separation of concerns. To help get your head around the React approach, Facebook has put together [a nice post](http://legacy.reactjs.org/docs/thinking-in-react.html) that walks you through it. ### Bottom line React delivers on the hype. Pretty much any size project can successfully use React and it excels in delivering on all the key things we look for in a framework while addressing the most frequent criticisms. React Native also looks like a game changer. ## Final Thoughts Perhaps you may be able to guess that I view React as the strongest framework, at least for now. I’m impressed. That said, all of them are good. That’s why they’ve risen to the top of a crowded field. JavaScript is becoming a much more important part of web development and is key to innovative approaches like decoupled—or “headless”—Drupal, an application architecture that the team here at Lullabot has helped pioneer. Ultimately, the framework you choose to invest in will come down to personal preference and the type of projects you or your team want to work on. However, if you’re a developer - particularly a front-end developer - I recommend taking the plunge and gaining expertise with one of them. Published in: - [ Front-end Development ](/topics/frontend-development) - [ JavaScript ](/topics/javascript) 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 Peer Review How-To Guide" url: "/articles/the-peer-review-howto-guide" type: article date: 2015-04-15 updated: 2015-11-19 --- # The Peer Review How-To Guide # The Peer Review How-To Guide Peer reviewing is a development strategy that improves code quality, knowledge sharing and team communication. By [ Juampy NR ](/about/juampy-nr) April 15, 2015 When we started with the [MSNBC](https://www.ms.now/) project, my colleague, [Jerad Bitner](https://www.lullabot.com/about/jerad-bitner), established a process that each ticket would be implemented in a Git branch and a [pull request](https://docs.github.com/articles/using-pull-requests) would then be created for someone on the team to review. I had done a bit of peer reviewing in the past, but this experience was totally different. In GitHub, when you are added to a repository, you get notifications for all issues and pull requests that are created, unless you change a setting. I don’t know why, but every morning while catching up on email, I started looking at all of them. I realized that apart from learning from everyone’s code and discussions I was gaining a feeling of safety by creating a mental picture of the site. This gave me a tremendous amount of confidence to chime in on pull requests, suggesting small improvements that helped with the quality and consistency of the entire codebase. Even in times when there was a lot to do, I kept looking at pull requests. I had to. I felt that if I stopped doing it I would end up duplicating code or my code wouldn't follow the same approach taken in other areas of the codebase. In this article we will see tips and examples on how to do peer reviews within a team. ## Improving code together Peer reviewing has become a habit for me and GitHub’s pull requests made this process easy and fun. They make the code visible for everyone to discuss it and they are a great chance to sneak in small improvements in the surrounding code like in the following example: ![Suggesting improvements](/sites/default/files/styles/wide_xs/public/assets/2015-11/improvement_0.png.webp?itok=f-nUCscI "improvement_0.png") The code above is correct and meets the requirements of the related ticket but here we are suggesting using a function from our codebase that achieves the same in a simpler way. Yay for standardization! ## Keeping the pace while reviewing Discussions on a peer review must be as efficient as possible or they would slow down progress. As soon as we see that we are taking too long in a discussion we would create a follow up ticket and merge what was implemented in that pull request. Here is an example: ![Creating a follow up](/sites/default/files/styles/wide_xs/public/assets/2015-11/follow-up_0.png.webp?itok=J0WcSvKX "follow-up_0.png") By creating follow ups we create an opportunity to rethink what was discussed and create a new pull request to test it and potentially merge the improvement. It keeps both project managers and developers happy as the project keeps evolving at the right pace. ## Tone to use in peer reviews The tone is very important in peer reviews. Being positive, constructive and vulnerable are key attributes to connect with someone’s work. Here is an example of typical suggestions and their feedback: ![A positive tone helps](/sites/default/files/styles/wide_xs/public/assets/2015-11/tone_0.png.webp?itok=ikJT_LAI "tone_0.png") Note the use of words like please or could. At the end, we are just making suggestions for the code to improve. Even when we find bugs in the code or missing functionality, keeping a constructive tone in our comments encourages team members to improve instead of provoking a defensive response. Some Open Source projects like Drupal reflect how its members should communicate in a [Code of Conduct](https://www.drupal.org/dcoc). ## There is always time for a laugh There are times when making a joke of how we feel about something may be the key to break the pressure of a deadline and inject good energy. Note that we should take a lot of care on when to do it and whether the author of the pull request has a sense of humor that connect with ours. Here is an example where it worked great: ![Jokes are useful](/sites/default/files/styles/wide_xs/public/assets/2015-11/iggy_0.png.webp?itok=DsRuaFqK "iggy_0.png") ## Responsibility is shared among the team By contributing to someone’s pull request with a peer review the team gains a feeling of shared responsibility over the code. It will be much harder for a bug to sneak in if there are more eyes looking at the code before it is merged into the main branch. Moreover, if there is an issue with the code there will be two or three people who can look into it and they will back each other up to fix it. Here is an example of a bug caught by peer review that never made it to production: ![Bug fixing](/sites/default/files/styles/wide_xs/public/assets/2015-11/eyes_0.png.webp?itok=3ua8iBHX "eyes_0.png") Not everyone likes to peer review though and this is fine. You can’t expect the whole team to review everyone else’s pull requests. However, having at least a couple people doing it makes a big difference. It does not need to be the senior back end and front end developers: less experienced folks will ask different questions which are valuable feedback too. ## Getting other roles involved In order to get project managers, external teams and the client into peer reviewing, we integrate the GitHub repository with Jenkins to [spin up a testing environment for each pull request](https://www.lullabot.com/articles/github-pull-request-builder-for-drupal). We can see this in action in the following screenshot where we not only get a link to the testing environment but we also see results of end to end tests: ![Tugboat comment](/sites/default/files/styles/wide_xs/public/assets/2015-11/tugboat_0.png.webp?itok=mbk7Tcxy "tugboat_0.png") This tool affords the opportunity to test something before it gets merged into the main branch for people who do not have a development environment. It saves us a lot of time to obtain feedback. ## Making code easier to read Peer reviewing starts by reading code that someone has written. If the code is easy to read, then we can go to the next step (testing it). If the code can’t be read easily, then we ask questions. A few days ago I read this quote from the book [The Pragmatic Programmer](https://pragprog.com/titles/tpp20/): > Remember that you (and others after you) will be reading the code many hundreds of times, but only writing it a few times. This quote really struck a chord with me. Our code will be read by many others (even ourselves) and therefore it has to be as clear as possible. The term clear has to be agreed within the team. Personally, I follow these steps before I ask for a peer review: 1. I check that the code is clear and meets our coding standards. 2. I check that I have commented my code enough and that comments are well written. 3. I check that the commit messages match with the changes in code. 4. Finally, I create a pull request and add testing instructions at the top for someone else to peer review it. The main point of the above list is that I want my code to be tested and merged quickly. Therefore, the easier I make it for the peer reviewer, the better chance I have to get it into the main branch and resolve the related ticket. ## It’s a learning experience for everyone Being vulnerable while peer reviewing has the benefit of learning. I have learnt a lot of things while doing peer reviews just because I asked about a particular line. For example, I had no idea of what `$(function ()` was for in JavaScript. This is how I learned about it: ![Being vulnerable](/sites/default/files/styles/wide_xs/public/assets/2015-11/vulnerable_0.png.webp?itok=VXjqp9vL "vulnerable_0.png") And there is more: when I was convinced that the function `empty()` was bulletproof, I stumbled upon this: ![empty() is not bulletproof](/sites/default/files/styles/wide_xs/public/assets/2015-11/vulnerable2_0.png.webp?itok=sKdC0nNk "vulnerable2_0.png") ## The peer review checklist Here are the guidelines that we used at the MSNBC team. If you want to try peer reviews in your team, you should get together and agree on a list of steps like the following ones: 1. Does the code follow our Coding Standards? 2. Is the code well documented? 3. Are there testing instructions on the ticket? 4. Does the code rely on our existing APIs? 5. Are all comments in the pull request resolved or discussed? 6. Does this pull request need any follow up tickets? Are they created? 7. If there is a testing environment, do all tests pass? 8. Are the requirements of the ticket met in your local environment or in a testing environment? 9. Are there database updates? Do they complete without any errors/warnings? 10. Are there any changes in Features components? Do they revert as expected? 11. Look back at the requirements — are they met in an efficient manner? Would you have architected the changes differently? ## Conclusion My aim with this article was to share how beneficial it has been for me to do peer reviews. Do you think that this applies to your project? Try it out with your team for a couple weeks and share your thoughts here with us. I am sure that you will get something good out of it. I take the chance to thank my colleague, [Jerad Bitner](https://www.lullabot.com/about/jerad-bitner) for getting the process into the team and [James Sansbury](https://www.lullabot.com/who-we-are/james-sansbury) for teaching me a lot about tone and thoroughness. Also, [Matt Oliveira](https://www.lullabot.com/about/matt-oliveira) and [Sean Lange](https://www.lullabot.com/about/sean-lange) provided great feedback while reviewing this article. Thanks also to all the bots who participated in the BoF about peer reviewing at the Lullabot retreat. Published in: - [ Community ](/topics/community) - [ Deployment ](/topics/deployment) - [ 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: "Finding related content faster with Apache Solr" url: "/articles/finding-related-content-faster-with-apache-solr" type: article date: 2014-09-11 updated: 2015-09-16 --- # Finding related content faster with Apache Solr # Finding related content faster with Apache Solr Speed up your related content lists by avoiding expensive database queries By [ Juampy NR ](/about/juampy-nr) September 11, 2014 We recently fixed a performance issue at the [MSNBC](https://www.ms.now) project: the More Like This list of content related to the current page was stressing our database servers with slow, complicated MySQL queries. Here is a screenshot of the block in question: ![Original More Like This Block](/sites/default/files/styles/wide_xs/public/assets/2015-09/more_like_this_0.png.webp?itok=QyPekL5C "more_like_this_0.png") The above block was being generated by Views using [Similar By References](https://www.drupal.org/project/similarreferences) module. The view was embedded into a Panels Content Type Plugin, similar to a Drupal block but with extra context about where it is being run. Similar By References module is easy to set up: while configuring a Views display, you add a relationship between the current node and the rest of the available content based in similarity of one or more [Entity Reference](https://www.drupal.org/project/entityreference) fields. Here is a screenshot of how the View was set up: ![View using Similar by References module](/sites/default/files/styles/wide_xs/public/assets/2015-09/view_0.png.webp?itok=mO0PtKgc "view_0.png") This block worked perfectly for the first few months after launch. Then, as the amount of content grew, we started getting alerts from New Relic due to slow performance on certain queries. The Slow Query Log at New Relic showed us just how slow they were: Extremely! ![New Relic's slow query log](/sites/default/files/styles/wide_xs/public/assets/2015-09/new_relic_0.png.webp?itok=luKkiVbL "new_relic_0.png") ## Analyzing SQL queries We started by analyzing these SQL queries to see if we could optimize them with indexes or similar tweaks. Drupal fields are stored in the database in a highly normalized way: every field has its own table. Given this fact, there was no easy way to make that query faster without radically changing the site's construction. That core problem — Drupal's heavily normalized database structure — pointed us in the direction of the solution. We needed a flattened, pre-calculated version of the content that could be sorted and filtered without complex joins. Fortunately, we already had one: the Apache Solr index that powered the site's search features. We were already indexing nodes along with their list of Topics and Issues, and we could query the same Apache Solr index to gather a list of related content for a given node. Topics and Issues are Drupal taxonomy fields that we use to classify content. They can be consistent site-wide issues like [Education](https://www.ms.now), Equality, and Health; or time-sensitive topics like Election 2014 or Dream Act. Because we had them indexed in our Apache Solr server, we thought that we could simply convert the current view into a custom Solr Query that would mimic the same logic to obtain a list of related node nids. Then, we would load these nodes and finally pass them to the theming layer to render the HTML. ## Going Solr Our first attempt was using [Apache Solr Views](https://www.drupal.org/project/apachesolr_views) module to build a view with the same conditions that we had in the original view. It looked very promising. but we saw that the resulting Solr query was not what we expected it to be and would require too much work to alter. Moreover, the resulting data needed a considerable amount of preprocessing before being passed to the theming layer. Next, we tested the More Like This block that ships with [Apache Solr Search](https://www.drupal.org/project/apachesolr) module. Unfortunately, it wasn't easy to customize the Solr query that it generated so we discarded that option as well. [Search API](https://www.drupal.org/project/search_api) module was the next solution we tried. We found out that the Acquia Stack (where MSNBC is hosted) and Search API module are not very good friends. Even though [there is a module that integrates the two](https://www.drupal.org/project/search_api_acquia), Apache Solr Search module is recommended instead of Search API because of scalability and performance reasons. ## Writing our own Solr query Given the above findings, we started studying the API of the [Apache Solr Search](https://www.drupal.org/project/apachesolr) module to generate our own Solr query mimicking our current view: Given a node, find related nodes in these content types, whose Issues or Topics match. Sort the result by Publishing Date in descending order and just return the latest 4 nids. Here is a simplified version of the PHP code we used to build that custom Solr query: ``` // Given the $node variable already contains the currently-viewed node... // Build the content types filter. $content_types = array('page', 'article'); $content_type_filter = new SolrFilterSubQuery('OR'); foreach ($content_types as $content_type) { $content_type_filter->addFilter('bundle', $content_type); } // Build the Issues filter. $issues = array(); if ($items = field_get_items('node', $node, 'field_issues')) { foreach ($items as $issue) { $issues[] = 'node:' . $issue['target_id']; } } $issues_filter = new SolrFilterSubQuery('OR'); foreach ($issues as $issue) { $issues_filter->addFilter('sm_field_issues', $issue); } // Build the Topics filter. $topics = array(); if ($items = field_get_items('node', $node, 'field_topics')) { foreach ($items as $topic) { $topics[] = 'node:' . $topic['target_id']; } } $topics_filter = new SolrFilterSubQuery('OR'); foreach ($topics as $topic) { $topics_filter->addFilter('sm_field_topics', $topic); } // Group conditions together. $main_filter = new SolrFilterSubQuery('AND'); $main_filter->addFilterSubQuery($content_type_filter); $issues_or_topics = new SolrFilterSubQuery('OR'); $issues_or_topics->addFilterSubQuery($issues_filter); $issues_or_topics->addFilterSubQuery($topics_filter); $main_filter->addFilterSubQuery($issues_or_topics); ``` That snippet was the trickiest bit: it's adding filters and grouping commands to the search to mirror the way the View was set up. Since some of the Apache Solr Search API functions can throw PHP exceptions (for example, if Solr Server is down) we wrapped the actual call to the search index in a Try/Catch statement: ``` try { // Create the query and then configure it. $query = apachesolr_drupal_query('apachesolr'); // Specify that we only want nids in the result. $query->addParam('fl', 'entity_id'); // Only return 4 matches. $query->addParam('rows', 4); // Add the above filters. $query->addFilterSubQuery($main_filter); // Sort by publish date in descending order. $sort_field = 'ds_field_publish_date_sort'; $sort_direction = 'desc'; $query->setAvailableSort($sort_field, $sort_direction); $query->setSolrsort($sort_field, $sort_direction); // Run query and render results if matches are found. list($final_query, $response) = apachesolr_do_query($query); if ($response->code == '200' && $response->response->numFound > 0) { // Extract nids from the response and load them. $nids = array(); foreach ($response->response->docs as $result) { $nids[] = $result->entity_id; } $nodes = node_load_multiple($nids); if (count($nodes)) { // Build and return the list of teasers. return theme('my_theme_callback', array('nodes' => $nodes)); } } } catch (Exception $e) { watchdog('msnbc_search', 'There was an error while processing More Like This block: %error', array('%error' => $e->getMessage()), WATCHDOG_ERROR); } ``` You can find and test the full script yourself at [this Gist](https://gist.github.com/juampy72/330bd6094274867556ec). The above script will generate the following Solr query for a node with Issues and Topics. ``` /solr/SOME_SERVER_ID/select?start=0 &rows=4 &fq=((bundle:article OR page) AND ((sm_field_issues:"node:453" OR sm_field_issues:"node:457") OR (sm_field_topics:"node:13672" OR sm_field_topics:"node:176716"))) &fl=entity_id &sort=ds_field_publish_date_sort desc &q= &wt=json &json.nl=map ``` We could say that the Solr query has some similarities with an SQL query: - It supports SQL style conditions at the fq parameter. - It can restrict the fields to be returned at fl. - It can sort results by a field at sort. - It can limit the amount of results at fq. You can find the full list of available parameters at the [Solr Wiki](https://cwiki.apache.org/confluence/display/solr/CommonQueryParameters) or by reading the API of the different classes that the Apache Solr Search module implements. ## Retrieving and theming results Our search works! Now, if we load a node which contains Issues and Topics, and we run the above script, we get the following JSON response from Solr: ``` {"response":{ "numFound":4812, "start":0, "docs":[ {"entity_id":386621}, {"entity_id":386561}, {"entity_id":386056}, {"entity_id":385996} ] }} ``` That data that's contained inside of $data->response->docs is an array of node IDs. Sweet. Just what we needed! Now we can load these nodes with node\_load\_multiple() and pass them to a theme function that will take care of rendering a list of teasers. ## Moving the request to the front end Although everything was working, we saw that the block was not only rendered on [article pages](https://www.ms.now/msnbc/franchise-association-sues-over-seattles-15-wage-msna386801), but also on [any pages with a slider](https://www.ms.now/speak-out) — and it had to reload through AJAX whenever a user scrolled to a new item. Following an approach that we took with other blocks on the MSNBC project, we decided to sidestep Drupal entirely and use AngularJS to obtain the list of related teasers and render them. This was a double bonus: AngularJS would speed up the initial page load by building the list of teasers after the main page content was sent, and we'd be able to reuse the self-contained "related content" plugin elsewhere on the site. You can find a sample of the plugin implementation at [this repository on GitHub](https://github.com/Lullabot/mltsolr). ## Conclusion When we deployed the new version of the More Like This block to production, we saw an immediate drop in server load. The following New Relic graphs show a brief spike during the deployment (when caches are cleared), and an immediate improvement in response time: ![New relic request load stats after deploying the new code](/sites/default/files/styles/wide_xs/public/assets/2015-09/new_relic_after_0.png.webp?itok=oxPsOrU_ "new_relic_after_0.png") Building dynamic lists of related content easily result in expensive queries. We hope that this article helps you when the problem occurs: it's easy to optimize them by moving them to Solr. We've also included a list of related links to help you dive deeper. Good luck! Published in: - [ Drupal Development ](/topics/drupal-development) - [ Performance and Scalability ](/topics/performance-and-scalability) - [ 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: "Importing huge databases faster" url: "/articles/importing-huge-databases-faster" type: article date: 2015-04-23 updated: 2016-03-26 --- # Importing huge databases faster # Importing huge databases faster Use parallel processing to save time importing databases By [ Juampy NR ](/about/juampy-nr) April 23, 2015 Over the past few months I have been banging my head against a problem at [MSNBC](https://www.ms.now/): importing the site's extremely large database to my local environment took more than two hours. With a fast internet connection, the database could be downloaded in a matter of minutes, but importing it for testing still took far too long. Ugh! In this article I'll walk through the troubleshooting process I used to improve things, and the approaches I tried — eventually optimizing the several-hour import to a mere 10-15 minutes. ## The starting point This was the set up when I started working on the problem: - The development server has a 5GB database with 650 tables with a total of 27,078,694 rows. - Drush 6.3.0 with sql-sync command adjusted to skip data from unneeded tables. - My local laptop’s CPU has an Intel Core i7-3630QM @ 2.40GHz and a Solid State Drive. The output of importing the development environment’s database in my local environment showed how serious the problem was: ``` juampy@juampy-box: $ time drush -y sql-sync @msnbc.dev @self --create-db You will destroy data in msnbc and replace with data from someserver/msnbcdev. Do you really want to continue? (y/n): y Command dispatch completed. [ok] real 120m53.545s ``` Wall-clock time was two hours, and it was causing a lot of frustration within the team. Having such a slow process was a big limitation when someone wanted to [peer review pull requests](https://www.lullabot.com/articles/the-peer-review-howto-guide). To avoid the delay, people were only updating their local database once a week instead of every couple days — making regressions much more likely when exporting configuration from the database into code. ## Analyzing the database My first attempt at a solution was reducing the content in the development database. If the size of the database could be reduced to about 1GB, then the import process would be fast enough. Therefore, I inspected which tables were the biggest and started analyzing what I could trim: ``` mysql> SELECT TABLE_NAME, table_rows, data_length, index_length, round(((data_length + index_length) / 1024 / 1024),2) "Size in MB" FROM information_schema.TABLES WHERE table_schema = "msnbc" ORDER BY round(((data_length + index_length) / 1024 / 1024),2) DESC LIMIT 0,15; +---------------------------------------------+------------+-------------+--------------+------------+ | TABLE_NAME | table_rows | data_length | index_length | Size in MB | +---------------------------------------------+------------+-------------+--------------+------------+ | field_revision_body | 362040 | 1453326336 | 104611840 | 1485.77 | | field_revision_field_theplatform_transcript | 45917 | 1241251840 | 49938432 | 1231.38 | | field_revision_field_homepage_item_text | 1338743 | 436125696 | 580747264 | 969.77 | | field_revision_field_homepage_item_headline | 1254389 | 264192000 | 641843200 | 864.06 | | field_revision_field_homepage_link | 947927 | 333365248 | 512671744 | 806.84 | | field_revision_field_homepage_reference | 1335704 | 177143808 | 629227520 | 769.02 | | field_revision_field_homepage_item_template | 1243619 | 177176576 | 598425600 | 739.67 | | field_revision_field_issues | 757708 | 90931200 | 295092224 | 368.14 | | field_revision_field_homepage_item_image | 584289 | 75218944 | 263585792 | 323.11 | | field_revision_field_homepage_items | 1041752 | 72515584 | 239206400 | 297.28 | | metatag | 241023 | 311394304 | 0 | 296.97 | | field_revision_field_short_summary | 467445 | 144556032 | 162529280 | 292.86 | | field_revision_field_original_topics | 387105 | 82477056 | 189874176 | 259.73 | | field_data_body | 91027 | 254656512 | 15171584 | 257.33 | | field_data_field_theplatform_transcript | 18638 | 241745920 | 11911168 | 241.91 | +---------------------------------------------+------------+-------------+--------------+------------+ 15 rows in set (2.52 sec) ``` What did I learn from that data? - `field_body_revision` was the biggest table in the database. There were many revisions, and many of them had a considerable amount of HTML. I thought about simply trimming this table and field\_body, but doing it without breaking HTML would be tricky. - `field_revision_field_theplatform_transcript` was the second biggest table. I looked at the source code to see how it was being used and asked the development team if it was needed for development and found out that I could trim this value without damaging the development experience. An easy win! - Fields used on the homepage had tons of revisions. One reason was heavy use of the Field Collection module on a multi-value Entity Reference field. Each node save created a cascade of new revisions that were good candidates for removal. ## Trimming long tables All of this was promising, and I set up a standard process for slimming down our development databases. Every night, the production database is copied into the development environment. MSNBC is hosted at Acquia; which offers a set of hooks to operate on environment operations such as copying a database, files or deploying a release. I wanted to trim down the size of the table `field_revision_field_theplatform_transcript` so I added the following statements to the [post-db-copy](https://github.com/acquia/cloud-hooks#post-db-copy) hook for the development environment: ``` # Truncate long strings for field_theplatform_transcript. drush6 @$site.$target_env -v sql-query \ 'update field_data_field_theplatform_transcript set field_theplatform_transcript_value = substring(field_theplatform_transcript_value, 0, 200)' drush6 @$site.$target_env -v sql-query \ 'update field_revision_field_theplatform_transcript set field_theplatform_transcript_value = substring(field_theplatform_transcript_value, 0, 200)' ``` The above queries trim a field of a couple tables which contains very long strings of plain text. Those steps consistently reduce the database size by 1GB. Here is a sample output when importing the development database into my local environment after this change was made: ``` juampy@juampy-box: $ time drush -y sql-sync @msnbc.dev @self --create-db You will destroy data in msnbc and replace with data from someserver/msnbcdev. Do you really want to continue? (y/n): y Command dispatch completed. [ok] real 105m32.877s ``` That reduced the total import time to an hour and forty five minutes. It was a step forward, but we still had more work to do. The next thing to try was slimming down revision data. ## Cutting down revisions Some nodes in MSNBC’s database had hundreds of revisions. developers and editors don’t need all of them but content is gold, so we can’t just go wipe them out. However, If we could cut down the number of revisions in development, then the database size would go down considerably. I looked for modules at Drupal.org that could help me to accomplish this task and found [Node Revision Delete](https://www.drupal.org/project/node_revision_delete). It certainly looked promising, but I realized that I had to put a bit of work into it so it could delete a large amount of revisions in one go. I added a Drush command to Node Revision Delete which used Batch API so it could run over a long period of time deleting old revisions. When I tested the command locally to keep just the last 10 revisions of articles, it ran for hours. The problem is that the function [node\_revision\_delete()](https://api.drupal.org/api/drupal/modules%21node%21node.module/function/node_revision_delete/7) triggers several expensive hooks, and slows the process down quite a bit. This made me to look at the production database. Did we need that many revisions? I asked the editorial team at MSNBC and we got confirmation that we could stop revisioning some content types. This was great news, as it would slow the database's future growth as well. I went one step further and configured Node Revision Delete so it would delete old revisions of content on every cron run. Unfortunately, our testing missed a bug in Field Collection module: [deleting a revision would delete an entire field collection item](https://www.drupal.org/node/2000690). This was one of the most stressful bugs I have ever dealt with: it showed up on production and was deleting fresh content every minute. Lesson learned: be careful with *any* logic that deletes content! Because of the concerns about lost content, and the fact that Node Revision Delete was still slow to do its work, we uninstalled the module and restored the deleted data. Reducing the number of revisioned content types would slow its growth, but we wouldn't try to remove historical revisions for for now. ## Deleting entire nodes from the development database Our next idea was deleting old nodes on a development environment, and sharing that slimmed down database with other developers. After testing, I found out that I could delete articles and videos published before 2013 while still leaving plenty of content for testing. To test it, I wrote a Drush script that picked a list of nids and used Batch API to delete them. Unfortunately, this was still too slow to help much. Each call to [node\_delete()](https://api.drupal.org/api/drupal/modules!node!node.module/function/node_delete/7) took around 3 seconds. With hundreds of thousands of nodes this was not a valid option either. At this point, I was out of ideas. Throughout this effort, I had been sharing my progress with other developers at Lullabot through Yammer. Some folks like [Andrew Berry](https://www.lullabot.com/about/andrew-berry) and [Mateu Aguiló](https://www.lullabot.com/about/mateu-aguilo-bosch) suggested I take a look at [MySQL parallel](https://github.com/deviantintegral/mysql-parallel), a set of scripts that break up a database into a set of SQL files (one per table) and import them in parallel using the [GNU Parallel](http://www.gnu.org/software/parallel/) project. Several Lullabot developers were using it for the [NBC.com](https://www.nbc.com/) project, which also had a database in the 5-6 GB range, and it looked promising. ## Importing tables via parallel processing Mateu showed me how they were using this tool at NBC TV, and it gave me an idea: I could write a wrapper for these scripts in Drush, allowing the other members of the development team to use it without as much setup. Coincidentally, that week [Dave Reid](https://www.lullabot.com/about/dave-reid) shared one of his latest creations on one of Lullabot's internal Show & Tell talks: [Concurrent Queue](https://www.drupal.org/project/concurrent_queue). While examining that project's code, I discovered that deep down in its guts, Drush has a mechanism to run concurrent processes in parallel. Eureka! I now had a feasible plan: a new Drush command that would either use GNU Parallel to import MySQL tables, or fall back to Drush’s `drush_invoke_concurrent()`, to speed up the import process. The result of that work was [SyncDb](https://www.drupal.org/project/syncdb), a Drupal project containing two commands: - `drush dumpdb` extracts a database into separate SQL files; one per table. Structure tables would be exported into a single file called structure.sql. - `drush syncdb` downloads SQL files and imports them in parallel. It detects if GNU Parallel is available to import these tables using as much CPU as possible. If GNU Parallel is not available, it falls back to `drush_invoke_concurrent()`, which spins up a sub-process per table import. Here is a sample output when running `drush syncdb`: ``` juampy@juampy-box: $ time drush syncdb @msnbc.dev You will destroy data in msnbc and replace with data from someserver/msnbcdev. Do you really want to continue? (y/n): y Command dispatch completed. [ok] real 13m10.877s ``` 13 minutes! I could not believe it. I asked a few folks at MSNBC to test it and the time averaged from 12 to 20 minutes. This was a big relief: although trimming content helped, making better use of CPU resources was the big breakthrough. Here is a screenshot of my laptop while running the job. Note that all CPUs are working, and there are 8 concurrent threads working on the import: ![CPU load while drush syncdb is running](/sites/default/files/styles/wide_xs/public/assets/2016-03/cpu-load.png.webp?itok=0JEfJMqm "cpu-load.png") ## Next steps and conclusion There is still a chance to optimize the process even more. In the future, I'll be looking into several potential improvements: - My friend [Pedro González](https://www.drupal.org/u/niteman) suggested me to look at [Drush SQL Sync Pipe](https://www.drupal.org/project/drush_sql_sync_pipe), which could speed up downloading the database dump. - [GNU Parallel](http://www.gnu.org/software/parallel/parallel_tutorial.html#Control-the-execution) has many options to make an even better use of your CPU resources. Andrew Berry told me that we could try using the [xargs](https://en.wikipedia.org/wiki/Xargs) command, which supports parallel processing as well and is available by default in all \*nix systems. - Get back to the root of the problem and see if we can reduce production’s or development’s database size. - Upgrade to Drush 7 in the server. Drush 7 removed the option `--skip-add-locks` when dumping a database, which speeds up imports considerably. Does importing the database take a long time in your project? Then have a look at the approaches I covered in this article. Optimizing these time consuming development steps can vary widely from project to project, so I am sure there are many more ways to solve the problem. I look forward for your feedback! Published in: - [ Drupal Development ](/topics/drupal-development) - [ Performance and Scalability ](/topics/performance-and-scalability) 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: "Should you Decouple?" url: "/articles/should-you-decouple" type: article date: 2018-04-25 updated: 2023-03-15 --- # Should you Decouple? # Should you Decouple? A Common Sense Guide to Headless Drupal By [ Andrew Berry ](/about/andrew-berry) April 25, 2018 ***Note:** This article was originally published on April 29, 2015. Following DrupalCon Nashville, we are republishing (with updates) some of our key articles on [decoupled or "headless" Drupal](https://www.lullabot.com/resource/decoupled-drupal) as the community as a whole continues to explore this approach further. Comments from the original will appear unmodified.* One of the major topics of discussion in the Drupal community has been [decoupled (or headless) Drupal](https://www.lullabot.com/resource/decoupled-drupal). Depending on who you ask, it’s either the best way to build [break-through user experiences](https://pantheon.io/blog/headless-websites-whats-big-deal) or [nothing short of a pandemic](https://www.zensations.at/en/blog/headless-drupal-cake-lie). But what exactly is a decoupled architecture? A decoupled content store splits the content of a website from how it is displayed on multiple independent systems. [Decoupled sites](https://www.lullabot.com/resource/decoupled-drupal) are the logical evolution of splitting content from templates in current CMSs. Decoupled architectures started to become mainstream with the publication of [NPR’s Create Once, Publish Everywhere](http://www.programmableweb.com/news/cope-create-once-publish-everywhere/2009/10/13) (COPE) series of articles. Other media organizations [including Netflix](https://www.lullabot.com/podcasts/insert-content-here/daniel-jacobson-on-nprs-cope-and-content-apis) have seen great benefits from a [decoupled approach to content](https://www.lullabot.com/resource/decoupled-drupal). Like many other solutions in computer science, decoupling is simply adding a layer of technical abstraction between what content producers create and what content consumers see. Technical decision makers face an important choice when evaluating Drupal 8. When an existing site is upgraded to Drupal 8, how do we decide if we should decouple the site or not? Before we decide to work on a [decoupled implementation](https://www.lullabot.com/resource/decoupled-drupal), it’s critical that *everyone*, from developers and project managers to content editors and business leaders, understand what decoupling is and how to ensure a decoupled effort is worth the technical risk. ## Why Decouple? I’ve seen many people jump to the conclusion that decoupling will solve problems unrelated to a [decoupled architecture](https://www.lullabot.com/resource/decoupled-drupal). Decoupling doesn’t mean a website will have a cleaner content model or a responsive design. Those are separate (though relevant) solutions for separate problem sets. These are the specific advantages of a decoupled architecture for a large organization: - **Clean APIs for mobile apps:** Since the website front-end is consuming the same APIs as mobile apps, app developers know that they aren’t a second-tier audience. - **Independent upgrades:** When the content API is decoupled from the front-end, the visual design of a website can be completely rebuilt without back-end changes. Likewise, the back-end systems can be rebuilt without requiring any front-end changes. This is a significant advantage in reducing the risk of re-platforming projects but [requires strict attention to be paid to the design of the content APIs](https://www.lullabot.com/articles/legally-binding-your-web-apis). - **APIs can grow to multiple, independent consumers:** New mobile apps can be created without requiring deep access to the back-end content stores. APIs can be documented and made available to third parties or the public at large with little effort. - **Less reliance on Drupal specialists:** Drupal is a unique system in that front-end developers need to have a relatively deep understanding of the back-end architecture to be effective. By defining a clear line between back-end and front-end programming, we broaden our pool of potential developers. - **Abstraction and constraints reduce individual responsibilities while promoting content reuse:** Content producers are freed from needing to worry about an exact presentation on every single front-end that consumes content. Style and layout tweaks are solely the responsibility of each front-end. Meanwhile, front-end developers can trust the semantics of content fields and the relationships between content as determined by the content experts themselves. ## Here Be Dragons At the beginning of a decoupled project, the implementation will seem simple and straight-forward. But don’t be fooled! [Decoupled architectures](https://www.lullabot.com/resource/decoupled-drupal) enable flexibility at the cost of simplicity. They aren't without risk. - **One system becomes a web of systems:** A decoupled architecture is more complex to understand and debug. Figuring out why something is broken isn’t just solving the bug, but sorting out whether the problem lies in the request or in the API itself. - **Strict separation of concerns is required to gain tangible benefits:** As front-end applications grow and change, care has to be taken to ensure that front-end display logic isn’t encoded in the API. Otherwise,[ decoupled systems](https://www.lullabot.com/resource/decoupled-drupal) can slowly create circular dependencies. This leads to systems with all of the overhead of a decoupled architecture and none of the benefits. - **Drupal out-of-the-box functionality only works for the back-end:** Many contributed modules provide pre-built functionality we rely on for Drupal site builds. For example, [the Google Analytics module](https://www.drupal.org/project/google_analytics) provides deep integration with Drupal users and permissions, "page not found" tracking, and link tracking. In a decoupled architecture, this functionality must be rewritten. Site preview (or even authenticated viewing of content) has to be built from scratch in every front-end, instead of using the features we get for free with Drupal. Need UI localization? Get ready for some custom code. Drupal has solved a lot of problems over the course of its evolution so you don’t have to—unless you decouple. - **The minimum team size is higher for efficient development:** A Drupal site with a small development team is not a good candidate for decoupling unless the content is feeding a large number of other applications. In general, decoupling allows larger teams to work concurrently and more efficiently, but doesn't reduce the total implementation effort. - **Abstraction and constraints affect the whole business:** The wider web publishing industry still has the legacy of the "webmaster". Editors are used to being able to tweak content with snippets of CSS or JavaScript. Product stakeholders often view products as a unified front-end and back-end, so getting the funding to invest in building excellent content APIs is an uphill battle. Post-launch support of decoupled products can lead to short-term fixes that are tightly coupled, negating the original investment in the first place. ## The Heuristic To help identify when decoupling is a good fit for a client, Lullabot uses the following guidelines: **Decoupled architectures may be appropriate when:** 1. The front-end teams require full freedom to structure and display the data. 2. The front-end team does not have Drupal expertise. 3. More than one content consumer (such as a website and multiple mobile apps) is live at the same time. 4. Display front-ends combine data from multiple distinct API sources like CMSs, video management systems, and social media. 5. A project consists of multiple development teams. If a project meets some of these criteria, then we’ll begin a deep-dive into what decoupling would require. - Does decoupling also require a complete content rewrite, such as when migrating from legacy "full-page" CMSs? We’ve encountered sites that haven’t made the move to structured data yet and still consist primarily of HTML “blobs.” This scenario presents a significant hurdle to decoupling, though it’s a separate problem from decoupling. - Does the development team have the time needed to build and document a content API with something like [Swagger](https://swagger.io)? Or is using Drupal as a site building (but coupled) development framework a better fit? - Does the web team consist primarily of Drupal developers, and will those developers continue to support the website in the future? Would the front-end team be better served by Views, Panels and the theme layer, or by a [pure front-end solution like React or Angular](https://www.lullabot.com/articles/choosing-the-right-javascript-framework-for-the-job)? - Is there enough value in decoupling that the business is willing to change how they work to see its benefits? [Decoupled architectures](https://www.lullabot.com/resource/decoupled-drupal) are a great solution - but they’re not the **only** solution. Some of the [best websites](https://www.syfy.com/) are built with a completely coupled Drupal implementation. It’s up to us as technical leaders and consultants to ensure we don’t let our excitement over an updated architecture get in between us and what a client truly needs. *Header image by Daniel Schwen [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), from [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Train_coupling.jpg)* Published in: - [ Decoupled Drupal ](/topics/decoupled-drupal) - [ Digital & Content Strategy ](/topics/content-strategy) - [ JavaScript ](/topics/javascript) 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: "Beyond Decoupling: The Inherent Virtues of an API" url: "/articles/beyond-decoupling-the-inherent-virtues-of-an-api" type: article date: 2015-04-30 updated: 2017-08-23 --- # Beyond Decoupling: The Inherent Virtues of an API # Beyond Decoupling: The Inherent Virtues of an API Your API as a communication interface By [ Mateu Aguiló Bosch ](/about/mateu-aguilo-bosch) April 30, 2015 Fellow Lullabot [Andrew Berry](https://www.lullabot.com/about/andrew-berry) has written [an article on why to decouple](https://www.lullabot.com/articles/should-you-decouple). If you do go this route, it’s because you’ve thought a lot about how to separate concerns. Content consumers are separated from content producers. Front-end developers are freed from the dictates of a back-end CMS. This article isn't about the separation of concerns, but rather what lies at the middle of all of these concerns— your HTTP API. In fact, you'll find that in a decoupled project the HTTP API provides not only the middleware, but also the middle ground. Let me explain. ## The communication interface One of the important things to understand is that [an HTTP API is not just a way to expose your data](https://www.lullabot.com/articles/legally-binding-your-web-apis) or content from a CMS; nor is it a way to workaround the complexity of making views for the front end. An HTTP API is an **interface**, the clearly defined contract between two sides of a data transaction that takes into account the needs and limitations of both providers and consumers. Done right, information can flow freely from the content management system to the end-user through many channels. Done wrong, circular dependencies or poorly defined object schemas impinge upon the free flow of information. To enable this “flow”, we need: - A common language—a vernacular—that allows every actor in the system a voice. This language should not be restricted to developers. Product teams, project managers, designers, stakeholders, and so on will influence decisions about the API. These decisions may come through the content model, which is built on the needs of the business. The people making these decisions might not be technical. You will want to re-use the nomenclature from the content model in your API, so a front-end developer, familiar with the API, can have a clear conversation with a non-technical business stakeholder. - The delegation of responsibilities. When collaborating across disciplines it’s easy to generate friction. Decisions about the API will affect who’s responsible for what, and all sides will have a claim. For instance, pagination requires a tricky balance between front-end and back-end performance. Make sure to think about these areas carefully so everyone knows what they’re responsible for. - Clear goals for the API. These goals can be written in such a way that they form the basis for [test-driven development](https://agiledata.org/essays/tdd.html). - Expectations to be set. Good, human-readable API documentation means that your front-end team can begin work while the back-end implementation of the API is still in progress. This approach provides iterative feedback to the API developers. The API consumers get to try out the API as it's being developed and quickly discover any gaps or design bugs. Everyone wins! ## Defining the middle ground Collaborating on requirements with other human beings is usually the most difficult component of a project. Machine-oriented description languages (JSON, YAML, and so on) are not languages we can use to speak to each other. We’ve just talked about the importance of a vernacular, but how do we put that to practical use? In his article “[API Best Practices: Spec-Driven Development](http://blogs.mulesoft.org/api-best-practices-series-spec-driven-development/)”, Mike Stowe writes: “One of the quickest ways to kill your API is to define the API in your code, instead of coding to its definition.” **Document and define your API first**. There are tools to translate human-readable API documentation to their machine-friendly counterparts. Consider [Apiary's Blueprint](https://apiary.io/blueprint) format, or, for a more technical approach (that also introduces testing), explore [postman](https://www.postman.com/docs/jetpacks_writing_tests), [Swagger](http://swagger.wordnik.com/), or [RAML](https://raml.org/). Too complicated for your stakeholders? Consider the esperanto of computer science: spreadsheets. The following caption shows an example of a Season resource being described using the Blueprint format. ![Apiary editor](/sites/default/files/styles/wide_xs/public/field_regular_upload/apiary2.png.webp?itok=jl8LtCDV "apiary2.png") That documentation is then rendered by Apiary in a more human-readable way. ![Apirary docs](/sites/default/files/styles/wide_xs/public/field_regular_upload/apiary1.png.webp?itok=wr-8dM5x "apiary1.png") Whatever approach you decide on, make that documentation—that definition—the **system of record**. That will prevent the troublesome spread of conflicting information via the ticketing system or your email inbox. Having shared documentation among a big team will introduce the usual problems. Consider version control for versioning and diffing, communication channels for discussion and change notifications, and a permissions system that is flexible across each stage of the API design. Choose someone to set this all up. You will want someone to draft a first approach for everyone to build from. This person may then become the gatekeeper if you decide to have one. GitHub, BitBucket and other web-based user interfaces for Git are good solutions. ## The inherent virtues Building from an API places decisions in the middle ground, avoiding favoring one specific team while also compartmentalizing the decision scope. This minimizes the probability of the ripple effects of a decision disproportionately affecting the API implementation or its consumers. An API, like a contract, shields both parties. There are many reasons why you will want to build your next project to be API-centric. When it comes to what the needs are and how to communicate to all the stakeholders involved, you want everyone to have a common language to express their requirements. This enables us to use our documentation tools and communication interface to implement each requirement in the API. When making technical decisions, your entire team will be able to effectively communicate using the API as a natural interaction point, reducing conflicts and helping to get your project out the door. Published in: - [ Drupal Site Building ](/topics/drupal-site-building) - [ Front-end Development ](/topics/frontend-development) - [ Decoupled Drupal ](/topics/decoupled-drupal) 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 XDebug on OSX Mountain Lion" url: "/articles/configuring-xdebug-on-osx-mountain-lion" type: article date: 2012-10-17 updated: 2015-05-01 --- # Configuring XDebug on OSX Mountain Lion # Configuring XDebug on OSX Mountain Lion By [ Angus Mak ](/about/angus-mak) October 17, 2012 Using a PHP Debugger is a great way to effectively debug your code. A debugger gives you the ability to trace your code line-by-line, with the call stack and all the variables available for inspection at run-time. In previous versions of Mac OS X, installing Xdebug could be a hassle. The recently release Mountain Lion version makes it easy by shipping with many of the tools we need for PHP debugging. ## Setting up Xdebug OSX Mountain Lion conveniently ships with Xdebug. If you're using the earlier OSX Lion version, you can download and install Xdebug with [PECL](https://pecl.php.net/) (PHP Extension Community Library) or [Homebrew](http://mxcl.github.com/homebrew/). Once you have Xdebug installed, we need to make it available to PHP by editing the php.ini file. To do that, open up /etc/php.ini with your text editor. If /etc/php.ini doesn't exist, create a copy of the default file with the following command: ``` sudo cp /etc/php.ini.default /etc/php.ini ``` Then find and uncomment the following lines in /etc/php.ini ``` zend_extension="/usr/lib/php/extensions/no-debug-non-zts-20090626/xdebug.so" xdebug.remote_enable=1 ``` Restart Apache, then make sure Xdebug is loading by looking at phpinfo. ``` ``` ![phpinfo](https://www.lullabot.com/sites/default/files/field_regular_upload/phpinfo.png) ## IDE Finally, we have to tell the IDE how to communicate with Xdebug. In this example, I'm using [PhpStorm](https://www.jetbrains.com/phpstorm/) but you can use any compatible IDE. Start by setting up a new project. Once you have it set up, we will set up the debugger here. ![Edit Configuration](https://www.lullabot.com/sites/default/files/field_regular_upload/edit_configuration.png) First, add a new PHP Web Application ![PHP Web Application](https://www.lullabot.com/sites/default/files/field_regular_upload/web_application.png) Add a new server, your Host will be different depending on your setup ![Server](https://www.lullabot.com/sites/default/files/field_regular_upload/new_server.png) Your configuration should look something like this, where http://drupal7.local/ is where your drupal instance is ![Config](https://www.lullabot.com/sites/default/files/field_regular_upload/config.png) Now we can run the Debugger ![Debug](https://www.lullabot.com/sites/default/files/field_regular_upload/debug.png) The browser should now open up http://drupal7.local?XDEBUG\_SESSION\_START=xxxxx and the debugger should open up in Phpstorm. To make sure the debugger is working properly, let's set a break point in index.php. ![Breakpoint](https://www.lullabot.com/sites/default/files/field_regular_upload/breakpoint.png) Refresh the browser and the debugger should invoke and stop at the break point. ![Trace](https://www.lullabot.com/sites/default/files/field_regular_upload/trace.png) We're now ready for some PHP 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: "Front-End Web Development Fundamentals" url: "/articles/frontend-web-development-fundamentals" type: article date: 2015-05-05 updated: 2023-10-26 --- # Front-End Web Development Fundamentals # Front-End Web Development Fundamentals New Book Helps Demystify Best Practices By [ Joe Fender ](/about/joe-fender) May 5, 2015 *DrupalCon: Carwin Young, co-author of Front-End Fundamentals, will be giving a session at DrupalCon LA 2015 on [The Why and How of Front End Architecture](https://events.drupal.org/losangeles2015/sessions/why-and-how-front-end-architecture). Come by and say hi!* I started building websites, like many of us, as a back-end developer. I spent many delightful years developing with PHP and Drupal. However, the projects that I started working on in 2011 allowed me to begin playing with various front-end technologies. In early 2012, I successfully launched [BracketCloud](http://www.bracketcloud.com) which was built with Backbone.js, Drupal and Node.js. Moving forward and eventually into 2013, my exposure to the front-end continued to grow. I worked on several projects that involved a lot of front-end development such as our very own [Drupalize.Me](https://drupalize.me/) and more recently the [MSNBC](https://www.ms.now/) site. When I look back to the beginning of my journey to becoming a front-end developer, I wish that someone had been there to hand me a list of all the popular tools that I should probably experiment with or at least be aware of. Instead, I stumbled blindly through an overgrown forest of JavaScript plugins and frameworks, trying to figure out for myself how a front-end developer was supposed to code. That being said, I love teaching myself new things and I thoroughly enjoyed the experience. One of my 2013 new year resolutions was to write a book on front-end development that would help people with the learning curve. There was a lot to consider as I began to draft out the book structure and I went through many iterations of the topics I wanted to cover. At our annual Design & Developer retreat here at Lullabot I talked with [Carwin Young](https://www.lullabot.com/about/carwin-young), our Senior Front-End Developer, about my progress and as he shared his thoughts it became obvious that his experience and knowledge would be invaluable to the book. We decided to co-author the book together and expand its scope. One year after conception, we are extremely pleased to announce the release of [Front-End Fundamentals](https://leanpub.com/front-end-fundamentals)! Front-End Fundamentals introduces the tools and fundamentals of front-end development practices and workflows. In the book we cover topics such as JavaScript frameworks, CSS styling, dependency management and task automation. You can grab it in your favorite digital format on [Leanpub.com](https://leanpub.com/front-end-fundamentals). For a 25% discount, please use this link: 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: "Lullabot's 7th Annual DrupalCon Party" url: "/articles/lullabots-7th-annual-drupalcon-party" type: article date: 2015-05-05 updated: 2015-05-05 --- # Lullabot's 7th Annual DrupalCon Party # Lullabot's 7th Annual DrupalCon Party Join us poolside at The Hotel Figueroa By [ Jeff Robbins ](/about/jeff-robbins) May 5, 2015 Lullabot's annual party has become a DrupalCon tradition – fun friendly people hanging out and having a good time. If you're new to DrupalCon, it's a great place to meet people. If you're an old-timer like most of us, it's a great place to see old friends and make new ones. **Lullabot's DrupalCon Party 2015** Wednesday, May 13th [Hotel Figueroa](http://hotelfigueroa.com) 939 S Figueroa St. Los Angeles, CA 90015 7PM ‘til whenever (just one block from DrupalCon) Lullabot is sending 46 people to DrupalCon this year. Eleven of them are presenting [sessions](https://www.lullabot.com/articles/drupalcon-2015-lullabot-sessions), so don't miss those. Also, both Lullabot and [Drupalize.Me](https://drupalize.me/) will be represented with booths (407 & 411) in the exhibit hall. We'll have our famous floppy disk party invites at the booth, so stop by early on Tuesday if you want to fill out your collection. The venue for the party is just one block from Los Angeles Convention Center. So stop by on Wednesday evening, have a beer, and say "hello!" 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: "Jen Simmons on Usability, CSS3, and Getting Bartik into D7" url: "/podcasts/drupal-voices/jen-simmons-on-usability-css3-and-getting-bartik-into-d7" type: episode date: 2010-05-04 updated: 2014-04-09 --- # Jen Simmons on Usability, CSS3, and Getting Bartik into D7 # Jen Simmons on Usability, CSS3, and Getting Bartik into D7 *Podcast episode player* [Jen Simmons](https://twitter.com/jensimmons) is on a mission to get a new theme into Drupal 7 within the next 12 days called [Bartik.](http://drupal.org/project/bartik) Drupal 7 core co-maintainer [Angie Byron said in the issue queue](http://drupal.org/node/683026#comment-2879342) that "the deadline for new core themes is **May 17, 2010** to allow for a few days to squash any additional bugs before the beta." In this interview, Simmons talks about the evolution of Bartik and how [she'd love to get some help](http://drupal.org/node/784900) in the [Bartik issue queue.](http://drupal.org/project/issues/bartik) There's also a [demo site](http://bartik.milkweedmediadesign.com/) that highlights the different features of the theme. If you'd like to see a new theme in Drupal 7, then be sure to jump in and get involved. Simmons also talks about her [Drupal Core Developer Summit](http://sf2010.drupal.org/conference/core-developer-summit) presentation on ["Why Clicking Buttons in Drupal Sucks"](http://extras.jensimmons.com/drupalconsf2010/buttons_in_drupal.pdf) that calls for the creation of a Drupal Module Developers User Experience Guide. Simmons also gave a DrupalCon presentation on [CSS3: The Future is Now,](http://sf2010.drupal.org/conference/sessions/css3-future-now) and she gives some highlights from [her presentation](http://extras.jensimmons.com/drupalconsf2010/css3.pdf). --- --- title: "James Walker" url: "/about/james-walker" type: bio date: 2016-04-07 updated: 2023-06-13 --- # James Walker # James Walker Former Director of Education ## Featured resources by James [ More Resources ](/resources) [### Do you wanna be Lullabot Drupal Certified? Read ](/articles/do-you-wanna-be-lullabot-drupal-certified) --- --- title: "Tim Smith" url: "/about/tim-smith" type: bio date: 2016-04-07 updated: 2023-06-13 --- # Tim Smith # Tim Smith Former Development Consultant ## Featured resources by Tim [ More Resources ](/resources) [### Retreats the &#039;Bot Way Read ](/articles/retreats-the-bot-way) [### Humility in Design Read ](/articles/humility-in-design) [### Constructive Criticism Read ](/articles/constructive-criticism) --- --- title: "Eric Duran" url: "/about/eric-duran" type: bio date: 2016-04-07 updated: 2023-06-13 --- # Eric Duran # Eric Duran Former Senior Developer [Eric Duran](https://www.linkedin.com/in/ericduran) is a former Senior Developer at Lullabot. --- --- title: "Hunter MacDermut" url: "/about/hunter-macdermut" type: bio date: 2017-05-17 updated: 2025-03-10 --- # Hunter MacDermut # Hunter MacDermut Senior JavaScript Developer Chapel Hill, NC For whatever reason, Hunter studied HTML in English class in 6th grade. He didn’t question why at the time because it meant he got to spend a portion of every otherwise dreaded school day in front of a computer, making interesting things appear on the screen. This activity was a perfect complement to his love of board and video games; rules dictate the constraints, but within those constraints, there’s no limit on creativity. Once he reached high school, Hunter's attention turned toward playing music. As a drummer, piano player, and, eventually, a guitarist, he found that same sense of satisfaction; guidelines dictate what notes and chords sound nice together, but infinite possible combinations exist. Hunter began giving guitar lessons after several years of playing and discovered a passion for teaching that rivaled his passion for music. When it came time to create a website for his teaching business, those latent HTML skills and his general voraciousness for learning propelled him into a career in front-end web development. Hunter learned the tools of the trade: HTML, CSS, and JavaScript. After much tinkering, he completed his site and realized that he wanted to work on websites full-time. He’s never looked back. Following stints working on enterprise-level Python and JavaScript React projects for Caktus Group and Cox Media Group, Hunter found Lullabot. The culture of constant learning, open, ego-free communication, and a distributed workplace model have proven to be a perfect match. The fact that so many Lullabots are also musicians was the icing on the cake. While at Lullabot, Hunter built widgets for IBM's Widget Registry system, some of which are key elements in their various product homepages. He also maintained and improved IBM's Widget Registry system, which provides CMS-agnostic functionality, allowing for an easier transition from Drupal to Adobe Experience Manager. Hunter lives in Carrboro, NC, where he enjoys playing jazz guitar, climbing fake rocks, and completing video games with pixelated graphics. ## Did you know? Hunter saves the best for first. ## More about Hunter ### Drupal Contributions - DrupalCon Seattle 2019 - [... and more.](https://www.drupal.org/u/huntermacd) ### Projects: - [Syfy](https://www.lullabot.com/our-work/syfy) - [IBM Cloud](https://www.lullabot.com/our-work/ibm-cloud) - The Recording Academy (Grammys) - Mastercard - Rand Corporation ## Featured resources by Hunter [ More Resources ](/resources) [### Hunter&#039;s Habits for Healthy Hands Read ](/articles/hunters-habits-healthy-hands) [### Decoupled Drupal: Getting Started with Gatsby and JSON:API Read ](/articles/decoupled-drupal-getting-started-gatsby-and-jsonapi) [### Eat This, It’s Safe: How to Manage Side Effects with Redux-Saga Read ](/articles/eat-this-its-safe) --- --- title: "Impact and Generosity in the Drupal Community" url: "/articles/impact-and-generosity-drupal-community" type: article date: 2020-05-13 updated: 2020-05-13 --- # Impact and Generosity in the Drupal Community # Impact and Generosity in the Drupal Community As the pandemic continues to spread, many of us are forced to reassess our values and our place in the world. Now is an especially good time to take a closer look at Drupal's values and principles. By [ Matthew Tift ](/about/matthew-tift) May 13, 2020 As the global pandemic continues to spread — causing widespread sickness and death, restricting in-person human contact, creating additional responsibilities at home or financial hardships, or any of the countless other changes to daily life that have resulted in feelings such as fear, anger, boredom, or uncertainty — this virus has forced some of us to reassess our values and our place in the world. While the majority of us who participate in the Drupal community remain focused squarely on technical issues, others might find now is an especially good time to take a closer look at Drupal's [Values and Principles](https://www.drupal.org/about/values-and-principles). For those of us privileged enough to have the time and space to consider more philosophical questions, we can ask if Drupal's stated values (still) align with our values, or even consider the role of Drupal in our lives when the pandemic subsides. This article — the first in a series of articles exploring Drupal's values and principles — considers Drupal's [first principle](https://www.drupal.org/about/values-and-principles#impact-gives-purpose), "impact gives purpose," which is one aspect of the first value, "prioritize impact." On one level, the first principle is merely practical. It concludes by prioritizing the "stakeholders" we should consider: "When faced with trade-offs, prioritize the needs of the people who create content (our largest user base) before the people who build sites (our second largest user base) before the people who develop Drupal (our smallest user base)." In its simplest form, this principle tells us that Drupal ranks the needs of content creators before the needs of the developers. However, the first principle offers considerably more depth. While acknowledging the practical nature of the Drupal software, it calls on us to aspire to a higher goal: "When contributing to Drupal, the goal is often to optimize the project for personal needs ('scratching our own itch'), but it has to be bigger than that." Thus, Drupal is presented as much more than simply a good product. The phrase "scratching our own itch" has become a platitude. It's everywhere. The [Harvard Business Review](https://hbr.org/2014/05/when-scratch-your-own-itch-is-dangerous-advice-for-entrepreneurs) called it "one of the most influential aphorisms in entrepreneurship." The phrase is well known among software developers in part because in his influential 1999 book, *The Cathedral and the Bazaar,* (the [highly](https://www.linux-magazine.com/Online/Blogs/Off-the-Beat-Bruce-Byfield-s-Blog/The-Decline-and-Fall-of-Eric-S.-Raymond)[ controversial](https://lunduke.com/posts/2020-03-9-b/)) Eric S. Raymond wrote, "Every good work of software starts by scratching a developer's personal itch." In the Drupal community, however, we see ourselves as aspiring to much more. As the first principle states, "Slowly, focus shifted from writing the perfect code to growing the project, and amplifying Drupal's impact through better marketing, user experience, and more." Countless individuals and Drupal subgroups express their desire to impact people. For instance, the Drupal agency Palantir prioritizes [impact](https://www.palantir.net/about-us) that is "positive," "lasting," "thoughtful," and "deliberate." Over at ThinkShout, a Drupal agency that works "with courageous organizations that put people and the planet first," the "impact" they aspire to in their first [core value](https://thinkshout.com/about/) "is driven by our sense of connectedness and desire to deliver meaningful, measurable results." Countless individuals and organizations in the Drupal community feel motivated by a sincere desire to positively "impact" other human beings. Drupal's first principle is especially ambitious in describing the impact of the Drupal community: "Prioritizing impact means that every community member acts in the best interest of the project." It seems unlikely that "every community member" can or should make the Drupal project their top priority. Though it may be idealized, it's a worthy goal. We also must reiterate that people will necessarily begin with their own needs. Contributions to the Drupal project should not come at personal expense. Imagine telling a single parent, who recently lost their job and wants to build a career with Drupal, to consistently act "in the best interest of the project." Change should come from individuals who have the capacity to help others. Part of why some of us contribute to Drupal is because we imagine another human being finding value in our work. We do not require those who benefit to give back. In this idealized form, we encourage people to participate, but we give with an open hand and no expectation of reciprocation. We contribute because we believe our actions have meaning. As the first principle states, "We derive meaning from our contributions when our work creates more value for others than it does for us." When we look inward to examine our value systems, we probably do not want to find a heap of clichés, and phrases like "prioritize impact" and "create value for others" might sound rather cliché to some ears. In fact, on various lists of "business buzzwords," the word "impact" takes [the top slot](https://www.inc.com/john-boitnott/you-still-need-to-use-these-20-smart-business-buzzwords.html). The noun "impact" comes from the Latin *impactus*, which means "collision" or "striking one thing against another." The cultural and historical context of "impact" doesn't negate its usefulness, but if the real goal is to "derive meaning," it might be helpful to reconsider this principle in more human terms. As previously noted, much of Drupal's first principle points toward bigger goals that extend beyond the conference room to a human-centered skill that good people work to cultivate: generosity. We seek to help others, both at home and in our careers. The business-friendly language in the first principle like, "maximize the impact the project can have on others," could, for at least some of us, be read as "practice generosity toward others." We seek to use [Drupal for Good](https://groups.drupal.org/drupal-for-good) or even live in (with) [Drutopia](https://www.drutopia.org/). Thanks to Drupal and its community, some of us possess the fortunate capacity to help others. If that describes you, then consider in what ways you have the opportunity to be generous. Toni Morrison — the iconic writer, activist, and college professor who became the first African-American woman to win the Nobel Prize in Literature — used to [tell her students](https://www.oprah.com/omagazine/toni-morrison-talks-love/4): "When you get these jobs that you have been so brilliantly trained for, remember that your real job is that if you are free, you need to free somebody else. If you have some power, then your job is to empower somebody else. This is not just a grab-bag candy game." In this case, Morrison's inspirational words apply not just to students, but to countless people in the Drupal community. Many in our community have freedom and power. We have the opportunity to help others. Help [other Drupalers](https://www.drupal.org/project/issues). Help [kids](https://2015.drupalcampcolorado.org/session/teaching-kids-code-next-gen-drupalers). Help the [homeless](https://events.drupal.org/nashville2018/sessions/homeless-addict-winning-drupal-how-community-saves-lives). Help anyone in need. Maybe even help Drupal and give to [\#DrupalCares](https://www.drupal.org/association/blog/drupalcares-thanks-to-drupal-businesses-you-can-now-triple-your-impact). If your actions produce positive results, keep going! Ultimately, action matters more than language. Whether you feel motivated by the desire to make an impact, or you want to practice generosity, don't let up because the world has changed. Take another look at Drupal's [Values & Principles](https://www.drupal.org/about/values-and-principles) and determine for yourself if they motivate you to action. This is not just a grab-bag candy game. Published in: - [ Community ](/topics/community) ## More Resources [ See all ](/resources) [### Drupal Community Leadership Listen ](/podcasts/drupalizeme-podcast/drupal-community-leadership) [### Sponsoring Drupal Contributions at Lullabot Read ](/articles/sponsoring-drupal-contributions-lullabot) [### Behind the Screens with Jen Witkowski Listen ](/podcasts/drupal-voices/242-jen-witkowski) 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: "Alex Nofer" url: "/alex-nofer" type: bio date: 2020-10-05 updated: 2023-06-13 --- # Alex Nofer # Alex Nofer Business Development Lead Denver, CO Alex is passionate about developing new business and contributing to a company's overall success and growth. Having worked with various clients in the technology, nonprofit, insurance, travel, and retail industries, Alex's primary motivation is to help brands reach their goals, not just sell them a product or service. Before landing at Lullabot, Alex was responsible for revenue growth at Delve, a top-rated Google partner that empowers marketing teams with integrated Google solutions. While there, Alex established solid relationships with clients and established Delve's business development operations, a foundation that has provided continued success for the sales team. An avid golf fan, Alex managed operations for the PGA of America's PGA and Senior PGA Championships before finding his way into sales. In his spare time, he also enjoys writing and producing music. ## Did you know? Alex won a club championship in Golf, he has tripled jointed thumbs, and is a licensed fork lift operator. --- --- title: "Morgan Eck" url: "/about/morgan-eck" type: bio date: 2022-11-22 updated: 2025-03-10 --- # Morgan Eck # Morgan Eck Front-end Developer Boise, ID Morgan Eck is a developer who enjoys delivering accessible, responsive, pixel-perfect code. After dabbling in WordPress early on, she found Drupal and has been hooked ever since. After spending nearly a decade in marketing, Morgan returned to college and received a Bachelor of Science degree in Computer Science. In a previous role, she established the IT department, which included setting up things like an Apple ordering portal for devices, Apple Business Manager, and Kandji (which is a mobile device management platform). In the short time since graduating, she transitioned from her role as an IT administrator to that of a full-stack developer, then on to a more specialized role in front-end. Morgan has worked on big and small sites, completing tasks ranging from bug fixes and module updates to migrations and rebuilds. In her mind, there’s nothing better than delivering a product that makes everyone involved happy. Morgan lives in Boise, Idaho, with her husband and her miniature dachshund, Pippin. When she’s not coding, she can be found traveling, reading, or tending to her garden (which she hopes to expand into a food forest one day!). She also loves to draw and paint and even created the most recent album cover for her husband’s band. ## More about Morgan ### Drupal Contributions: - Core: Olivero - Fancy File Delete: deprecation - IndieWeb: deprecation - Paragraph Blocks: deprecation, documentation - Role Paywall: deprecation - Session-Based Temporary Storage: Formatting ### Projects Worked on at Lullabot: - Iowa.gov ### Education and Certification: - Bachelor of Arts in Journalism, University of Nevada-Reno - Bachelor of Arts in Women's Studies, University of Nevada-Reno - Bachelor of Science in Computer Science, Oregon State University ## Featured resources by Morgan [ More by Morgan ](/resources?author=9608) [### The Shadow DOM {Frontend.Darkside} Listen ](/podcasts/lullabot-podcast/shadow-dom) [### Directing How Single Directory Components Directly Simplify Theming Listen ](/podcasts/lullabot-podcast/sdc) [### Just Say Drupal‽ Listen ](/podcasts/lullabot-podcast/just-say-drupal) --- --- title: "Drupal: An Overview" url: "/resource/drupal" type: page date: 2023-08-15 updated: 2025-01-14 --- # Drupal: An Overview # Drupal: An Overview Table of contents: - [What is Drupal?](#what-is) - [What can be built with Drupal?](#what-can) - [Who benefits the most from using Drupal?](#who-benefits) - [Who uses Drupal?](#who-uses) - [Misconceptions and misunderstandings about Drupal](#misconceptions) - [Is Drupal an accessible platform?](#is-accessible) - [How secure is Drupal?](#secure) - [Migrating to Drupal](#migrating) - [A brief history of Drupal](#history) - [What is the current version of Drupal](#version) - [What is the difference between Drupal CMS and Drupal core?](#drupal-cms) - [Should you use Drupal for your next project?](#project) - [Is Drupal the same as Acquia?](#acquia) - [How to become a Drupal developer](#developer) ![Drupal logo](/sites/default/files/styles/max_900/public/2020-05/new-project.png.webp?itok=oL0UZfbo) ## What is Drupal? Drupal is an open-source content management system (CMS). Out of the box, it allows you to create websites and manage your content through a simple administration interface. However, because of Drupal’s flexibility and extensibility, it’s more like a content management *framework*. Drupal allows you to construct your own CMS suited for the needs of your business or organization. It has [structured content](https://www.lullabot.com/articles/benefits-structured-content) capabilities that are best in class, and you can implement simple and complex content models, [relating different pieces of content to each other in creative ways](https://www.lullabot.com/articles/microsites-drupal). You can implement editorial workflows that scale to the size of your team. You can integrate with external services to pull content in or push content out. [You can even use Drupal as a backend to power a decoupled ecosystem of applications](https://www.lullabot.com/resource/decoupled-drupal). And so much more. From [Drupal.org](https://www.drupal.org/about): > It's also a great choice for creating integrated digital frameworks. You can extend it with any one, or many, of thousands of add-ons. Modules expand Drupal's functionality. Themes let you customize your content's presentation. Distributions are packaged Drupal bundles you can use as starter-kits. Mix and match these components to enhance Drupal's core abilities. Or, integrate Drupal with external services and other applications in your infrastructure. No other content management software is this powerful and scalable. ## What can be built with Drupal? Drupal can power any type of website. Blogs, community hubs with social features, [marketing platforms](https://www.lullabot.com/our-work/new-relic) for all sorts of organizations, conference websites, [ecommerce](https://www.lullabot.com/webinars/drupal-saas-flexible-websites-hundreds-bookstores) platforms, and media-heavy destinations like newspapers. You can also use it to power systems of sites for governments or higher education institutions. Drupal’s content modeling capabilities, which allow you to add whatever kind of field you want to various types of entities, offer a solid foundation for all sorts of use cases. Combine it with Drupal’s built-in Views, which allow site builders and developers to rapidly create dynamic lists of content based on any criteria, and you get lots of possibilities. It does some of these things better than others. For example, if you want a simple blog or personal website, Drupal might be excessive, and you won’t find many ready-to-go themes, at least in comparison to WordPress. Instead of a bound book ready for you to read, you are given the paper, and then you must choose the binding material and cover so you can create your own book. For many, that might be exactly what they want. Similarly, you can also develop applications quickly with Drupal, as long as you know what you’re doing. Because Drupal provides so much functionality out-of-the-box, it can help you build a working prototype relatively quickly (If you’re familiar with Drupal’s patterns and ecosystem). However, in the long term, you might be better served with a pure application framework for building pro, like Symfony or Laravel, or one of the many JavaScript-based frameworks. So who is Drupal for? Who gets the most potential upside of investing in a Drupal solution? ## Who benefits the most from using Drupal? Organizations whose website is strategic to their success and where they value ownership and control of their own website technology stack benefit the most from investing in Drupal. Its [multilingual capabilities](https://www.drupal.org/features/multilingual) make it popular with global organizations that need to provide content in two or more languages. There are proprietary CMSs available that could power their websites, but they wish to avoid vendor lock-in, high licensing fees, and the inability to customize things on their own terms. There are also other open-source projects that could power their websites, but with Drupal, they get unparalleled flexibility, ready-to-use content APIs, and [robust security](https://www.drupal.org/drupal-security-team). Its [community](https://www.drupal.org/community) is large and active. Drupal has also been validated in the enterprise space for over a decade. If your website is central to your core business, you need to manage a lot of content, and have large content teams collaborating to publish that content, Drupal is a great choice. ## Who uses Drupal? Drupal has proven itself so versatile that many different types of organizations use it to power their websites. - Governments like the [State of Georgia](https://www.lullabot.com/our-work/govhub-building-georgias-digital-future), the [State of Massachusetts](https://www.lullabot.com/our-work/massgov-das), Iowa, and the City of London. - Enterprise organizations that need to market their products, like [New Relic](https://www.lullabot.com/our-work/new-relic), Verizon, and Johnson & Johnson. - Higher education institutions like Harvard, [Carnegie Mellon University](https://www.lullabot.com/our-work/carnegie-mellon-university), Evergreen State College, Princeton, [UMass Amherst](https://www.lullabot.com/our-work/umass-amherst), and [Lehigh University](https://www.lullabot.com/our-work/lehigh-university). - Media organizations like Economist.com, NPR, and [Georgia Public Broadcasting](https://www.lullabot.com/our-work/georgia-public-broadcasting). - And so many more that range from retail to hospitality and financial services to nonprofits. View all the [case studies on Drupal.org](https://www.drupal.org/case-studies). According to [BuiltWith.com](https://trends.builtwith.com/cms/Drupal), almost 10% of the top 10,000 sites in the world are powered by Drupal. ## Misconceptions and misunderstandings about Drupal Drupal is a mature content management system and has been around for a long time. With that long life comes a reputation, some of it earned, some of it unearned. There are still some misconceptions about Drupal that are worth debunking. ### Drupal is complex It’s true that, under the hood, Drupal is a complex piece of software and has a lot of moving parts, including upstream dependencies. But this is not unique to Drupal. Compared to most JavaScript projects, Drupal has far fewer external dependencies, and those dependencies are well-vetted by the Drupal community and maintain high-quality code If this objection refers to the editorial experience and managing the website, this is also dependent on customizations that have been implemented. Out of the box, Drupal is very straightforward and easy to use. But lots of organizations modify the experience to get it *just right*, and often, they don’t follow best practices. Or they don’t plan properly for their desired audiences. This can lead to bloated forms and confusing navigation. Drupal’s tooling is such that it might make it easier for things to get out of hand. With great power comes great responsibility, after all. A box of LEGO bricks can become a beautiful toy, or those same bricks can be strewn across the floor, waiting to be stepped on by a bare foot. If someone accidentally steps on one of those bricks, that’s not the fault of the brick. ### Drupal is hard to code with Drupal is powered by and uses many of the same patterns as [Symfony PHP components](https://symfony.com/packages), following well-established OOP principles. If a developer knows PHP and Symfony, that developer will be able to customize Drupal just fine. While Drupal still has its quirks, this is no different from any other platform or framework. Someone might not prefer the quirks of Drupal, but that doesn’t mean it is more difficult to work with. Some users of Drupal can get frustrated with the editorial interface because it doesn’t match their expectations and get even more frustrated when those expectations cannot be met without sufficient time and budget. Drupal is a powerful piece of software that can power complicated editorial workflows, but there are some tradeoffs you’ll need to be aware of, and an honest agency will let you know about those tradeoffs. You might be better served with a different CMS but understand that every CMS has tradeoffs you must take into account, and mismatched expectations are not unique to Drupal. ### Drupal is hard to maintain This misconception often arises because organizations find they can’t launch new features quickly, they are drowning in bugs, and things seem to be getting worse, not better. However, this is usually because of a few factors. - Previous developers have failed to follow Drupal best practices. They have dumped logic into theme files where it is harder to maintain, or they haven’t gone with the grain of how Drupal is supposed to work when creating custom modules. Or, they have treated Drupal like it’s WordPress. We have cleaned up many Drupal builds, and once this technical debt has been trimmed to a manageable level, [most development teams have no problem maintaining Drupal](https://www.lullabot.com/digital-modernization-platform-development/support-maintenance). A lot of custom solutions get slapped onto Drupal, and then Drupal gets blamed because those custom solutions are hard to maintain. - Drupal core and contributed module updates are not kept current. This can introduce incompatibilities and fragility. While it's true that Drupal is not as easy to update as WordPress or SaaS CMSs, when compared to other enterprise-level CMSs, Drupal compares favorably. A knowledgeable developer will have no problems, and there are many[ support and maintenance](https://www.lullabot.com/digital-modernization-platform-development/support-maintenance) offerings available if you don’t have the technical expertise on staff. - Before Drupal 8, Drupal had ways of working with it that were unique to Drupal. Even experienced PHP developers could have problems, and this could lead to them to solutions that did not follow best practices. This reputation can still persist, even though Drupal is now powered by Symfony components, and there is an easier onramp for PHP developers. ### Drupal is more expensive than other CMSs This is usually a different way of saying one of the above misconceptions, just in a language that different types of stakeholders resonate with more. If Drupal is complex, hard to code with, and hard to maintain, that must translate into a higher cost, right? But since those “ifs” aren’t necessarily true, neither is the conclusion. As long as you plan accordingly, hire the right experts, and go with the grain of how Drupal works, then a Drupal website project will be comparable to other CMS website projects. In many cases, it will be a cheaper option. And with Drupal, you’ll have freedom and flexibility. ## Is Drupal an accessible platform? Short answer: yes. [Drupal is built and designed with accessibility in mind](https://www.drupal.org/about/features/accessibility), making it easy to create websites that everyone can use regardless of physical ability. The project has a dedicated accessibility team that works diligently to ensure that Drupal is accessible to all users. This includes code review to ensure that submitted patches adhere to WCAG 2.1 accessibility guidelines before acceptance, making Drupal's core themes and modules accessible, and providing accessibility tools for site builders. Drupal includes many built-in accessibility features that ensure that content is accessible to screen readers and other assistive technologies, such as semantic HTML markup, required alt text for image uploads, ARIA labels, and other tools that make it easy to create accessible content. Drupal also natively includes keyboard navigation support, which allows users to navigate the site using only a keyboard. Now, all of this doesn’t mean it’s foolproof. Any custom development, especially theme development, can make things inaccessible quickly if the developers do not follow best practices. Designs can enshrine terrible contrast ratios. So, while Drupal core takes accessibility seriously, that doesn’t mean you can slack off when building out new features. ## How secure is Drupal? Drupal has a dedicated [security team](https://www.drupal.org/drupal-security-team) that is responsible for reviewing and addressing security issues in Drupal core and contributed modules. Security patches are [released consistently and predictably](https://www.drupal.org/about/core/policies/core-release-cycles/schedule#monthly), making it easier for site administrators to stay up-to-date and secure. Any changes to Drupal core go through a strict [review process](https://www.drupal.org/about/core/policies/core-change-policies/core-gates). Many popular contributed modules follow a similar process. Drupal's architecture is designed with security in mind. Drupal has a strong access control system that allows site administrators to set granular permissions for users and roles, and also includes built-in protections against common security threats, such as cross-site scripting (XSS) and SQL injection attacks. Many contributed modules are also covered by the security team. You can tell if a module is covered by looking for a shield icon at the bottom of the module page, as you can see with the [Type Tray module](https://www.drupal.org/project/type_tray). ![Type tray module release information, security shield and usage statistics](/sites/default/files/styles/max_900/public/2023-08/image1.png.webp?itok=CDGfsHVb) If a security vulnerability is discovered in a popular contributed module, it’s not always up to the maintainer to fix it. Sometimes the security team will step in to create a new release. However, for less popular modules, if the issue remains unfixed, the module will be marked as unsupported. While this benefit is hard to quantify, you shouldn’t ignore it. It’s one of the many gears that work beneath the surface to keep things running smoothly. ## Migrating to Drupal Drupal has a suite of migration tools that are robust, feature-rich, and stable. As long as you have access to a data source, like a relational database or even CSV files, you can pull data into a Drupal website. Not only that, but you can transform and combine that data before you insert it. This means that no matter what platform you need to migrate from, Drupal’s migration tools will be able to meet your needs. If coming from WordPress, [there is already a module for it](https://www.drupal.org/project/wordpress_migrate). [While these tools are reserved for developer use and customization](https://www.lullabot.com/articles/overview-migrating-drupal-sites-8), you can know that they are tried and tested. [The official path for upgrading from Drupal 7 to Drupal 8/9/10 includes using these tools](https://www.drupal.org/docs/upgrading-drupal/upgrading-from-drupal-6-or-drupal-7). You won’t have to reinvent the wheel. However, that doesn’t mean a migration will be cheap or easy. If you take this as an opportunity to rework your content model or implement a redesign, you’ll need additional planning. Sometimes, determining what you *don’t* want to migrate is just as important, and [those audits don’t do themselves](https://www.lullabot.com/articles/content-audits-heavy-lift-huge-payoff). Every migration is a little different. Goals, audiences, servers, content models, and more all come together to form a unique fingerprint. But because of the work put into Drupal’s migration tools and processes, there is an undercurrent of commonality, so the path forward is never completely unmarked. ## A brief history of Drupal 2000: Created by Dries Buytaert in his dorm room as messaging board software. 2001: Drupal 1.0 released. 2001: Two months later, Drupal 2.0 is released. Basic translation system introduced. 2001: Six months later, Drupal 3.0 is released. Nodes introduced. 2002: Drupal 4.0 is released. Taxonomy is introduced. 2003: Howard Dean’s presidential campaign uses Drupal. 2005: Drupal security team formed. 2006: Drupal 4.7 released. Form API introduced. Views module is released, along with CCK, which makes site building more flexible and powerful. 2007: Drupal 5.0 released. Acquia is founded. 2008: Drupal 6.0 released. 2009: Whitehouse.gov uses Drupal. 2011: Drupal 7.0 released, which is still the most popular version of Drupal. Field API (formerly CCK) is included in Drupal core. 2015: Drupal 8.0 released. This represented an overhaul of the architecture, introducing Symfony components. Views is now included in Drupal core. 2020: Drupal 9.0 released. The first new version of Drupal to follow the new release schedule, aimed at more minor updates and a faster release schedule. 2022: [Drupal 10 released](https://www.lullabot.com/articles/drupal-10-what-you-need-know). New default themes Claro and [Olivero](https://www.lullabot.com/articles/designing-chaos-finding-order-olivero). Drupal now uses Symfony 6. 2023: Drupal 11 released. New navigation, Single Directory Components, Workspaces, and Recipes. [Visit DrupaHistory.org for a more detailed timeline](https://drupalhistory.org/). ## What is the current version of Drupal? Drupal 11 is the latest release of Drupal. Drupal 10 will reach end of life, with no further security support, sometime in 2026 to coincide with the release of Drupal 12. [See more details on Drupal’s release cycle](https://www.drupal.org/about/core/policies/core-release-cycles/schedule). ## What is the difference between Drupal CMS and Drupal core? Drupal CMS is the product arising from the [Drupal Starshot](https://www.lullabot.com/articles/could-drupal-starshot-help-drupal-compete) initiative. It uses Drupal core but includes other modules and recipes to help marketers, designers, and content creators create rich, ambitious websites. Drupal CMS aims to be the standard for no-code websites with things like advanced SEO, a user-friendly editorial experience, straightforward installation and onboarding, automatic updates, and smart defaults. Users will have many recipes to choose from to add new functionality to their website with a few clicks of their mouse. Drupal core is what Drupal has always been: a powerful core set of tools, modules, and APIs that enable users to compose powerful content management experiences. Users who are more advanced or organizations that have a full development team might want to start with Drupal Core. Perfect for crafting a 100% customizable vision. Many of the improvements from Drupal CMS can still be used. You'll just need to manually install and configure them. ## Should you use Drupal for your next project? There are many good reasons to choose Drupal as your content management system, but it might not make sense for everyone, depending on your organization and circumstances. These questions will help narrow down your answer. **Do you need to manage a lot of content on your website, and is your website strategic for your success?** Drupal shines for this use case. **Do you value ownership and control over your website technology stack?** Since Drupal is open source, you won’t be limited by restrictive licenses and fees. **Do you need the power and flexibility of connected and relational content?** Drupal can model complicated domains, enable powerful editorial workflows, and offer flexibility to build whatever you need. But…do you really need all of that? To do it right, you’ll need to invest time to plan and money to implement. **Do you have at least one experienced Drupal developer on staff, or a trusted agency partner who specializes in Drupal?** While PHP developers will be able to get things done, it helps to have someone who already knows the vocabulary and best practices. And many agencies will say they do Drupal, but it is an afterthought for the rest of their business. **Are you ok with following best practices when it comes to the editorial experience, even if that means it’s not working** ***exactly*** **like you imagined it?** Drupal is flexible and can do almost anything, but, like other CMSs, it does some things better than others. Just because something *can* be done in Drupal doesn’t mean it *should*, or it could be unexpectedly expensive and cause maintenance headaches in the future. If your expectations are set accordingly, Drupal will not disappoint you. ## Is Drupal the same as Acquia? No. [Acquia](https://www.acquia.com/) is a company co-founded by Drupal’s creator, Dries Buytaert. Acquia provides enterprise hosting and support for Drupal applications, along with additional marketing tools. Sometimes there is confusion because industry research organizations like Gartner use the name “Acquia” when they are really talking about Drupal. Acquia invests heavily in the Drupal project and has helped increase its name recognition, but Drupal does not belong to Acquia. Drupal belongs to the community and is free software that anyone can use and modify. ## How to become a Drupal developer Drupal is so powerful that it can feel daunting to learn. If you already know PHP, we recommend reading some of the [Symfony book](https://symfony.com/book) or the [documentation](https://symfony.com/doc). These will get you familiar with some of the same patterns you’ll use when developing with the latest version of Drupal, especially object-oriented PHP. Install Drupal for yourself and play around with the interface. Try to build something basic and browse through the [contributed modules](https://www.drupal.org/project/project_module). [Pantheon](https://pantheon.io/) offers a free development instance of Drupal, so you can kick the tires around without configuring a local environment. Start tweaking things with your own modules and themes. For a full guide for someone who doesn’t know PHP or Git, [check out the full guide on how to become a Drupal developer](https://drupalize.me/blog/how-become-drupal-developer) at [Drupalize.me](https://drupalize.me/). ## One of the first Drupal agencies Lullabot has been building things with Drupal since 2006. We're also dedicated to [improving Drupal](https://www.lullabot.com/articles/improving-drupals-administration-ux) for everyone who uses it. If you want to evaluate the platform or are ready to move forward with your project, [contact us](https://www.lullabot.com/contact). We have worked with state governments, higher education, and enterprise organizations. Check out [our work](https://www.lullabot.com/our-work) and the [services we offer](https://www.lullabot.com/digital-modernization-platform-development). ### More resources: - [Drupal 10: Everything You Need to Know](https://www.lullabot.com/articles/drupal-10-what-you-need-know) - [Microsites in Drupal](https://www.lullabot.com/articles/microsites-drupal) - [How to Get Excited About Drupal Again](https://www.lullabot.com/articles/how-get-excited-about-drupal-again) - [Making the Most of Display Modes in Drupal](https://www.lullabot.com/articles/making-most-display-modes-drupal) - [The Basics of Drupal Revisions and Content Moderation](https://www.lullabot.com/articles/basics-drupal-revisions-and-content-moderation) ---