Oct 21 2018
Oct 21

Wouldn’t it be nice if you could add any block you want to your paragraphs?

In years past, layout for Drupal has been in the hands of front-end developers, but over time various modules were developed that provided site-builders the ability to adjust the layout. An improvement yes, but there still wasn’t a clear cut option that empowered content editors to alter the layout during the editorial process.

Look out! Here comes the Paragraphs Module. This module has been taking the Drupal community over by storm because it allows content editors to add pre-designed components which gives each page the option to have different layouts. One of the limitations of the Paragraphs module, is that each paragraph can only be used once, and only for the current node you are editing. This means that you can’t re-use a common paragraph such as a call to action block, email signup or contact us form, so you end up finding yourself duplicating a lot of work if you want the same block on numerous pages. While the Drupal community has been working to help solve this problem by allowing the re-use of paragraphs, there are still going to be plenty of situations where you want to insert custom blocks, views, or system blocks such as the site logo or login block.

How do you allow your site editors to add re-used blocks into their content during the editorial process?

Let me introduce you to the Block Field Module. Maintained by the one and only Jacob Rockowitz (you know the webform guy ), you can be assured that the code follows best practices and that there will be support. The block field module allows you to reference any block regardless of where it is coming from and the best part, you don’t have to create some hidden region in your theme in order for the blocks to be rendered.

There are plenty of awesome articles out there that explains how to use paragraphs so I won’t get into that. To follow along with my steps be sure to have downloaded and enabled both the Paragraphs and the Block Field modules.

Steps to Add Blocks to Paragraphs

  1. Download and Enable the Paragraphs and Block Field modules.
  2. Create a paragraph type called Block Reference (or whatever name you want)
  3. Add a new field, by selecting the Block (plugin) field type from the dropdown and save it.
  4. Go to manage display and make the label hidden. 
    I always forget this step and then I scratch my head when I see the Block Ref field label above my views title.
  5. Now go to back to your content type that has the paragraph reference field and ensure the Block Reference paragraph type is correctly enabled. 
    The content type with the paragraph reference field was not covered in this tutorial.
  6. When adding or editing your content with a paragraph reference field. Add the Block Reference paragraph type. Select the name of the block that you would like to reference from the dropdown hit save on the content and watch the magic happen.

In conclusion, it does feel a little scary giving content editors this much freedom so it will be imperative that all views and custom blocks have descriptive names so that editors can clearly identify what blocks to reference. Overall I feel like this is a good solution for referencing existing blocks that can save a lot of time and really unleashes the power of the paragraphs module. The Drupal community continues to amaze me!

Jun 18 2018
Jun 18
June 18th, 2018

Last month, Ithaca College introduced the first version of what will represent the biggest change to the college’s website technology, design, content, and structure in more than a decade—a redesigned and rebuilt site that’s more visually appealing and easier to use.

Over the past year, the college and its partners Four Kitchens and design firm Beyond have been hard at work within a Team Augmentation capacity to support the front-to-back overhaul of Ithaca.edu to better serve the educational and community goals of Ithaca’s students, faculty, and staff. The results of the team’s efforts can be viewed at https://www.ithaca.edu.  

Founded in 1892, Ithaca College is a residential college dedicated to building knowledge and confidence through a continuous cycle of theory, practice, and performance. Home to some 6,500 students, the college offers more than 100 degree programs in its schools of Business, Communications, Humanities and Sciences, Health Sciences and Human Performance, and Music.

Students, faculty, and staff at Ithaca College create an active, inclusive community anchored in a keen desire to make a difference in the local community and the broader world. The college is consistently ranked as one of the nation’s top producers of Fulbright scholars, one of the most LGBTQ+ friendly schools in the country, and one of the top 10 colleges in the Northeast.

We emphasized applying automation and continuous integration to focus the team on the efficient development of creative and easy to test solutions.

On the backend, the team—including members of Ithaca’s dev org working alongside Four Kitchens—built a Drupal 8 site. The transition to Drupal 8 keeps the focus on moving the college to current technology for sustainable success. Four Kitchens emphasized applying automation and continuous integration to focus the team on the efficient development of creative and easy to test solutions. To achieve that, the team set up automation in Circle CI 2.0 as middleware between the GitHub repository and hosting in PantheonGitHub was used throughout the project to implement, automate, and optimize visual regression, advanced communication between systems and a solid workflow using throughout the project to ensure fast and effective release cycles. Learn from the experiences obtained from implementing the automation pipeline in the following posts:

The frontend focused heavily on the Atomic Design approach. The frontend team utilized Emulsify and Pattern Lab to facilitate pattern component-based design and architecture. This again fostered long-term ease of use and success for Ithaca College.

The team worked magic with content migration. Using the brainchild of Web Chef, David Diers, the team devised a plan to migrate of portions of the site one by one. Subsites corresponding to schools or departments were moved from the legacy CMS to special Pantheon multidevs that were built off the live environment. Content managers then performed a moderated adaptation and curation process to ensure legacy content adhered to the new content model. A separate migration process then imported the content from the holding environment into the live site. This process allowed Ithaca College’s many content managers to thoroughly vet the content that would live on the new site and gave them a clear path to completion. Learn more about migrating using Paragraphs here: Migrating Paragraphs in Drupal 8

Steady scrum rhythm, staying agile, and consistently improving along the way.

In addition to the stellar dev work, a large contributor to the project’s success was establishing a steady scrum rhythm, staying agile, and consistently improving along the way. Each individual and unit solidified into a team through daily 15-minute standups, weekly backlog grooming meetings, weekly ‘Developer Showcase Friday’ meetings, regular sprint planning meetings, and biweekly retrospective meetings. This has been such a shining success the internal Ithaca team plans to carry forward this rhythm even after the Web Chefs’ engagement is complete.     

Engineering and Development Specifics

  • Drupal 8 site hosted on Pantheon Elite, with the canonical source of code being GitHub and CircleCI 2.0 as Continuous Integration and Delivery platform
  • Hierarchical and decoupled architecture based mainly on the use of group entities (Group module) and entity references that allowed the creation of subsite-like internal spaces.
  • Selective use of configuration files through the utilization of custom and contrib solutions like Config Split and Config Ignore modules, to create different database projections of a shared codebase.
  • Migration process based on 2 migration groups with an intermediate holding environment for content moderation.
  • Additional migration groups support the indexing of not-yet-migrated, raw legacy content for Solr search, and the events thread, brought through our Localist integration.
  • Living style guide for site editors by integrating twig components with Drupal templates
  • Automated Visual Regression
Aerial view of the Ithaca College campus from the Ithaca College homepage. From the Ithaca College Homepage.

A well-deserved round of kudos goes to the team. As a Team Augmentation project, the success of this project was made possible by the dedicated work and commitment to excellence from the Ithaca College project team. The leadership provided by Dave Cameron as Ithaca Product Manager, Eric Woods as Ithaca Technical Lead and Architect, and John White as Ithaca Dev for all things legacy system related was crucial in the project’s success. Ithaca College’s Katherine Malcuria, Senior Digital User Interface Designer,  led the creation of design elements for the website. 

Katherine Malcuria, Senior Digital User Interface Designer, works on design elements of the Ithaca.edu website

Ithaca Dev Michael Sprague, Web Chef David Diers, Architect,  as well as former Web Chef Chris Ruppel, Frontend Engineer, also stepped in for various periods of time on the project.  At the tail end of the project Web Chef, Brian Lewis, introduced a new baby Web Chef to the world, therefore the amazing Randy Oest, Senior Designer and Frontend Engineer, stepped in to assist in pushing this to the finish line from a front-end dev perspective. James Todd, Engineer, pitched in as ‘jack of all trades’ connoisseur helping out where needed.

The Four Kitchens Team Augmentation team for the Ithaca College project was led by Brandy Jackson, Technical Project Manager, playing the roles of project manager, scrum master, and product owner interchangeably as needed. Joel Travieso, Senior Drupal Engineer, was the technical lead, backend developer, and technical architect. Brian Lewis, Frontend Engineer, meticulously worked magic in implementing intricate design elements that were provided by the Ithaca College design team, as well a 3rd party design firm, Beyond, at different parts of the project.

A final round of kudos goes out to the larger Ithaca project team, from content, to devOps, to quality assurance, there are too many to name. A successful project would not have been possible without their collective efforts as well.

The success of the Ithaca College Website is a great example of excellent team unity and collaboration across multiple avenues. These coordinated efforts are a true example of the phrase “teamwork makes the dream work.” Congratulations to all for a job well done!

Special thanks to Brandy Jackson for her contribution to this launch announcement. 

Four Kitchens

The place to read all about Four Kitchens news, announcements, sports, and weather.

Aug 02 2017
ao2
Aug 02

Drupal 8 is quite an improvement over previous versions of Drupal with regard to the reproducibility of a site from a minimal source repository.

The recommended way to set up a new Drupal 8 site is to use composer, and the most convenient way to do that is via drupal-composer/drupal-project, which brings in Drush and drupal-console as per-site dependencies; from there the site can be installed and managed.

However the fact that Drush and drupal-console are per-site dependencies poses a problem: they are not available until the site dependencies have been downloaded, this means that it's not really possible to add “bootstrapping” commands to them to make it easier to set up new projects.

This is what motivated me to put together drupal-init-tools: a minimalistic set of wrapper scripts which can be used when Drush and drupal-console are not available yet, or when they have to be combined together to perform a task in a better way.

drupal-init-tools has been developed mainly to speed up prototyping, and it is particularly useful to set up sites under ~/public_html when the web server is using something like the Apache userdir module; however I feel it could be useful in production too after some cleanups.

drupal-init-tools standardizes the setup and installation of new Drupal projects: for example the drin new command makes sure that the original drupal-composer/drupal-project repository is still accessible from an upstream git remote so that the developer can easily track changes in drupal-composer/drupal-project and keep up if appropriate for their particular project.

Some drin commands also provide a nicer interface for frequent and important tasks, like the actual site-install step, or the creation of an installation profile that could replicate the installed site.

Here are some examples of use taken from the drin manual.

Create and install a new Drupal project:

cd ~/public_html
drin new drupal_test_site
cd drupal_test_site
$EDITOR bootstrap.conf
drin bootstrap --devel

Create an installation profile from the currently installed project:

drin create-profile "Test Profile" test_profile

Clean and rebuild the whole project to verify that installing from scratch works:

drin clean
drin bootstrap

A quick way to test drupal-init-tools is this:

git clone git://git.ao2.it/drupal-init-tools.git
cd drupal-init-tools/
make local
./drin --help

Give it a go and let me know what you think.

If it proves useful to others I could have it packaged and uploaded to Debian to make it more “official”, IMHO it makes sense to have such a small meta-tool like drin as a system global command.

Jun 12 2017
ao2
Jun 12

Most of the information I have come across about migrating from Drupal 6 to Drupal 8 is about migrating content, however before tackling this problem another one must be solved, maybe it is obvious and hence understated, so let's spell it out loud: preserving the site functionality.

That means checking if the contrib modules need to be ported to Drupal 8, and also checking if the solution used in the previous version of the site can be replaced with a completely different approach in Drupal 8.

Let's take ao2.it as a study case.

When I set up ao2.it back in 2009 I was new to Drupal, I choose it mainly to have a peek at the state of Open Source web platforms.

Bottom line, I ended up using many quick and dirty hacks just to get the blog up and running: local core patches, theme hacks to solve functional problems, and so on.

Moving to Drupal 8 is an opportunity to do things properly and finally pay some technical debt.

For a moment I had even thought about moving away from Drupal completely and use a solution more suited to my usual technical taste (I have a background in C libraries and linux kernel programming) like having the content in git and generate static web pages, but once again I didn't want to miss out on what web frameworks are up to these days, so here I am again getting my hands dirty with this little over-engineered personal Drupal blog, hoping that this time I can at least make it a reproducible little over-engineered personal Drupal blog.

In this series of blog posts I'll try to explain the choices I made when I set up the Drupal 6 blog and how I am re-evaluating them for the migration to Drupal 8.

The front page view

ao2.it was also an experiment about a multi-language blog, but I never intended to translate every content, so it was always a place where some articles would be in English, some in Italian, and the general pages would be actually multi-language.

This posed a problem about what to show on the front page:

  • If every node was shown, there would be duplicates for translated nodes, which can be confusing.
  • If only nodes in the current interface language were shown, the front page would list completely different content across languages, which does not represent the timeline of the blog content.

So a criterion for a front page of a partially multi-lingual site could be something like the following:

  • If a node has a translation in the current interface language, show that;
  • if not, show the original translation.

The “Select translation” module

In Drupal 6 I used the Select translation module which worked fine, but It was not available for Drupal 8.

So I asked the maintainers if they could give me the permission to commit changes to the git repository and I started working on the port myself.

The major problem I had to deal with was that Drupal 6 approached the multi-language problem using by default the mechanism called "Content translations" where separate nodes represented different translations (i.e. different rows in the node table each with its own nid), tied together by a tid field (translation id): different nodes with the same tid are translations of the same content.

Drupal 8 instead works with "Entity translations", so one single node represents all of its translations and is listed only once in the node table, and actual translations are handled at the entity field level in the node_filed_data table.

So the SQL query in Select translation needed to be adjusted to work on the node_filed_data rather than of the node table, as it can be seen in commit 12f70c9bb37c.

While at it I also took the chance to refactor and clean up the code, adding a drush command to test the functionality from the command line.

The code looks better structured thanks to the Plugin infrastructure and now I trust it a little more.

Preserve language

On ao2.it I also played with the conceptual difference between the “Interface language” and the “Content language” but Drupal 6 did not have a clean mechanism to differentiate between the two.

So I used the Preserve language module to be able to only switch the interface language when the language prefix in the URL changed.

It turns out that an external module is not needed anymore for that because in Drupal 8 there can be separate language switchers, one for the interface language and one for the content language.

However there are still some issues about the interaction between them, like reported in Issue #2864055: LanguageNegotiationContentEntity: don't break interface language switcher links, feel free to take a look and comment on possible solutions.

More details about the content language selection in a future blog post.

Feb 09 2017
Feb 09

Not sure who's to blame, but we have a new HTML validation method from GoDaddy. It is an improvement from the "none HTML validation at all" phase they went through, but took me a while to make it work with apache. The problem was the dot/hidden directory they request to put your validation code in: /.well-known/pki-validation/godaddy.html

In my case there were a couple of reasons why this was difficult:

  • I didn't know about the hidden directory (.) block in Apache.
  • In my case some domains run the whole site over HTTPS, so I needed to make the new rules co-exist with the old HTTPS redirection rules.
  • I have a mixture of hostings. For some sites I control apache, so I could use Virtual Host configurations. But for others (like the ones running on Acquia) I need to create .htaccess rules.

The solution was much simpler than I anticipated, but quite difficult to debug. Finally I made it work for both environments.

I could have used the DNS ownership verification method, but in my case that means I would need to involve the people owning the domain. In my experience that takes longer and it can become really involved when owner doesn't know anything about DNS.

Using Virtual Host config (possible on self hosted sites)


RewriteEngine  on
RewriteRule    "^/\.well-known/pki-validation/godaddy\.html/" "/godaddycode.txt" [PT]
RewriteRule    "^/\.well-known/pki-validation/godaddy\.html$" "/godaddycode.txt" [PT]
    

If the site is only running on HTTPS and I have a redirection rule I'll work around these URLs. The rules below will work together with the one above:


RewriteCond %{REQUEST_URI} =!/.well-known/pki-validation/godaddy.html
RewriteCond %{REQUEST_URI} =!/.well-known/pki-validation/godaddy.html/
RewriteRule ^(.*)$ https://www.mydomain.com/ [R=permanent,L]
    

Using only .htaccess rules (and with no HTTPS redirection):


# GoDaddy verification rewrite rules
<IfModule mod_rewrite.c>
  RewriteRule    "^.well-known/pki-validation/godaddy.html/" "/godaddycode.txt" [PT,L]
  RewriteRule    "^.well-known/pki-validation/godaddy.html$" "/godaddycode.txt" [PT,L]
</IfModule>
    

Using .htaccess rules when site is only running over HTTPS:


# GoDaddy with HTTPS redirection rules
<IfModule mod_rewrite.c>
  # GoDaddy PassThrough rules
  RewriteRule    "^.well-known/pki-validation/godaddy.html/" "/godaddycode.txt" [PT,L]
  RewriteRule    "^.well-known/pki-validation/godaddy.html$" "/godaddycode.txt" [PT,L]
  # Set "protossl" to "s" if we were accessed via https://.  This is used later
  # if you enable "www." stripping or enforcement, in order to ensure that
  # you don't bounce between http and https.
  RewriteRule ^ - [E=protossl]
  RewriteCond %{HTTPS} on
  RewriteRule ^ - [E=protossl:s]
  # Redirect HTTP to HTTPS
  RewriteCond %{HTTP:X-Forwarded-Proto} !=https
  RewriteCond %{REQUEST_URI} !=/godaddycode.txt
  RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
    

And to make this work on Acquia I had to borrow some rules from D8 .htaccess

So I replaced these sections/rules:


# Protect files and directories from prying eyes (D7)
<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(|~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig\.save)$">
  Order allow,deny
</FilesMatch>

# Block access to "hidden" directories whose names begin with a period... (D7)
RewriteRule "(^|/)\." - [F]
    

With these D8 sections/rules:


# Protect files and directories from prying eyes (D8)
<FilesMatch "\.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock))$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
  <IfModule !mod_authz_core.c>
    Order allow,deny
  </IfModule>
</FilesMatch>

# Block access to "hidden" directories whose names begin with a period... (D8)
RewriteRule "(^|/)\.(?!well-known)" - [F]
    

I hope this helps someone else. I know it took me some time to figure it out and couldn't find an specific blog post about it.

Note: Just to be super clear, you should put the code given by GoDaddy into a file called godaddycode.txt on your docroot directory.

Oct 27 2016
Oct 27

We have been using Memcached as the standard to speed up our Drupal internal caching. Shall we now use Redis instead?

It seems there is a new trend on the Drupal community, Redis. What made me pay attention to it is that it seems to go in detriment of Memcached.

In a nutshell:
  • Memcached does only one thing, and it does it well. Stores key/value pairs in memory.
  • Now Redis has about the same performance than Memcache storing/accessing key/value pairs, but it does much more.

This alone would make a point as to why you should use Redis if you are developing a new platform. Granted, it is more convoluted and involved to learn, but you can do much more. It also has a solid scalable architecture.

In my book the recipe goes:
  • If you are already using memcached, stick with it. I don't see a reason to change right away.
  • If you have a provider which restricts you to either of them, just follow their lead. Best to have key-value server for cache than using the main database. No matter if it is Memcached or Redis, use what ever they provide.
  • If you are not using either of them, just choose one and use it. Both will do the trick.

For Drupal7 both modules are stable: memcache and Redis. I have some details on how to configure memcache module in this Drupal performance presentation (in spanish), but there are lots of articles on how to do it online.

If you are using Drupal8 or you are doing a new development that could benefit from in-memory key-value cache and store I would look into using Redis. There are alpha versions of the Drupal integration modules for either of them.

Important fact about major players: Acquia only supports Memcached. Pantheon and Platform only support Redis.

If you want to know more about Redis I can recommend:
Jul 01 2016
Jul 01

What's important to learn about the recent AberdeenCloud meltdown? You can not trust your backups and site to a single provider!

Well it is all over the blogosphere, AberdeenCloud breakdown. I already read a couple of articles on the subject and I couldn't agree more with the one from annertech, only way to avoid this kind of issues is offsite backup. I would normally write how to recover from something like this, but sadly as codeenigma explained there isn't much we could do, their servers just collapsed and were gone. But now for 36hs we'll be able to recover our data.

Sadly we didn't notice problems with AberdeenCloud until it was too late. Due to other reasons we had a plan to pull away, but we didn't get to apply it for the only site that remained there. We did have backups, but not automatic. So they weren't as frequent as we would have needed given the total meltdown on AberdeenCloud. Since we had some backups and it was only one site it didn't take that long to rebuild what we lost. It wasn't really an option to wait for them to come back as it didn't seem it would happen. I was really surprised and pleased to see they finally replied, but before that we used AberdeenCloud cache (wasn't realiable but with patience you could get something) and also use the Wayback Machine.

So what's important about this? We need to learn we should ALWAYS have an alternate site backup on a different provider. You'll need it for disaster recovery, that's the only way to guard your client's data. You can not trust a single provider with both your site and backups. We shouldn't be naive about this, it is also important that you track back to learn which company your provider trust on. If for example your sites are hosted by Pantheon and you want to trust your backups to NodeSquirrel, think again as they are both the same company.

Bottom line, be serious about backups even if you are only trying the service. In the company I work for we do it for the other services we have, and from now one we won't try a service without implementing offsite backups first. That needs to be part of the initial implementation, not a post launch burn down list task.

Feb 16 2015
Feb 16

I know that BDConf had to be a decent conference for me because four months later I am still thinking about it. Right after the trip, I was focused more internally because the conference was a good kick in the pants that there is a broader web world than just Drupal. The last three years, my job has kept me so focused on Drupal that I had lost some of that perspective, so it was good to get me thinking of the broader world again.

In the time since then, I keep wondering about the tradeoffs Drupal has caused my team to make for our development. Don’t get me wrong, any programming language or platform has tradeoffs, whether it is about complexity, performance, stability or a number of other options. When I mentioned Drupal at the conference, one of the reactions I got was “so many divs!” with a look of distain on her face. That sort of thing makes you take pause and it got me wondering whether we tradeoffs we made when picking Drupal originally still apply.

I still don’t know the answer, I spend more time thinking about it than I probably should. The “so many divs” comment is an example of this. I realize that by having the community develop Drupal and it’s add-ons, our team has been able to make a lot of really cool web sites. But that means that we don’t know what every part of our site is doing, so we have sites that average 1.5-2MB in size routinely with likely inefficent queries, markup, styling and Javascript in there. As someone who came to Drupal from straight HTML, PHP and .NET, this drives me a little bonkers and makes me wonder, is this the right tradeoff to make.

Tangentially to this, I have some thoughts about the Drupal 8/Backdrop too that I am working on another post for. To the web folks out there reading this, I would love to hear your comments because with this spinning in just my head, I am not getting as far as I would like.

Aug 18 2014
Aug 18

In this article, we’ll discuss how you can leverage various Drupal API functions to achieve more fine grained theming. We’ll cover template preprocessing and alter hooks using path patterns, types and args(). We’ll use the arg() function which returns parts of a current Drupal URL path and some pattern matching for instances when you want to match a pattern in a URL. We’ll also take a look at creating a variable for an array of content types.

Template preprocessing is a means to define variables for use within your page. For example, you can define a body class variable. In turn you can use the resulting class in your Sass or CSS. An alter or build hook is something that’s run before the page renders so you can do things such as adding JS or CSS to specific pages or content types.

I’ll explain and demonstrate these hooks and how you can use them to:

  • Add a <body> class to a specific page for better theming
  • Add Javascript or CSS to specific pages and paths
  • Use wildcard path arguments
  • URL pattern matching using preg_match
  • Create an array of content types to use as a variable in your argument
  • Using path arguments as parameters

The functions we discuss here will be added to your theme’s template.php file. Although you can also use these functions in a custom module, you’ll need to specify that the functions are not for admin pages unless that’s your intent, and I’ll cover how to do that.

Getting Started

When using preprocess or alter functions within your theme, you’ll want to be sure template.php exists but if not, you can go ahead and create this file in the root of your theme. So if my theme name is foobar, the path to template.php will be:

/sites/all/themes/foobar/template.php

API functions are prefaced by the machine name of your theme. For example, if you are using hook_page_alter and your theme name is foobar, we’d write it as function foobar_page_alter(). (The machine name is simply the theme’s folder name.)

Custom Body Classes Using a Content Types Array

A body class is a class that’s added to your HTML <body> tag. For example, on a blog page, you might see something like this:

<body class="page-node page-node-blog">

You can leverage that class in your Sass or CSS to fine tune your theming by doing something like this just for blog pages on your site:

.page-node-blog h1 {
// custom Sass here
}

Out of the box, Drupal 7 comes with some pretty good body classes and usually these are fine for most use cases. In addition, Drupal contribution or Contrib themes such as Zen add enhanced and expanded classes.

In our case, we want to add a class to some pages which share some common attributes but may not necessarily derive from the same content type. Let’s say we have two content types that we want to add a specific class to in order to theme those alike but perhaps different from other pages on our website. We can build an array of Drupal content types we want to target and then use that array to add the class. Once we’ve defined the array, we just check to ensure that a given node exists and then pass the array in.

<?php

/**
 * Implements template_preprocess_html().
 *
 * Define custom classes for theming.
 */
function foobar_preprocess_html(&$vars) {

  // Build a node types array from our targeted content types.
  $foo_types = array(
    'landing_page',
    'our_services',
  );

    // Define the node.
    $node = menu_get_object();
  
  // Use the array to add a class to those content types.
  if (!empty($node) && in_array($node->type, $foo_types)) {
    $vars['classes_array'][] = 'page-style-foobar';
    }
  }

This function preprocesses variables for anything that would typically be before the ending HTML </head> tag which is in Drupal’s core template, html.tpl.php. We add the body class using $vars['classes_array']. This variable gets rendered with <?php print $classes; ?> in the <body> tag. In our case, this class will only render in landing_page and our_services content types. Now we can use .page-style-foobar in our Sass or CSS to style these pages.

URL Pattern Matching

You can also use URL pattern matching for adding useful custom classes. Let’s say we have an “Our Services” landing page and then some sub-pages under that path. The URL architecture might look like this:

example.com/our-services
- example.com/our-services/subpage-1
- example.com/our-services/subpage-2

We’ll add a custom body class to those pages using preg_match, regex, PHP matches and Drupal’s request_uri function. Once again, we’d put this in a foobar_preprocess_html as above.

 <?php
function foobar_preprocess_html(&$vars) {

  // Define the URL path.
  $path = request_uri();

  // Add body classes to various pages for better theming.
  if (preg_match('|^/our-services((?:/[a-zA-Z0-9_\-]*)*)?|', $path, $matches)) {
    $vars['classes_array'][] = 'page-services';
  }
}

Now you can use .page-services .some-common-element for theming these “our-services” pages. Obviously this method has pitfalls if your URL structure changes so it may only fit some use cases.

Path Arguments

Another clever way of custom theming specific parts of your site is to partition those off using arg(). Drupal tends to get script and CSS heavy so ideally if you’re adding extra CSS or JS and you don’t need it for every page (which is normally done using your theme’s .info file), you can use path arg() to add these only to pages where they’re needed. For example, if I want to add a custom script to just the Drupal registration page, I can create a path arg() if statement and then add the script within that. The URL path we’ll focus on here is /user/register and you’ll see how the arg() breaks down the URL structure.

We’ll be using hook_page_alter here which can apply alterations to a page before it’s rendered. First we define the theme path and use Drupal’s attached function.

<?php

/**
 * Implements hook_page_alter().
 *
 * Add custom functions such as adding js or css.
 */
function foobar_page_alter(&$page, $form) {

  // Define the module path for use below.
  $theme_path = drupal_get_path('theme', 'foobar');

  if (arg(0) == "user" && arg(1) == "register") {
     $foobar_js = array(
      '#attached' => array(
        'js' => array(
          $theme_path . '/js/custom.js' => array(
            'group' => JS_THEME,
          ),
        ),
      ),
    );
    drupal_render($foobar_js);
  
  }
}

In this function, note that we’ve substituted our theme name for hook so hook_page_alter becomes foobar_page_alter. The arg() number signifies the position in the URL path. Zero is first, one is second and so on. You can get pretty creative with these adding more parameters. Let’s say you wanted to add JS to just the user page but no paths underneath it. You could add a NULL arg() after the initial arg().

if (arg(0) == "user" && arg(1) == NULL) {
// code here
}

In the examples above, we’ve implemented various functions in our theme’s template.php file. You can also use these in a custom module as well and in that case you’d just preface your module name in the function rather than the theme name. When theming from a module, you’ll probably want to exclude admin paths since a module can target anywhere in your site. You can do that excluding admin paths.

if (!path_is_admin(current_path())) {
// code here
}

Conclusion

As you can see, leveraging the Drupal API Toolbox for theming extends your reach as a developer. The examples above are not definitive but they do give you a feel for what’s possible. As a Drupal Themer, using these has helped me expand my bag of tricks when theming and building a site. Comments? Feedback? Leave them below!

Resources

Jul 05 2014
Jul 05

How to Build a Drupal 8 Module

Even though Drupal 7 core fell short of a proper way of handling its brand new entity system (we currently rely on the great Entity module for that), it did give us EntityFieldQuery. For those of you who don’t know, EntityFieldQuery is a very powerful querying class used to search Drupal entities programatically (nodes, users, etc).

It provides a number of methods that make it easy to query entities based on conditions such as field values or class properties. If you don’t know how it works, feel free to check out this documentation page or this great tutorial on the subject.

In this article I am going to talk about what we have in Drupal 8 for querying entities. There is no more EntityFieldQuery, but there’s an entity.query service that will instantiate a query object for a given entity type (and that implements the \Drupal\Core\Entity\Query\QueryInterface). We can access this service statically through the \Drupal namespace or using dependency injection.

First up, we’ll look at querying node entities and then we’ll see how to load them. The same techniques will work with other content entities as well (users, comments etc), but also with configuration entities, and that’s really cool.

The entity query service

As mentioned, there are two ways we can access the entity.query service that we use for querying entities. Statically, we can do this:

$query = \Drupal::entityQuery('node');

Instead of node, we can specify any other entity type machine name and what we get inside the $query variable is the query object for our entity type. The entityQuery() static method on the \Drupal namespace is a shortcut for doing so using the entity.query service.

Alternatively (and the highly recommended approach) is to use dependency injection.

If you have access to the container, you can load the service from there and then get the right query object:

$entity_query_service = $container->get('entity.query');
$query = $entity_query_service->get('node');

As you can see, we use the get() method on the entity_query service to instantiate a query object for the entity type with the machine name passed as a parameter.

Querying entities

Let’s illustrate a couple of examples of querying for node entities using this object.

A very simple query that returns the published nodes:

$query = \Drupal::entityQuery('node')
    ->condition('status', 1);
    
$nids = $query->execute();

$nids will be an array of entity ids (in our case node ids) keyed by the revision ids (if there is revisioning enabled for the entity type) or the entity ids if not. Let’s see an example in which we add more property conditions as well as field conditions:

$query = \Drupal::entityQuery('node')
    ->condition('status', 1)
    ->condition('changed', REQUEST_TIME, 'condition('title', 'cat', 'CONTAINS')
    ->condition('field_tags.entity.name', 'cats');

$nids = $query->execute();

In this query, we retrieve the node ids of all the published nodes that have been last updated before the current time, that have the word cat inside their title and that have a taxonomy term called cats as a reference in the field_tags.

As you can see, there is no more distinction between propertyCondition and fieldCondition (as there is in D7 with EntityFieldQuery). Additionally, we can include conditions based on referenced entities tacking on the entity.(column) to the entity reference field name.

An important thing to note is that we also have the langcode parameter in the condition() method by which we can specify what translation of the node should be included in the query. For instance, we can retrieve node IDs that contain a specific value inside of a field in one language but another value inside the same field for another language.

For more information on the condition() method you should consult the API documentation.

The next thing we are going to look at is using condition groups (both AND and OR) for more powerful queries:

$query = \Drupal::entityQuery('node')
    ->condition('status', 1)
    ->condition('changed', REQUEST_TIME, 'orConditionGroup()
    ->condition('title', 'cat', 'CONTAINS')
    ->condition('field_tags.entity.name', 'cats');

$nids = $query->condition($group)->execute();

Above, we altered our previous query so as to retrieve nodes that either have the cat string in their title or have a reference to the term called cats in their field_tags field. And we did so by creating an orConditionGroup object that we then pass to the query as a condition. And we can group together multiple conditions within a andConditionGroup as well.

There are many other methods on the QueryInterface that can extend the query (such as for sorting, range, etc). I encourage you to check them out in the documentation and experiment with them. For now, though, let’s take a quick look at what to do with the result set.

Loading entities

As I mentioned above, the execute() method on the query object we’ve been working with returns an array of entity IDs. Supposedly we now have to load those entity objects and work with them. How do we do that?

In Drupal 7 we had the entity_load() function to which we passed an array of IDs and that would return an array of objects. In Drupal 8, this helper function is maintained and you can use it pretty much in the same way, except only for one entity at a time:

$node = entity_load('node', $nids[1]);

And the return value is a node object. To load multiple nodes, you can use the entity_load_multiple() function:

$nodes = entity_load_multiple('node', $nids);

Which then returns an array of entity objects keyed by their ids.

A bonus nugget of information is that both of these functions are wrappers for the storage manager of the entities in question. They basically retrieve the storage manager statically and then call the load() and loadMultiple() methods, respectively, on it:

Statically, you could do similarly:

$node_storage = \Drupal::entityManager()->getStorage('node');

// Load multiple nodes
$node_storage->loadMultiple($ids);
// Load a single node
$node_storage->load($id);

But better yet, you could use dependency injection and retrieve the storage class from the container:

$node_storage = $container->get('entity.manager')->getStorage('node');

And then proceed with the loading. Using dependency injection is usually the recommended way to go when it’s possible, i.e. when working within a class. This makes it easier to test your class and better decouples it from the rest of the application.

Conclusion

In this article we’ve seen how to work with querying and loading entities in Drupal 8. There has been an overhaul of the D7 EntityFieldQuery class that turned into a robust API for querying both content and configuration entities. We’ve looked at querying content entities but the system works just the same with config entities. And that is a bit of a win for the new Drupal 8 entity system.

We’ve also seen how to load entities based on the IDs resulted in these queries and what is actually behind the wrapper functions that perform these operations. Next up, we are going to look at defining our own content entity type in Drupal 8. For a refresher on how we do it in Drupal 7, you can check out these Sitepoint articles on the subject.

Jun 23 2014
Jun 23

In this article we will continue exploring the powers of Views and focus on how to use relationships, contextual filters and rewrite field outputs. In a previous tutorial I showed you how to create a new View and perform basic customizations for it. We’ve seen how to select a display format, which fields to show and how to filter and sort the results.

In this article we will go a bit further and see what relationships and contextual filters are – the two most important options found under the Advanced fieldset at the right of the View edit page. Additionally, we’ll rewrite the output of our fields and combine their values into one.

To begin with, I have a simple article View that just shows the titles. Very easy to set up if you want to follow along. And there are three things I want to achieve going forward:

  1. Make it so that the View shows also the username of the article author
  2. Make is so that the View shows only articles authored by the logged in user
  3. Make it so that the author username shows up in parenthesis after the title

Relationships

First, let’s have the View include the author of the articles. If the View is displaying fields (rather than view modes or anything else), all we have to do is find the field with the author username, right? Wrong. The problem is the following: the node table only contains a reference to the user entity that created the node (in the form of a user ID – uid). So that’s pretty much all we will find if we look for user related fields: Content: Author uid.

What we need to do is use a relationship to the user entity found in the user table. Relationships are basically a fancy way of saying that table A (in our case node) will join with table B (in our case user) in order to retrieve data related to it from there (such as the name of the user and many others). And the join will happen in our case on the uid field which will match in both tables.

So let’s go ahead and add a new relationship of the type Content: Author. Under Identifier, we can put a descriptive name for this relationship like Content Author. The rest we can leave as default.

Now if you go and add a new field, you’ll notice many others that relate to the user who authored the content. Go ahead and add the User: Name field. In its settings, you’ll see a Relationship select list at the top where the relationship identifier we just specified is automatically selected. That means this field is being pulled in using that relationship (or table join). Saving the field will now add the username of the author, already visible in the View preview.

relationships

You can also chain relationships. For instance, if the user entity has a reference to another table using a unique identifier, you can add a second relationship. It will use the first one and bring in fields from that table. So the end result will be that the View will show fields that relate to the node through the user who authored the node but not strictly from the user table but somewhere else connected to the author. And on and on you can join tables like this.

Contextual filters

Contextual filters are similar to regular filters in that you can use mainly the same fields to filter the records on. Where contextual filters differ greatly is that you do not set the filtering value when you create the View, but it is taken from context.

There are many different contexts a filter value can come from, but mainly it comes from the URL. However, you can instruct Views to look elsewhere for contexts as well – such as the ID of the logged in user.

What we’ll do now is add a contextual filter so that the View shows only the articles authored by the logged in user. So go ahead and add a new contextual filter of the type Content: Author uid. Next, under the WHEN THE FILTER VALUE IS NOT IN THE URL fieldset, select the Provide default value radio. Our goal here is to have Views look elsewhere if it does not find the user ID in the URL.

contextual filters

You then have some options under the Type select list, where you should choose User ID from logged in user. This will make Views take the ID of the user that is logged in and pass it to the View as a filter. The rest you can leave as is and save the filter. You’ll immediately notice in your preview that only articles authored by you show up. The filtering is taking place dynamically. If you log in with another user account, you should see only the articles authored by that user account.

A great thing about contextual filters is that if you are displaying a View programatically in a custom module, you can pass the filtering value in code, which opens the door to many possibilities.

Rewriting fields

The last thing we will do in this tutorial is look at rewriting fields in order to concatenate their values. We will illustrate this technique by changing the title field to include the author username in parenthesis.

We’ll start by rearranging the order of the fields and move the title to be the last one showing. The reason we want to do this is that when you rewrite fields, you can use tokens that get values only from fields that are added before the one being rewritten. And since we want to rewrite the title field, we want the token for the username value to be present so we need to move it before the title field.

Now that the title field is last, edit the author username field and uncheck the box Create a label and then check the box Exclude from display. You can now save the field. The reason we are excluding this field from being displayed in our View is so that we don’t duplicate it once we concatenate it to the title field.

rewriting fields

Next, edit the title field and under REWRITE RESULTS, check the box Rewrite the output of this field. A new textarea should appear below where we will write the new contents of this field. If you write some gibberish in there and save the field, you’ll notice the title gets replaced by that gibberish.

Below this textarea, you’ll notice also some REPLACEMENT PATTERNS. These represent tokens of all the fields in the View loaded before this one (and including this one as well). So if you followed along, you’ll see there [name] and [title], among others.

What we need to do now is put these tokens in this box, wrapped with the text or markup we want. Having said that we want the username to be in parenthesis after the node title, we can add the following to the text box to achieve this:

[title] ([name])

Save the field and check out the result. Now you should have the author user in parenthesis. However, it’s still not perfect. We left the title field’s Link this field to the original piece of content box checked and this is breaking the output for us a bit due to also the username having a link to the user profile page. What we want is a clean link to the node title and in parenthesis (which themselves do not link to anything), the username linking to the user profile page.

So first up, add a new field called Content: Path (the path to the node). Make sure you exclude it from display, remove its label and move it before the title field. Then, edit the title field, uncheck the Link this field to the original piece of content box and replace the REWRITE RESULTS text with this:

 href="[path]">[title] ([name])

The [path] token is available from the new field we just added. And after you save, you should see already in the preview a much cleaner display of title nodes and usernames in parenthesis.

Conclusion

In this tutorial we’ve looked at three main aspects of building Views in Drupal 7: relationships, contextual filters and rewriting fields. We’ve seen how with the use of relationships we can use information also from related entities, not just those on the base table a View is built on. Contextual filters are great for when the View needs to display content dynamically depending on various contextual conditions (such as a URL or logged-in user). Lastly, we’ve learned how to rewrite fields and build more complex ones with values taken from multiple fields. As you can see, this technique is very powerful for theming Views as it allows us to output complex markup.

Views is pretty much the most popular Drupal module and it is highly complex. Despite its complexity, building views as a site administrator is very easy. All you need to understand is a few basic concepts and you are good to go. Developing for Views to extend its functionality or expose data to it is also an enjoyable experience. If you’d like to know more about that, you can read my tutorial on exposing your own custom module table to Views right here on Sitepoint.com.

Jun 18 2014
Jun 18

How to Build a Drupal 8 Module

In the previous article on Drupal 8 module development, we’ve looked at creating block types and forms. We’ve seen that blocks are now reusable and how everything we need to do for defining block types happens in one single class. Similarly, form generation functions are also grouped under one class with specific methods performing tasks similar to what we are used to in Drupal 7.

In this tutorial, I will continue where we left off. I will illustrate how we can turn our DemoForm into a form used to store a value through the Drupal 8 configuration system. Following that, we will talk a bit about the service container and dependency injection by way of illustration.

Don’t forget that you can check out this repository if you want to get all the code we write in this tutorial series.

When we first defined our DemoForm, we extended the FormBase class which is the simplest implementation of the FormInterface. However, Drupal 8 also comes with a ConfigFormBase that provides some additional functionality which makes it very easy to interact with the configuration system.

What we will do now is transform DemoForm into one which will be used to store the email address the user enters. The first thing we should do is replace the extended class with ConfigFormBase (and of course use it):

use Drupal\Core\Form\ConfigFormBase;

class DemoForm extends ConfigFormBase {

Before we move on to changing other things in the form, let’s understand a bit how simple configuration works in Drupal 8. I say simple because there are also configuration entities that are more complex and that we will not cover today. As it stands now, configuration provided by modules (core or contrib) is stored in YAML files. On enabling a module, this data gets imported into the database (for better performance while working with it). Through the UI we can change this configuration which is then easily exportable to YAML files for deployment across different sites.

A module can provide default configuration in a YAML file located in the config/install folder in the module root directory. The convention for naming this file is to prefix it with the name of the module. So let’s create one called demo.settings.yml. Inside this file, let’s paste the following:

demo:
  email_address: [email protected]

This is a nested structure (like an associative array in PHP). Under the key demo, we have another key|value pair. And usually to access these nested values we use a dot(.). In our case demo.email_address.

Once we have this file in place, an important thing you need to remember is that this file gets imported only when the module is installed. So go ahead and reinstall it. And now we can turn back to our form and go through the methods that need adapting one by one.

This is how the buildForm() method should look like now:

public function buildForm(array $form, array &$form_state) {
  
  $form = parent::buildForm($form, $form_state);
  
  $config = $this->config('demo.settings');
  
  $form['email'] = array(
    '#type' => 'email',
    '#title' => $this->t('Your .com email address.'),
    '#default_value' => $config->get('demo.email_address')
  );
  
  return $form;
}

First of all, as opposed to FormBase, the ConfigFormBase class implements this method as well in order to add elements to the form array (a submit button). So we can use what the parent did before adding our own elements.

Now for the configuration part. Drupal 8 provides a Config object that we can use to interact with the configuration. Some classes already have it available through dependency injection. ConfigFormBase is one such class.

As you can see, we are using the config() method of the parent class to retrieve a Config object populated with our demo.settings simple configuration. Then, for the #default_value of the email form element, we use the get() method of the Config object to retrieve the value of the email address.

Next, we only need to change the submit handler because the validateForm() method can stay the same for now:

public function submitForm(array &$form, array &$form_state) {
  
  $config = $this->config('demo.settings');
  $config->set('demo.email_address', $form_state['values']['email']);
  $config->save();
  
  return parent::submitForm($form, $form_state);
}

In this method we first retrieve the Config object for our configuration (like we did before). Then, we use its set() method to change the value of the email_address to the value the user submitted. Then we use the save() method to save the configuration. Lastly, we extend the parent submit handler because it does contain some functionality (in this case it sets a Drupal message to the screen).

And that’s pretty much it. You can clear the cache and try it out. By submitting a new email address, you are storing it in the configuration. The module demo.settings.yml file won’t change of course, but you can go and export the demo.settings configuration and import it into another site.

The service container and dependency injection

The next thing we are going to look at is the service container. The idea behind services is to split functionality into reusable components. Therefore a service is a PHP class that performs some global operations and that is registered with the service container in order to be accessed.

Dependency injection is the way through which we pass objects to other objects in order to ensure decoupling. Each service needs to deal with one thing and if it needs another service, the latter can be injected into the former. But we’ll see how in a minute.

Going forward, we will create a very simple service and register it with the container. It will only have one real method that returns a simple value. Then, we will inject that service as a dependency to our DemoController and make use of the value provided by the service.

In order to register a service, we need to create a demo.services.yml file located in the root of our module, with the following contents:

services:
    demo.demo_service:
        class: Drupal\demo\DemoService

The file naming convention is module_name.services.yml.

The first line creates an array of services. The second line defines the first service (called demo_service, prefixed by the module name). The third line specifies the class that will be instantiated for this service. It follows to create the DemoService.php class file in the src/ folder of our module. This is what my service does (nothing really, it’s just to illustrate how to use it):

<?php

/**
 * @file
 * Contains Drupal\demo\DemoService.
 */

namespace Drupal\demo;

class DemoService {
  
  protected $demo_value;
  
  public function __construct() {
    $this->demo_value = 'Upchuk';
  }
  
  public function getDemoValue() {
    return $this->demo_value;
  }
  
}

No need to explain anything here as it’s very basic. Next, let’s turn to our DemoController and use this service. There are two ways we can do this: accessing the container globally through the \Drupal class or use dependency injection to pass an object of this class to our controller. Best practice says we should do it the second way, so that’s what we’ll do. But sometimes you will need to access a service globally. For that, you can do something like this:

$service = \Drupal::service('demo.demo_service');

And now $service is an object of the class DemoService we just created. But let’s see how to inject our service in the DemoController class as a dependency. I will explain first what needs to be done, then you’ll see the entire controller with all the changes made to it.

First, we need access to the service container. With controllers, this is really easy. We can extend the ControllerBase class which gives us that in addition to some other helpers. Alternatively, our Controller can implement the ContainerInjectionInterface that also gives us access to the container. But we’ll stick to ControllerBase so we’ll need to use that class.

Next, we need to also use the Symfony 2 ContainerInterface as a requirement of the create() method that instantiates another object of our controller class and passes to it the services we want.

Finally, we’ll need a constructor to get the passed service objects (the ones that create() returns) and assign them to properties for later use. The order in which the objects are returned by the create() method needs to be reflected in the order they are passed to the constructor.

So let’s see our revised DemoController:

<?php

/**
 * @file
 * Contains \Drupal\demo\Controller\DemoController.
 */

namespace Drupal\demo\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * DemoController.
 */
class DemoController extends ControllerBase {
  
  protected $demoService;
  
  /**
   * Class constructor.
   */
  public function __construct($demoService) {
    $this->demoService = $demoService;
  }
  
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('demo.demo_service')
    );
  }
  
  /**
   * Generates an example page.
   */
  public function demo() {
    return array(
      '#markup' => t('Hello @value!', array('@value' => $this->demoService->getDemoValue())),
    );
  }
}

As you can see, all the steps are there. The create() method creates a new instance of our controller class passing to it our service retrieved from the container. And in the end, an instance of the DemoService class gets stored in the $demoService property, and we can use it to call its getDemoValue() method. And this value is then used in the Hello message. Clear your cache and give it a try. Go to the demo/ path and you should see Hello Upchuk! printed on the page.

I’m sure you can see the power of the service container as we can now write decoupled functionality and pass it where it’s needed. I did not show you how, but you can also declare dependencies when you register services. This means that when Drupal instantiates a service object, it will do so for all its dependencies as well, and pass them to its constructor. You can read more about how to do that on this documentation page.

Conclusion

In this article we’ve looked at a lot of cool stuff. We’ve seen how the configuration system manages simple configuration and what we have available form-wise for this. I do encourage you to explore how the ConfigFormBase is implemented and what you have available if you extend it. Additionally, you should play around in the UI with importing/exporting configuration between sites. This will be a great improvement for the deployment process from now on.

Then, we looked at services, what they are and how they work. A great way of maintaining reusable and decoupled pieces of functionality accessible from anywhere. And I do hope the concept of dependency injection is no longer so scary (if it was for you). It is basically the equivalent of passing parameters to procedural functions, but done using constructor methods (or setters), under the hood, by Symfony and its great service container.

Jun 16 2014
Jun 16

How to Build a Drupal 8 Module

In the first installment of this article series on Drupal 8 module development we started with the basics. We’ve seen what files were needed to let Drupal know about our module, how the routing process works and how to create menu links programatically as configuration.

In this tutorial we are going to go a bit further with our sandbox module found in this repository and look at two new important pieces of functionality: blocks and forms. To this end, we will create a custom block that returns some configurable text. After that, we will create a simple form used to print out user submitted values to the screen.

Drupal 8 blocks

A cool new change to the block API in D8 has been a switch to making blocks more prominent, by making them plugins (a brand new concept). What this means is that they are reusable pieces of functionality (under the hood) as you can now create a block in the UI and reuse it across the site – you are no longer limited to using a block only one time.

Let’s go ahead and create a simple block type that prints to the screen Hello World! by default. All we need to work with is one class file located in the src/Plugin/Block folder of our module’s root directory. Let’s call our new block type DemoBlock, and naturally it needs to reside in a file called DemoBlock.php. Inside this file, we can start with the following:

<?php

namespace Drupal\demo\Plugin\Block;

use Drupal\block\BlockBase;
use Drupal\Core\Session\AccountInterface;

/**
 * Provides a 'Demo' block.
 *
 * @Block(
 *   id = "demo_block",
 *   admin_label = @Translation("Demo block"),
 * )
 */

class DemoBlock extends BlockBase {
  
  /**
   * {@inheritdoc}
   */
  public function build() {    
    return array(
      '#markup' => $this->t('Hello World!'),
    );
  }
  
  /**
   * {@inheritdoc}
   */
  public function access(AccountInterface $account) {
    return $account->hasPermission('access content');
  }  
  
}

Like with all other class files we start by namespacing our class. Then we use the BlockBase class so that we can extend it, as well as the AccountInterface class so that we can get access to the currently logged in user. Then follows something you definitely have not seen in Drupal 7: annotations.

Annotations are a PHP discovery tool located in the comment block of the same file as the class definition. Using these annotations we let Drupal know that we want to register a new block type (@Block) with the id of demo_block and the admin_label of Demo block (passed through the translation system).

Next, we extend the BlockBase class into our own DemoBlock, inside of which we implement two methods (the most common ones you’ll implement). The build() method is the most important as it returns a renderable array the block will print out. The access() method controls access rights for viewing this block. The parameter passed to it is an instance of the AccountInterface class which will be in this case the current user.

Another interesting thing to note is that we are no longer using the t() function globally for translation but we reference the t() method implemented in the class parent.

And that’s it, you can clear the caches and go to the Block layout configuration page. The cool thing is that you have the block types on the right (that you can filter through) and you can place one or more blocks of those types to various regions on the site.

Drupal 8 block configuration

Now that we’ve seen how to create a new block type to use from the UI, let’s tap further into the API and add a configuration form for it. We will make it so that you can edit the block, specify a name in a textfield and then the block will say hello to that name rather than the world.

First, we’ll need to define the form that contains our textfield. So inside our DemoBlock class we can add a new method called blockForm():

/**
 * {@inheritdoc}
 */
public function blockForm($form, &$form_state) {
  
  $form = parent::blockForm($form, $form_state);
  
  $config = $this->getConfiguration();

  $form['demo_block_settings'] = array(
    '#type' => 'textfield',
    '#title' => $this->t('Who'),
    '#description' => $this->t('Who do you want to say hello to?'),
    '#default_value' => isset($config['demo_block_settings']) ? $config['demo_block_settings'] : '',
  );
  
  return $form;
}

This form API implementation should look very familiar from Drupal 7. There are, however, some new things going on here. First, we retrieve the $form array from the parent class (so we are building on the existing form by adding our own field). Standard OOP stuff. Then, we retrieve and store the configuration for this block. The BlockBase class defines the getConfiguration() method that does this for us. And we place the demo_block_settings value as the #default_value in case it has been set already.

Next, it’s time for the submit handler of this form that will process the value of our field and store it in the block’s configuration:

/**
* {@inheritdoc}
*/
public function blockSubmit($form, &$form_state) {
 
 $this->setConfigurationValue('demo_block_settings', $form_state['values']['demo_block_settings']);
 
} 

This method also goes inside the DemoBlock class and all it does is save the value of the demo_block_settings field as a new item in the block’s configuration (keyed by the same name for consistency).

Lastly, we need to adapt our build() method to include the name to say hello to:

 /**
 * {@inheritdoc}
 */
public function build() {
  
  $config = $this->getConfiguration();
  
  if (isset($config['demo_block_settings']) && !empty($config['demo_block_settings'])) {
    $name = $config['demo_block_settings'];
  }
  else {
    $name = $this->t('to no one');
  }
  
  return array(
    '#markup' => $this->t('Hello @name!', array('@name' => $name)),
  );  
}

By now, this should look fairly easy. We are retrieving the block’s configuration and if the value of our field is set, we use it for the printed statement. If not, use use a generic one. You can clear the cache and test it out by editing the block you assigned to a region and add a name to say hello to. One thing to keep in mind is that you are still responsible for sanitizing user input upon printing to the screen. I have not included these steps for brevity.

Drupal 8 forms

The last thing we are going to explore in this tutorial is how to create a simple form. Due to space limitations, I will not cover the configuration management aspect of it (storing configuration values submitted through forms). Rather, I will illustrate a simple form definition, the values submitted being simply printed on the screen to show that it works.

In Drupal 8, form definition functions are all grouped together inside a class. So let’s define our simple DemoForm class inside src/Form/DemoForm.php:

<?php

/**
 * @file
 * Contains \Drupal\demo\Form\DemoForm.
 */

namespace Drupal\demo\Form;

use Drupal\Core\Form\FormBase;

class DemoForm extends FormBase {
  
  /**
   * {@inheritdoc}.
   */
  public function getFormId() {
    return 'demo_form';
  }
  
  /**
   * {@inheritdoc}.
   */
  public function buildForm(array $form, array &$form_state) {
    
    $form['email'] = array(
      '#type' => 'email',
      '#title' => $this->t('Your .com email address.')
    );
    $form['show'] = array(
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    );
    
    return $form;
  }
  
  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, array &$form_state) {
    
    if (strpos($form_state['values']['email'], '.com') === FALSE ) {
      $this->setFormError('email', $form_state, $this->t('This is not a .com email address.'));
    } 
  }
  
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, array &$form_state) {
    
    drupal_set_message($this->t('Your email address is @email', array('@email' => $form_state['values']['email'])));
  }
  
}

Apart from the OOP side of it, everything should look very familiar to Drupal 7. The Form API has remained pretty much unchanged (except for the addition of some new form elements and this class encapsulation). So what happens above?

First, we namespace the class and use the core FormBase class so we can extend it with our own DemoForm class. Then we implement 4 methods, 3 of which should look very familiar. The getFormId() method is new and mandatory, used simply to return the machine name of the form. The buildForm() method is again mandatory and it builds up the form. How? Just like you are used to from Drupal 7. The validateForm() method is optional and its purpose should also be quite clear from D7. And finally, the submitForm() method does the submission handling. Very logical and organised.

So what are we trying to achieve with this form? We have an email field (a new form element in Drupal 8) we want users to fill out. By default, Drupal checks whether the value input is in fact an email address. But in our validation function we make sure it is a .com email address and if not, we set a form error on the field. Lastly, the submit handler just prints a message on the page.

One last thing we need to do in order to use this form is provide a route for it. So edit the demo.routing.yml file and add the following:

demo.form:
  path: '/demo/form'
  defaults:
    _form: '\Drupal\demo\Form\DemoForm'
    _title: 'Demo Form'
  requirements:
    _permission: 'access content'

This should look familiar from the previous article in which we routed a simple page. The only big difference is that instead of _content under defaults, we use _form to specify that the target is a form class. And the value is therefore the class name we just created.

Clear the caches and navigate to demo/form to see the form and test it out.

If you are familiar with drupal_get_form() and are wondering how to load a form like we used to in Drupal 7, the answer is in the global Drupal class. Thus to retrieve a form, you can use its formBuilder() method and do something like this:

$form = \Drupal::formBuilder()->getForm('Drupal\demo\Form\DemoForm');

Then you can return $form which will be the renderable array of the form.

Conclusion

In this article we’ve continued our exploration of Drupal 8 module development with two new topics: blocks and forms. We’ve seen how to create our own block type we can use to create blocks in the UI. We’ve also learned how to add a custom configuration to it and store the values for later use. On the topic of forms, we’ve seen a simple implementation of the FormBase class that we used to print out to the screen the value submitted by the user.

In the next tutorial we will take a quick look at configuration forms. We will save the values submitted by the user using the Drupal 8 configuration system. Additionally, we will look at the service container and dependency injection and how those work in Drupal 8. See you then.

Jun 13 2014
Jun 13

How to Build a Drupal 8 Module

Drupal 8 brings about a lot of changes that seek to enroll it in the same club other modern PHP frameworks belong to. This means the old PHP 4 style procedural programming is heavily replaced with an object oriented architecture. To achieve this, under the initiative of Proudly Found Elsewhere, Drupal 8 includes code not developed specifically for Drupal.

One of the most important additions to Drupal are Symfony components, with 2 major implications for Drupal developers. First, it has the potential to greatly increase the number of devs that will now want to develop for Drupal. And second, it gives quite a scare to some of the current Drupal 7 developers who do not have much experience with modern PHP practices. But that’s ok, we all learn, and lessons taken from frameworks like Symfony (and hopefully Drupal 8), will be easily extensible and applicable to other PHP frameworks out there.

In the meantime, Drupal 8 is in a late stage of its release cycle, the current version at the time of writing being alpha11. We will use this version to show some of the basic changes to module development Drupal 7 devs will first encounter and should get familiar with. I set up a Git repo where you can find the code I write in this series so you can follow along like that if you want.

How do I create a module?

The first thing we are going to look at is defining the necessary files and folder structure to tell Drupal 8 about our new module. In Drupal 7 we had to create at least 2 files (.info and .module), but in Drupal 8, the YAML version of the former is enough. And yes, .info files are now replaced with .info.yml files and contain similar data but structured differently.

Another major change is that custom and contrib module folders now go straight into the root modules/ folder. This is because all of the core code has been moved into a separate core/ folder of its own. Of course, within the modules/ directory, you are encouraged to separate modules between custom and contrib like in Drupal 7.

Let’s go ahead and create a module called demo (very original) and place it in the modules/custom/ directory. And as I mentioned, inside of this newly created demo/ folder, all we need to begin with is a demo.info.yml file with the following required content:

name: Drupal 8 Demo module
description: 'Demo module for Drupal 8 alpha11'
type: module
core: 8.x

Three out of four you should be familiar with (name, description and core). The type is now also a requirement as you can have yml files for themes as well. Another important thing to keep in mind is that white spaces in yml files mean something and proper indentation is used to organize data in array-like structures.

You can check out this documentation page for other key|value pairs that can go into a module .info.yml file and the change notice that announced the switch to this format.

And that’s it, one file. You can now navigate to the Extend page, find the Demo module and enable it.

As I mentioned, we are no longer required to create a .module file before we can enable the module. And architecturally speaking, the .module files will be significantly reduced in size due to most of the business logic moving to service classes, controllers and plugins, but we’ll see some of that later.

What is ‘routing’ and what happened to hook_menu() and its callbacks?

In Drupal 7, hook_menu() was probably the most implemented hook because it was used to define paths to Drupal and connect these paths with callback functions. It was also responsible for creating menu links and a bunch of other stuff.

In Drupal 8 we won’t need hook_menu() anymore as we make heavy use of the Symfony2 components to handle the routing. This involves defining the routes as configuration and handling the callback in a controller (the method of a Controller class). Let’s see how that works by creating a simple page that outputs the classic Hello world!.

First, we need to create a routing file for our module called demo.routing.yml. This file goes in the module root folder (next to demo.info.yml). Inside this file, we can have the following (simple) route definition:

demo.demo:
  path: '/demo'
  defaults:
    _content: '\Drupal\demo\Controller\DemoController::demo'
    _title: 'Demo'
  requirements:
    _permission: 'access content'

The first line marks the beginning of a new route called demo for the module demo (the first is the module name and the second the route name). Under path, we specify the path we want this route to register. Under defaults, we have two things: the default page title (_title) and the _content which references a method on the DemoController class. Under requirements, we specify the permission the accessing user needs to have to be able to view the page. You should consult this documentation page for more options you can have for this routing file.

Now, let’s create our first controller called DemoController that will have a method named demo() getting called when a user requests this page.

Inside the module directory, create a folder called src/ and one called Controller/ inside of it. This will be the place to store the controller classes. Go ahead and create the first one: DemoController.php.

The placement of the Controllers and, as we will see, other classes, into the src/ folder is part of the adoption of the PSR-4 standard. Initially, there was a bigger folder structure we had to create (PSR-0 standard) but now there is a transition phase in which both will work. So if you still see code placed in a folder called lib/, that’s PSR-0.

Inside of our DemoController.php file, we can now declare our class:

<?php
/**
 * @file
 * Contains \Drupal\demo\Controller\DemoController.
 */

namespace Drupal\demo\Controller;

/**
 * DemoController.
 */
class DemoController {
  /**
   * Generates an example page.
   */
  public function demo() {
    return array(
      '#markup' => t('Hello World!'),
    );
  }      
}

This is the simplest and minimum we need to do in order to get something to display on the page. At the top, we specify the class namespace and below we declare the class.

Inside the DemoController class, we only have the demo() method that returns a Drupal 7-like renderable array. Nothing big. All we have to do now is clear the caches and navigate to http://example.com/demo and we should see a Drupal page with Hello World printed on it.

In Drupal 7, when we implement hook_menu(), we can also add the registered paths to menus in order to have menu links showing up on the site. This is again no longer handled with this hook but we use a yml file to declare the menu links as configuration.

Let’s see how we can create a menu link that shows up under the Structure menu of the administration. First, we need to create a file called demo.menu_links.yml in the root of our module. Inside this yml file we will define menu links and their position in existing menus on the site. To achieve what we set out to do, we need the following:

demo.demo:
  title: Demo Link
  description: 'This is a demo link'
  parent: system.admin_structure
  route_name: demo.demo

Again we have a yml structure based on indentation in which we first define the machine name of the menu link (demo) for the module demo (like we did with the routing). Next, we have the link title and description followed by the parent of this link (where it should be placed) and what route it should use.

The value of parent is the parent menu link (appended by its module) and to find it you need to do a bit of digging in *.menu_links.yml files. I know that the Structure link is defined in the core System module so by looking into the system.menu_links.yml file I could determine the name of this link.

The route_name is the machine name of the route we want to use for this link. We defined ours earlier. And with this in place, you can clear the cache and navigate to http://example.com/admin/structure where you should now see a brand new menu link with the right title and description and that links to the demo/ path. Not bad.

Conclusion

In this article we began exploring module development in Drupal 8. At this stage (alpha11 release), it is time to start learning how to work with the new APIs and port contrib modules. To this end, I am putting in writing my exploration of this new and exiting framework that will be Drupal 8 so that we can all learn the changes and hit the ground running when release day comes.

For starters, we looked at some basics: how you start a Drupal 8 module (files, folder structure etc), all compared with Drupal 7. We’ve also seen how to define routes and a Controller class with a method to be called by this route. And finally, we’ve seen how to create a menu link that uses the route we defined.

In the next tutorial, we will continue building this module and look at some other cool new things Drupal 8 works with. We will see how we can create blocks and how to work with forms and the configuration system. See you then.

May 20 2013
May 20

20th May
2013

This is a bit of a follow-up to Mike Bell's introductory article on using Codeception to create Drupal test suites. He concludes by stating he "need[s] to figure out a way of creating a Codeception module which allows you to plug in a Drupal testing user (ideally multiple so you can test each role) and then all the you have to do is call a function which executes the above steps to confirm your logged in before testing authenticated behaviour."

"Something along the lines of:

$I->drupalLogin('editor');

So, after skimming through Codeception and Mink documentation, I've tinkered with two potential ways of achieving this... for acceptance testing at least.

A crude toolbox

The first method is to use two custom classes to provide details of (a) a general Drupal site and (b) the specific site to be tested. This idea stemmed from this article which suggests that including literals - such as account credentials, paths and even form labels - in tests is bad practice. What if the login button label changes? etc.

Anyway, this is currently set up as follows. In the tests/_helpers directory, we include a new file providing an abstract class, DrupalSite:


  1. abstract class DrupalSite {

  2. // Site structure: login and registration.

  3. public $loginPage = 'user/login';

  4. public $usernameField = 'Username';

  5. public $passwordField = 'Password';

  6. public $loginSubmitField = 'edit-submit';

  7. // Site data: user accounts.

  8. public $adminUsername;

  9. public $adminPassword;

  10. }

It contains some defaults (the usual path to the login page, the default labels for Username & Password fields and the Login submit button) and two member variables to hold a test admin user's credentials. Then, in order to provide some values specific to the site we're testing, we extend that class to provide some of the missing information:


  1. class MySite extends DrupalSite {

  2. // Site data: user accounts.

  3. public $adminUsername = 'admin';

  4. public $adminPassword = 'test';

  5. }

Assuming we have such a system in place (it's proving useful in other areas already, such as managing HTTP authentication on testing and staging environments and dealing with Drupal's clean URLs) then we can also use the DrupalSite class to provide Drupal- or site-specific routines for, eg, logging in:


  1. abstract class DrupalSite {

  2. // Site structure: meta data.

  3. ...

  4. // Site structure: login and registration.

  5. public $loginPage = 'user/login';

  6. public $usernameField = 'Username';

  7. public $passwordField = 'Password';

  8. public $loginSubmitField = 'edit-submit';

  9. // Site data: user accounts.

  10. public $adminUsername;

  11. public $adminPassword;

  12. public $testUsername;

  13. public $testPassword;

  14. /**

  15.   * Acceptance helper to log in an (admin) user.

  16.   */

  17. public function logInAsAdminUser($I) {

  18. $this->logIn($I, $this->adminUsername, $this->adminPassword);

  19. }

  20. /**

  21.   * Acceptance helper to log in a test user.

  22.   */

  23. public function logInAsTestUser($I) {

  24. $this->logIn($I, $this->testUsername, $this->testPassword);

  25. }

  26. /**

  27.   * Acceptance helper to log in a user with given credentials.

  28.   *

  29.   * @param $I

  30.   * @param $username

  31.   * @param $password

  32.   */

  33. protected function logIn($I, $username, $password) {

  34. $I->amOnPage($this->getSiteUrl($this->loginPage));

  35. $I->see('User account');

  36. $I->see('Enter your [$site_name] username.');

  37. $I->amGoingTo('fill and submit the login form');

  38. $I->fillField($this->usernameField, $username);

  39. $I->fillField($this->passwordField, $password);

  40. $I->click($this->loginSubmitField);

  41. $I->expect('to be logged in');

  42. // @todo You'll probably have a much better way of verifying

  43. // whether we've successfully logged in.

  44. $I->see('My account');

  45. $I->see('Log out');

  46. }

  47. }

Of course we must set the site-specific user credentials in MySite.php. We can also override the method in the subclass to provide an alternative method of logging in, if the site provides alternate or customised login methods (such as a single sign-on implementation). In addition, we can benefit from building on these classes to provide, for example, a better structure for managing site roles and corresponding test user accounts or other helper functionality such as passing HTTP authentication or managing clean URLs for paths used in tests.

To actually put this into practice and use it in a test, we must first include the subclass in the acceptance suite's _bootstrap.php:

require_once 'tests/_helpers/MySite.php';

then instantiate an object of the MySite class in our test:


  1. $I = new WebGuy($scenario);

  2. $S = new MySite($I);

  3. $I->wantTo('log in as an admin');

  4. $S->logInAsAdminUser($I);

  5. // Verify login steps...

Using WebHelper

The second method is perhaps more in line with Mike's idea of using Codeception's helpers to build new methods into the WebGuy object $I:

$I->loginToDrupal('editor');

Codeception achieves this by "emulat[ing] multiple inheritance for Guy classes (CodeGuy, TestGuy, WebGuy, etc)". Custom actions "can be defined in helper classes", Basically, the Guy classes have their methods defined in modules. They don't actually truly contain any of them, but act as a proxy for them. Codeception provides modules to emulate web requests, access data, interact with popular PHP libraries, etc. On top of this, we can provide additional methods using the corresponding Helper class to the relevant Guy class.

To 'enable' all of the gathered Guy methods, you use the build command. It generates the definition of the Guy class by copying the signatures from the configured modules:

$ codecept.phar build

For more about this, see the Codeception guide to Modules and Helpers.

Phew. With all that in mind, we can dive into editing the empty WebHelper class Codeception provides. We have to dig a little deeper to implement this: trying to use the test 'sub-routine' idea from above (i.e. implementing the login procedure as a series of $I scenario steps) doesn't really fit, but we can kludge it like so:


  1. <?php

  2. namespace Codeception\Module;

  3. // here you can define custom functions for WebGuy

  4. class WebHelper extends \Codeception\Module {

  5. /**

  6.   * Helper function to log in WebGuy with given credentials. We pass in

  7.   * in $I, resulting in a call like:

  8.   *

  9.   * $I->loginToDrupal($I, $name, $pass);

  10.   *

  11.   * This is horrible, and I'm probably missing something.

  12.   *

  13.   * @param $I

  14.   * @param $name

  15.   * @param $pass

  16.   */

  17. function loginToDrupal($I, $name, $pass) {

  18. $I->amOnPage('user/login');

  19. $I->see('User account');

  20. $I->see('Enter your [$site_name] username.');

  21. $I->amGoingTo('fill and submit the login form');

  22. $I->fillField('name', $name);

  23. $I->fillField('pass', $pass);

  24. $I->click('Log in');

  25. $I->expect('to be logged in');

  26. // @todo Provide the verification steps after successfully

  27. // being logged in here.

  28. $I->see(...);

  29. }

  30. }

but boy, does that seem nasty. If we're going down this route, it would be best left to using custom classes as above.

Shminky pinky (Chris Waddle)

Codeception by default uses the phpBrowser module for acceptance tests, and Mink to control it. The Mink Acceptance Testing documentation was a great place to start looking deeper - and of course with any OO frameworks the Session class API documentation also proved useful.

So, we can directly manipulate the browser session using Mink from within what will eventually be our new WebGuy method. I ended up with something like this:


  1. <?php

  2. namespace Codeception\Module;

  3. // here you can define custom functions for WebGuy

  4. class WebHelper extends \Codeception\Module {

  5. // Frist attempt at a custom login function...

  6. public function login() {

  7. $username = 'admin';

  8. $password = 'test';

  9. $session = $this->getModule('PhpBrowser')->session;

  10. $login_url = $session->getCurrentUrl() . '/user/login';

  11. $session->visit($login_url);

  12. // Fail the test step if we cannot access the login page.

  13. $this->assertTrue(

  14. $session->getStatusCode() == 200,

  15. 'could not access login page'

  16. );

  17. // Get login form elements from $page.

  18. $page =$session->getPage();

  19. $loginForm = $page->findById('user-login');

  20. $usernameField = $loginForm->findField('edit-name');

  21. $passwordField = $loginForm->findField('edit-pass');

  22. $submitButton = $loginForm->findButton('edit-submit');

  23. // Enter credentials and submit the form.

  24. $usernameField->setValue($username);

  25. $passwordField->setValue($password);

  26. $submitButton->click();

  27. }

  28. }

and the corresponding test:


  1. $I = new WebGuy($scenario);

  2. $I->wantTo('log in as an admin');

  3. $I->login();

  4. // Verify log in, if necessary.

  5. // Continue additional test steps.

Of course we can be a bit cleverer in passing in the test user's role and/or account credentials by using our custom class MySite - getting the best of both worlds. We use the custom classes to provide information about the structure of the site we're testing and WebHelper to add a 'proper' new method for WebGuy objects. Note there are site 'component' literals including in the login() method, such as the ids for the form elements and the path to the login page, but hey - WIP etc.

Caveat - login sessions and database refreshes

At this point I noticed some of my tests started failing. I realised that, in running multiple tests, subsequent session visits we're already logged in, resulting in a 403 HTTP code being returned when visiting the user login page. As you might have noticed in the code above, there is a slightly crappy assertTrue statement to check the page response is a 200. It's not, so the test fails. So our log in/session issue here is is mostly down to checks in the login method that could be improved somewhat.

Anyway, we might get away with it - tests should ideally be run on a clean, stable version of the site database and be cleaned-up or refreshed before any test is ran. One test should never affect another - it's likely that some of our tests will write to the database (testing creating a new node, creating a user etc.) so we should really use the Db module's cleanup configuration option. To set up database refreshes, do the following:

  1. place a clean SQL dump of the site's database in tests/_data/ using, eg, drush sql-dump --result-file=/path/to/suite/tests/_data/project_db_clean.sql
  2. edit the acceptance.suite.yml file to include the Db module and add configuration for your MySQL server:

  1. # Codeception Test Suite Configuration

  2. # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.

  3. class_name: WebGuy

  4. modules:

  5. enabled:

  6. - PhpBrowser

  7. - WebHelper

  8. - Db

  9. config:

  10. PhpBrowser:

  11. url: 'http://project.drupal.dev:8080'
  12. Db:

  13. dsn: 'mysql:host=localhost;dbname=project_db'

  14. user: 'db_user'

  15. password: 'db_pass'

  16. dump: tests/_data/project_db_clean.sql

  17. populate: false

  18. cleanup: true

switching in appropriate values for dsn, user and password. Also ensure that the dump option points to the correct path within your test suite where the SQL dump is stored. Read more about this in the Cleaning Up section of the Codeception Acceptance Tests documentation.

So, which method shall we use for a login procedure?

Custom classes

  • With this method, the login procedure still effectively runs as a 'sub routine' of a test, i.e. it can (and does) contain wantTo, expect, see or other WebGuy method calls.
  • We can build on these classes to provide the roles (from stories or Drupal roles) and test user credentials for each.
  • Can be overridden in MySite.php if a site uses a alternate or customised login method.

Using WebHelper

  • No longer a test or 'subroutine' of a test, but effectively now a single step in a test scenario.
  • No longer site-specific.
  • Nicer integration with Codeception's framework.
  • Nicer syntax, e.g. $I->login('admin')

By introducing a new method to the WebGuy class, we effectively condense the login procedure into one, atomic test or scenario step. We can of course precede and follow this one step with wantTo, amGoingTo and see steps in our tests themselves. The step can also fail 'internally' and thus fail the calling test (for example if the session cannot access the login page).

However, we should realise that we have also removed the finer-grained steps of the original 'can log in' test. So, perhaps we should always use the WebHelper method providing we include a single test dedicated solely to testing the individual steps to login. Technically this could be a standard test or a 'subroutine' test as described in A crude toolbox above. However, the subroutine loses its value if we're to only call it once.

With that in mind, two of our tests might end up looking something like this:

AdminCanLoginCept.php - full


  1. $I = new WebGuy($scenario);

  2. // Used here for site structure/user credentials:

  3. $S = new MySite($I);

  4. $username = 'admin';

  5. $password = 'test';

  6. $I->wantTo('log in as an admin');

  7. $I->amOnPage($S->loginPage);

  8. $I->see('User account');

  9. $I->see('Enter your [$site_name] username.');

  10. $I->amGoingTo('fill and submit the login form');

  11. $I->fillField($S->usernameField, $username);

  12. $I->fillField($S->passwordField, $password);

  13. $I->click($S->loginSubmitField);

  14. $I->expect('to be logged in');

  15. // Verify login steps...

AdminCanLoginCept.php - optionally using custom classes and 'subroutine'


  1. $I = new WebGuy($scenario);

  2. $S = new MySite($I);

  3. $I->wantTo('log in as an admin');

  4. $S->logInAsAdminUser($I);

  5. // Verify login steps...

AdminCanPostArticle.php (and all other tests requiring login)


  1. $I = new WebGuy($scenario);

  2. $I->want to('post an article');

  3. $I->amGoingTo('login as an admin');

  4. $I->login('admin');

  5. $I->amGoingTo('post an article');

  6. $I->amOnPage('node/add/article');

  7. $I->fillField(...);

  8. ...

Where to go from here?

This brain-fart only really involves acceptance testing and of course has been delivered from an addled brain who has only just started looking into testing - and Codeception in particular. Once we've got some acceptance suites under our belts, the most sensible place to start looking next would be functional tests - for which we can provide Framework Helpers:


  1. <?php

  2. namespace Codeception\Module;

  3. class DrupalHelper extends \Codeception\Util\Framework {

  4. public function _initialize() {

  5. $this->client = new \Codeception\Util\Connector\Universal();

  6. // or any other connector you implement

  7. // we need specify path to index file

  8. $this->client->setIndex('index.php');

  9. }

  10. }

Following that? A fully-blown Drupal module as part of the Codeception framework? Codeception suggests that "if you have written a module that may be useful to others, share it. Fork the Codeception repository, put the module into the src/Codeception/Module directory, and send a pull request."

Back at you, Mike ;)

Addendum: This article was written on my Nexus 7 - it was only when coming to post it here that I realised just how overdue some loving is for my site... I also had a bit of a re-write when a network/sync issue on my tablet (and the subsequent accessing of the article via the web interface at evernote.com) led to a loss of most the latter half...

Aug 09 2012
Aug 09

I love the fact we can get updates, and I would rather get them via RSS... But how many formats do we have?

This is not strictly bind to Drupal, but more a general thing. I did had to get involved due to a Drupal related job.

I have lately been heavily exposed to feeds/syndication protocols pushing the boundaries of what I knew in relation to this subject. There are lots of other news services (facebook, twitter, etc...) but the ones described over here provide a common ground/language that's open to anyone. You'll just need a reader (there are many including Firefox/Thunderbird) and you are set to go.

So what's out there? Basically 2 formats plus one for media syndication:
  • The first one to appear was RSS standard which has lots of version and is the oldest one.
  • Then you have Atom which is newer and is suppose to be an improvement over RSS.
  • Finally there is a separate one to syndicate media content called podcast, originally promoted by Apple.

Why so many? Don't know really... I guess we are humans after all. They do give headaches sometimes and they do have it's perks, but they also all use XML as the language of choice. So to distinguish them if you do not know the specific format differences you'll need to read the header.

Finally I want to mention feedburner which is a Google service now which provides some features that it is said to be really useful. I guess it provides flexibility and it can push content to different platforms like facebook or tweeter making your content easy to publish across different mediums.

In terms of Drupal you can use the feeds module to pull data from them into your site. That's involve and takes time to explain so I won't get into it now.

Jul 17 2012
ao2
Jul 17

There are cases when you want to export some content from a website, in order to import the same content at some later point into a similar website in an automated fashion.

In my case I want some “default content” to be imported every time I rebuild a Drupal site with drush make, this is particularly useful if you are building a Drupal distribution or a Drupal installation profile, or basing your product on these concepts.

I am going to show how to set up a source site and a destination site, and one possible way to export and import the content from and into Drupal.

In the following text there are some assumptions:

  • The Operating System is Debian GNU/Linux, in particular the group of the web server is www-data.
  • The web server has per-user web directories configured, and the user name is username; you have to substitute your own in URLs when you follow the instructions below.
  • The DBMS is MySQL (BTW, when drush sql-create becomes available I may update the article and drop this assumption).
  • In the code sections below, lines starting with $ are supposed to be commands to be written in a command line shell.
  • The reader has some Drupal knowledge, especially with regard to modules installation and content creation, knowing what the Features and Deploy modules do is a plus.

I used the OpenOutreach distribution because it is simple enough and provides a default content type which supports images, which are not trivial to export.

Let's get started.

A script to set up test sites

Here is a script to make it easier to build test sites under $HOME/public_html/, let's call it create_test_site.sh:

#!/bin/sh
 
set -e
 
[ $# -eq 5 ] || { echo "usage: $(basename $0) <db_name> <db_user> <db_password> <site_path> <site_name>" 1>&2; exit 1; }
 
DB_NAME="$1"
DB_USER="$2"
DB_PASS="$3"
 
echo -n "MySQL root user. "
mysql -u root -p <<EOM
CREATE DATABASE IF NOT EXISTS ${DB_NAME};
USE ${DB_NAME};
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES \
ON ${DB_NAME}.*
TO '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
FLUSH PRIVILEGES;
EOM
 
SITE_PATH="$4"
SITE_NAME="$5"
 
DISTRIBUTION=openoutreach
INSTALLATION_PROFILE=standard
WEB_SERVER_GROUP="www-data"
 
(
  cd $HOME/public_html
  drush dl "$DISTRIBUTION" --drupal-project-rename="$SITE_PATH"
  cd "$SITE_PATH"
  drush site-install "$INSTALLATION_PROFILE" \
    --site-name="$SITE_NAME" \
    --db-url="mysql://${DB_USER}:${DB_PASS}@localhost/${DB_NAME}"
  sed -i "[email protected]# RewriteBase /drupal\[email protected] /~${USER}/${SITE_PATH}@" .htaccess
  chmod 775 sites/default/files && \
  sudo chgrp -R "$WEB_SERVER_GROUP" sites/default/files
)

Set up the source site, create and export some content

Creating a new site is as simple as:

$ cd ~/public_html/
$ ./create_test_site.sh drupal_test_src user_test_src pass_test_src openoutreach_src "Source site"

The admin password will be printed on the standard output.

Log in to http://localhost/~username/openoutreach_src, go to node/add and create some contents with images.

Install the modules needed to export the content (NOTE: development versions are needed for now):

$ cd ~/public_html/openoutreach_src
$ drush dl --dev ctools deploy entity entity_dependency features uuid
$ drush en deploy deploy_ui entity entity_dependency features uuid
$ drush cc all

Go to admin/structure/deploy/plans/add and create a new deployment plan selecting these options:

  • Managed aggregator
  • Fetch-only

Go to admin/content, select some content and add it to the deployment plan using the “Update Options” dropdown menu.

Go to admin/structure/features/create in order to export the content to a Feature:

  1. Choose a name (e.g. Default Content) and a version (e.g. 7.x-0.1)
  2. select the option “Deployment: deploy_plans” from the “Edit components” dropdown menu, and you will see the deployment plan defined before;
  3. check it;
  4. select the option “UUID entities: uuid_entities” from the “Edit components” dropdown menu, and you will see an entry with the same name of the deployment plan defined before;
  5. check the entry with the same name of the deployment plan;
  6. then click on “Download feature”.

Let's say that the new feature module is called default_content-7.x-0.1.tar

NOTE

The resulting feature seems to be lacking dependencies, not only the needed modules from above are missing, but for instance the content type of the exported nodes is not mentioned anywhere either.

Anyhow, as long as you move contents between site installations with the same configuration the default_content feature will work fine.

Let's verify that, let's install a destination site.

Set up a destination site and import the content

$ cd ~/public_html/
$ ./create_test_site.sh drupal_test_dest user_test_dest pass_test_dest openoutreach_dest "Destination site"
$ cd ~/public_html/openoutreach_dest
$ drush dl --dev ctools deploy entity entity_dependency features uuid
$ drush en deploy entity entity_dependency features uuid
$ drush cc all

Install the exported feature and enable it:

$ cp default_content-7.x-0.1.tar ~/public_html/openoutreach_dest/sites/all/modules
$ cd ~/public_html/openoutreach_dest/sites/all/modules
$ tar xvf default_content-7.x-0.1.tar
$ cd ~/public_html/openoutreach_dest/
$ drush en default_content
$ drush cc all

Enjoy the imported content at http://localhost/~username/openoutreach_dest.

Final words

I know that a push deployment plan can be used to exchange content between two actual sites, but remember that I aim to import the content from code when rebuilding a site from scratch, that's why I exported the content to a “feature module”; in my use case, the destination site in this article can easily be seen as a future incarnation of the source site itself.

Someone may also wonder if it is right at all to export content as code; well, in my case I really see this “default content” as configuration so having it stored as PHP code in a feature module makes totally sense to me.

Feb 15 2011
ao2
Feb 15

I've added an IPv6 Greeter block on ao2.it in order to greet the IPv6 users, and to inform the others about the existence of the new protocol.

The code of the Drupal module is in the IPv6 Greeter sandbox, and I applied to create a full project on Drupal.org now that the Drupal migration to git is complete, so to have it properly reviewed and eventually hosted there.

If your site has a tech audience and it is built with Drupal, consider using the module above in order to raise the awareness that, like it or not, we could be needing IPv6 sooner than expected: the last IANA IPv4 Free pool has been allocated on January 31st 2011 to APNIC, while this does not mean automatically that we have run out of Ipv4 addresses for Internet nodes today, we should be prepared.

So deploy IPv6 NOW, and be ready for the World IPv6 Day.

Jan 12 2011
ao2
Jan 12

Added short URL links to ao2.it, you will find one at the end of each node content.

I decided to use the simple approach of nids as unique identifiers, together with Apache URL rewriting as seen in URL Shorteners Must Die.

Apply this patch to Drupal .htaccess to recognize short URLs in the form http://example.com/123:

Redirect URLs in the form
  http://example.com/123
to
  http://example.com/node/123
from here on Drupal will take care of any other URL aliasing.

Signed-off-by: Antonio Ospite <[email protected]>
---
 .htaccess |    3 +++
 1 file changed, 3 insertions(+)

Index: drupal/.htaccess
===================================================================
--- drupal.orig/.htaccess
+++ drupal/.htaccess
@@ -106,6 +106,9 @@
   # uncomment the following line:
   # RewriteBase /
 
+  # Allow short URLs of the form http://example.com/123
+  RewriteRule ^(\d+)$ index.php?q=node/$1 [L,QSA]
+
   # Rewrite URLs of the form 'x' to the form 'index.php?q=x'.
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteCond %{REQUEST_FILENAME} !-d

Then from THEME_preprocess_node() (change THEME to match your actual theme name) in template.php call the function below to set a new variable that can be used in other templates:

/**
 * Add a new shortlink variable
 */
function THEME_preprocess_node_add_shortlink(&$vars) {
  $node = $vars['node'];

  $options = array(
    'language' => '',
    'alias' => TRUE,
    'absolute' => TRUE,
  );
  $shortlink = url($node->nid, $options);

  /* add also the info to the header when viewing a node page */
  if (arg(0) == 'node') {
    drupal_set_html_head('<link rel="shortlink" href="https://ao2.it/en/news/2011/01/13/short-url-links-drupal-6-ao2it/'. $shortlink .'" />');
    drupal_set_header('Link: <' . $shortlink . '>; rel=shortlink');
  }
  
  $options = array(
    'language' => '',
    'attributes' => array(
      'title' => $node->title,
      'rel' => 'shortlink nofollow',
    ),
  );

  $vars['shortlink'] = l($shortlink, $shortlink, $options);
}

The code above adds support for the shortlink relation specification, and takes care to add the nofollow relation to the short duplicate link for the content, which shouldn't hurt.

The new variable for the actual link can be used wherever the short URL link is wanted to show up:

print '<div class="shortlink">'.t('Short link'). ': ' . $shortlink . '</div>';

A problem with this approach could be the multiple redirects performed before actually serving the content:

/[nid]  ->  /node/[nid]  ->  content at Pathauto generated alias

I haven't checked if caching mechanisms at the web server can help with that, anyone?

An alternative, more complete solution, would have been to use the Short URL and Shorten Drupal modules; overkill for my use case.

I still have to find a way to add support for short URLs to AddToAny without patching it.

Oct 26 2010
Oct 26

I'm happy to announce that my extensible BBCode module XBBCode is now ported to Drupal 7. I last worked on (and wrote about) this module in April while writing a new parsing engine for it.

Although the filter was working well, there were many small flaws in the surrounding management code that dealt with storing and editing custom tags and settings, interfaced with Drupal's filter API and handled the installation.

A recent core update to the filter module changed the numerical format IDs to textual names, which makes things lots more easier for automatically created text formats (namespaces, etc), so the required compatibility fix left the installation process far cleaner than it had been before.

Also, the actual tag packages, xbbcode_basic, xbbcode_list and xbbcode_table have been ported, so the module can actually be used beyond custom tags.

Unfortunately, while the Syntax Highlighter tie-in has been ported to Drupal 7, it is useless until the Highlighter module itself is ported. I cleaned it up for Drupal's CVS a few months ago, but it is still only compatible with Drupal 6.

Note that while the module can be installed and used without causing immediate major errors on a clean Drupal 7 install, it has not been exhaustively tested, and small bugs can still remain. It should not be used in production yet.

Oct 29 2009
Oct 29

So you're a small startup company, ready to go live with your product, which you intend to distribute under an Open Source License. Congratulations, you made a wise decision! Your developers have been hacking away frantically, getting the code in good shape for the initial launch. Now it's time to look into what else needs to be built and setup, so you're ready to welcome the first members of your new community and to ensure they are coming back!

Keep the following saying in mind, which especially holds true in the Open Source world: "You never get a second chance to make a first impression!". While the most important thing is of course to have a compelling and useful product, this blog post is an attempt to highlight some other aspects about community building and providing the adequate infrastructure. This insight is based on my own experiences and my observations from talking with many people involved in OSS startups and projects.

First of all, realize that your community is diverse. They have different expectations, skills and needs. Pamper your early adopters. They are the multipliers that help you to spread the word, if they are convinced and excited about what you provide. Put some faith and trust in them and listen to their input. In the beginning, you might want to focus on your developer community and the tech-savvy early adopters, but this of course depends on the type of product you provide and on what your target audience looks like. In any case, make sure that you provide the necessary infrastructure to cater the respective needs of these different user bases.

Also remember that you can not overcommunicate with your community. Blog heavily, write documentation/FAQs/HOWTOs, build up Wiki content and structure, create screencasts. Don't rely on the community to create any of this in the early stages. But be prepared to embrace and support any activities, if they arise. Solicit input, provide opportunities and guidelines for participation!

While it's tempting to do: don't establish too many communication channels in the beginning. Keep it simple and reduce the different venues of communication to an absolute minimum at this point. A new forum with many different topics but no comments looks like an art gallery with a lot of rooms, but they are either empty or there's just a single picture hanging at the wall. Nobody wants to visit that, he'd feel lost in the void. At the early stage of a project, I think it's essential to keep the discussions in as few places as possible. This helps you to identify your key community contributors (the "regulars" aka the "alpha geeks") and to build up personal relationships with them (and among themselves).

Consider establishing a forum with only a few topics, start with one or two mailing lists. Also make sure that these are actively being followed (e.g. by yourself or your developers) and that questions are being answered! I personally prefer mailing lists over forums, but I'm probably not representative. Ideally, it would be nice if there would be a unified communication hub that supports both posting via the web site like a forum, or via email or NNTP (similar to Google Groups). This keeps the discussions on one central place (which eases searching for specific keywords/topics) and still allows users to choose their preferred means of communication. Unfortunately, I haven't really found any suitable platform for this approach yet — suggestions are welcome! And once your community grows and people start complaining about too many or off-topic discussions, you can think about further separation of the discussion topics.

Allow your users to submit and comment on issues and feature requests by providing a public bug/feature tracking system. Use this system for your release tracking and planning as well, to give your users a better insight into what they can expect from upcoming versions. Also, make it very clear to your users where bug reports and feature requests should be sent to! Should one use the Forums or the bug tracker for that? A mailing list or forum makes it easier for users to participate in these discussions, but makes it more difficult to keep track of them and to ensure they are being followed up on. For the sake of simplicity, I would actually suggest to remove any separate forums about these topics. Instead, educate your community early about which is the right tool and venue to use for such requests. This saves time and resources on your side and helps to build up an initial core of community members that can then educate others about "the ropes". Otherwise you end up with the burden of keeping track of every feature request or bug report that was posted somewhere, ensuring it has been added to the bug tracker...

If your community infrastructure consists of separate building blocks to provide the required functionality (e.g. forums, bug tracking, wiki), consider setting up a single-sign on (SSO) technology and establish a unified look and feel between these applications. Your users should not be required to log in with more than one username and password, and every application should share the same login and profile data. However, only require a login, if absolutely necessary! Many users feel alienated by having to enter their personal data, even if they only want to lurk around or browse through existing discussions or documentation. As an additional benefit, it helps you to quickly identify your "community stars" in the various sections of your site: Who reports the most bugs? Who is the most helpful person on our Forums? This information could also be published on your community site, giving users the opportunity to build up reputation and karma. Community infrastructure sites like Drupal or Joomla provide an excellent foundation to get you started, while offering enough room for improvement and additional functionality at a later point.

Lower the entrance barrier and make it as easy as possible for people to get started with your application. Don't just throw a source archive at them, hoping that someone else will take care of doing the binary builds. Put some effort into building and providing binary, ready-to-install packages for the most popular platforms that your target audience is likely to use. The three most important platforms to cover are Microsoft Windows, Mac OS X and Linux. While users of the latter usually have the required tools and experience in building stuff from source, Windows and Mac users are usually "spoiled" and don't want to be bothered with having to install a full-fledged development environment before they could eventually evaluate your application.

When it comes to Linux distributions, you should look into building distribution-specific packages. This heavily depends on the requirements for external libraries that your application is using, which might differ on the various flavours of Linux. Depending on the purpose of your application, you may either focus on the more desktop/developer-centric distributions like Mandriva, openSUSE, Ubuntu, or on the distributions commonly used in server environments, e.g. Debian, CentOS, Fedora, RHEL, SLES (Yes, I am aware that most distributions are multi-purpose and serve both tasks equally well, and it's of course possible to use each of them to get the job done — it's a matter of taste and preference). If possible, make use of existing build infrastructure like Fedora's Koji build system, Launchpad's Personal Package Archives (PPA) or the openSUSE Build Service (which even allows you to build RPMs and DEBs for non-SUSE distributions) to automate the building and provisioning of distribution-specific packages for a wide range of Linux flavours. If your application is slightly complicated to install or set up, consider providing a live demo server that people can access via the Internet to give it a try. Alternatively, create ready-to-run images for virtual machines like Parallels, VirtualBox or VMWare. Everything that makes it easier to access, install and test your software should be explored.

In closing, make community involvement a part of your company culture and make sure that you preserve enough time to take care of it. Community engagement has so many different aspects, you don't necessarily have to be a developer or a very technical person to get involved. I'm aware that doing community work can be seen as a distraction and definitely takes away time from other tasks. But community involvement should become a habit and a well-accepted part of everyone's job — this is much easier to establish while you're still small and growing.

Jun 20 2009
Jun 20

My last post on a Drupal-based database of proxy servers provided a link to the proxydb module I wrote. However, realistically the only potential user of the module, right now, is myself, since it is a very buggy unfinished version. So I set up a site ready for production use (after much further debugging).

The site runs on Drupal 7, which is extremely sleek. I still get almost 9M memory peak for bootstrap, unfortunately - but premature performance-tweaking is the root of all evil.

The newest code of the module can be downloaded at proxydb-7.x-0.2-r355.tar.gz.

The production site is at barred.ermarian.net. (I had the barred subdomain left over, and it seemed close enough in meaning to be repurposed for this).

---

Note that I am a newcomer to all this: Austin Heap already has a very good proxy list running (the development of which I'm following, and which I might contribute to as well). However, just as the proxies themselves, these resources are all at risk of filtering, so you could say "the more the merrier".

Dec 26 2008
Dec 26

This is just a notice of a bugfix update to XBBCode, my light-weight stack-based parser for customizable and extensible BBCode in Drupal.

I have been developing the module for two years now and using it on this blog for almost as long (see earlier posts), so I've had lots of time to work out most of the bugs.

It can be downloaded here:

It is also still available on SVN:

Dec 08 2008
Dec 08

Mullenweg: Scale WordPress to 20,000,000 Views per Day for $100 p/month

Posted by Aaron D. Campbell | Monday, December 8th, 2008
, , , , ,

An interesting article appeared on the front page of the drupal.org website, detailing the migration of the popular “crooks and liars” blog from WordPress to Drupal. According to the developers, when the site was averaging around the “200,000 hits per day mark, we started experiencing a lot of down time from server overloads. We were utilizing the famous wp-cache plugin for WordPress, as well as hosting the database on a single master and two slaves, using the HyperDB class for WordPress to handle the replication.” After experiencing a high degree of server downtime from the massive number of comments on the site, “crooks and liars” began to consider porting the site to Drupal for performance issues.

According to the site development team, benchmark tests showed that a Drupal 5.x installation was able to serve more than 8 times the number of pages per second vs. a standard WordPress 2.3 set up:

“I setup default installations of WordPress 2.3 and Drupal 5. I only enabled the core caching mechanisms in both setups and populated them with the exact same data and display options. Both systems also used the default themes and features. After running a series of tests through JMeter, I quickly confirmed my beliefs and even exceeded them as I saw Drupal was able to handle about eight times the requests per second as WordPress, both on the front page and the same single post view with 157 comments.”

An interesting overview of the migration, and custom modules used in the development of the “crooks and liars” site can be found online here:

http://drupal.org/node/341231

What is more interesting, is after the post was published, WordPress / Automattic founder Matt Mullenweg weighed in personally on the issue, by commenting on the post and listing ways to configure WordPress to scale to 20 million hits per day – at a cost of only $100 per month. He writes:

“Always sorry to see someone leave WordPress, but you ended up pretty much the other best place I could think of. Features are a great reason to switch, but scaling doesn’t need to be. We host some of the largest poltical blogs like all of CNN’s which regularly get thousands of comments per day and we do about a billion pageviews a month on WordPress.com, so here are some tips for future people who may come across this post (some which may be useful to the Drupal community as well):

1. Every release of WP gets faster, so upgrading can get you sometimes significant boosts depending on your bottleneck.
2. Use the memcached object cache backend.
3. If memcached is set up, use Batcache instead of wp-cache.
4. If you get a lot of comments, consider using InnoDB as your storage engine instead of MyISAM inside of MySQL.
5. Double-check that your webserver is set up properly for static requests, this is the cause of 90%+ of the problems we see.

With the above and a single $100/month server from LT you can get around 20,000,000 pageviews a day. With shared Batcache and HyperDB (which you already used, nice) it’s a lot easier to scale out both the web and database tier independently as needed. We haven’t found the upper limit of this strategy yet.”

Included are links to Quantcast’s statistics proving 1 billion page hits per month on wordpress.com (globally): http://www.quantcast.com/p-18-mFEk4J448M/traffic

Link to the Memcache Plugin: http://plugins.trac.wordpress.org/browser/memcached/trunk

Link to the Batcache Plugin: http://wordpress.org/extend/plugins/batcache/

According to the site:

“Development testing showed a 40x reduction in page generation times: pages generated in 200ms were served from the cache in 5ms. Traffic simulations with Siege demonstrate that WordPress can handle up to twenty times more traffic with Batcache installed.”

Based on Quantcast statistics, Drupal.org ranks 13,298 overall while WordPress.org ranks #11. Global tracking statistics are not available for drupal.org on the site.

http://www.quantcast.com/drupal.org

Pings/Trackbacks

Oct 30 2008
Oct 30

According to my drupal.org user profile, it's been three years now since I joined the Drupal community, and in that time I've been involved with building a large Drupal-based collaborative platform at work, helped friends create Drupal sites and developed a number of small Drupal sites in a freelance capacity, as well as contributing a number of modules back to the community, but had not managed to get around to actually migrating my own website to Drupal. Until today, that is.

Today marks the soft launch of my new Drupal website, an initial version that has most of the content from the old site but still needs some user interface improvements and the like.

The blog is up and running, now also with RSS feeds for each of the main categories and also each of the tags. The photos are all in there, though I'm missing any gallery functionality at the moment, so they're going to be a little difficult to find for now.

Watch out for more over the coming weeks and months as I find time to make improvements, expand what's here and tie in with other services on the web like Twitter, Flickr and delicious.

Sep 10 2008
Sep 10

MySQL UniversityTomorrow (Thursday, 11th of September) at 9:00 PST/16:00 UTC/17:00 GMT/18:00 CET, there will be an new free MySQL University Session. MySQL University started as an internal training program for MySQL engineers, to share and spread knowledge about their areas of expertise and has been available to the public for quite some time now. It covers a wide range of technical topics around the MySQL Server and usually takes place once per week.

For the first time, the presentation will not be performed by (former) MySQL employees/developers, but by two of our "Sun Classic" colleagues: Jyri Virkki (OpenSolaris Web Stack community lead) and Murthy Chintalapati (Sr Engineering Manager, Web Stack development) will talk about the OpenSolaris Web Stack:

OpenSolaris Web Stack is an OpenSolaris project and community building an integrated stack of popular open source web tier infrastructure technologies such as Apache HTTP server, MySQL, memcached, PHP and Ruby On Rails optimized for Solaris platform. This session introduces OpenSolaris Web Stack, its status and future development including addition of newer technologies such as lighttpd, Varnish etc., as well as the ease of use features for developers and deployers. We will also be discussing an experimental web stack IPS package repository and it could be leveraged to build and make available popular end user applications such as Drupal.

MySQL University sessions are free to attend - all you need is an IRC client (to post your questions and comments) and an audio player capable of playing back an OGG audio stream, so you can listen to what is being said. See the Instructions for Attendees on the MySQL University pages for more information on how to log in and attend. The audio stream will be recorded and published on the MySQL University pages for later consumption, in case you can't make it or want to listen to a previous session.

Aug 10 2008
jh
Aug 10
Illustration of interchangeable presentation Interchangeable presentation

You know the drill

If you didn't ignore the web for the past few years, you've probably heard it a million times by now: separate your content and presentation, use CSS for presentation, and say yes to semantic markup. There is no doubt that it's the right thing to do, given the sheer amount of benefits.

In a nutshell: it makes your life easier by lumping those pieces together which belong next to each other. It's somewhat akin to the proximity usability rule. It also keeps the noise down; if you want to change parts of the presentation there are no content bits in the way and vice versa. There is also a lot less overhead since those style sheets can be cached on the client side. In extreme cases it can save as much as 200kb of utterly pointless bloat. Additionally, proper CSS usage also paves the way for the ultimate killer feature: interchangeable presentation.

Reality check

In theory you can rebrand or even redesign a whole website just by replacing the style sheets. Unfortunately it's not always that easy in practice. With static sites you usually have to hack'n'slay through the markup with regex search & replace until you get usable markup. If you ever go down that route use Tidy first.

If a CMS was used things will usually look a lot better. The outer theme markup (rough layout, navigation, etc.) is externalized and can be easily replaced. And the inner article markup (i.e. the actual content) resides elsewhere. So, if the new layout requires some (outer) markup changes this won't be much of an issue. You write a new theme and that's it.

With overly verbose markup as seen on CSS Zen Garden you can also get a high degree of flexibility. Having lots of unused ids and classes in your markup isn't really feasible though. I don't know of any real website which went down that route. Zen Garden is just a content free demonstration page after all.

Legacy by default

With a CMS everything should be fine, shouldn't it? Well, almost. Typically individual articles or blog posts will be HTML or XHTML fragments. And that's already the first big issue: the content is hard coded to fit a specific digital distribution flavor from the very beginning.

For example if the site started with HTML 4.01, switching to XHTML 1.1 won't be easy. Of course you might be inclined to ask for a reason for doing so. However, the more important question is why you can't.

State-of-the-art markup is always a moving target. HTML 5 is just around the corner and so is the next incarnation of XHTML. Things will look different in 5 years. Even more so in 10 or even 20 years. The internet isn't a gimmicky piece of tech anymore. It's fairly save to say that the internet will virtually stay forever. The human span of life isn't all that long after all.

Disposable by default

Of course there is lots of disposable content. This blog isn't an exception with its technical focus. As long as none of the programming languages I talk about is the next COBOL, the article won't be of any interest for anyone a couple of years down the road.

But there is also truly timeless content. And sometimes it might be desirable to distribute it in completely different flavors. For example a "book" created with Drupal (it's a core module, which allows you to create a structured set of pages) might be also distributed as PDF, as DocBook, or even as a real physical book. As you can imagine (X)HTML fragments aren't really suited for that task.

Somewhat semantic (X)HTML

Pure semantics would address all those issues. But even semantic (X)HTML is a tad less semantic than it should be. That shouldn't be much of a surprise though. It's meant to be used to represent the structure of a typical web document in a generalized fashion and - to be fair - it does this job pretty well. But it can't be used to create the most accurate structure for any kind of document as illustrated by the following diagram:

intersection diagram Figure 1: Depressing intersection diagram

To illustrate this aspect a bit more take a look at the required markup for the eye-catcher in the upper right:

<dl style="float:right">
  <dt>
    <a href="http://kaioa.com/b/0808/content_presentation.svgz">
      <img src="http://kaioa.com/b/0808/content_presentation.png" width="192" height="192" alt="Illustration of interchangeable presentation" title="click for SVG"/>
    </a>
  </dt>
  <dd>Interchangeable presentation</dd>
</dl>

The inline style attribute at the very beginning isn't great, but it should always float to the right, because floating to the left would look really ugly. Why would I want to do that? I saw no benefit in creating some class just for that. Well, lets ignore that for now. The point is that we have some definition list there, which contains one term (which contains an anchor element, which contains an image) and one definition.

Basically I just used some random elements which happen to provide the required structure. It isn't a definition or a list. It's some image with some tag line.

There is even more nonsense. Why are the paths absolute? Well, to work around some issues of some aggregators. You also have to use absolute paths if you use anchor links. The width and height attributes are also pointless. They don't belong to the content. They are merely there to aid the rendering of browsers. It's a lot more pleasant to the eyes if no reflowing happens and it's also a tad quicker to render (since there is no need to recalculate the layout once the image headers are loaded).

While that stuff is somewhat mandatory it isn't related to the content itself. It shouldn't be part of handwritten markup. Even more so if it can be generated automatically.

Even headings are a pain

Even something as simple as headings are somewhat tiresome with (X)HTML. Ideally they should start with H1 and go all the way down to H6 if necessary. Steps shouldn't be omitted and there should be only one H1 heading (one root - everything else is silly). What could go possibly wrong there?

With a CMS you're only writing an (X)HTML fragment and the first headline is from a separate title field. If you look at that page this generated title will be either a first level heading or a second level heading. And over at those overview pages, which only display excerpts it's usually a second level heading, but it might be even a third level heading if those articles are grouped by author for example.

The XHTML 2.0 working draft addresses this issue with the introduction of H elements whose semantic weight is proportional to the section nesting level. The following example was directly taken from the working draft:

<body>
<h>This is a top level heading</h>
<p>....</p>
<section>
    <p>....</p>
    <h>This is a second-level heading</h>
    <p>....</p>
    <h>This is another second-level heading</h>
    <p>....</p>
</section>
<section>
    <p>....</p>
    <h>This is another second-level heading</h>
    <p>....</p>
    <section>
        <h>This is a third-level heading</h>
        <p>....</p>
    </section>
</section>
</body>

It would be great if that issue would be solved now, but you can't actually use XHTML 2.0 yet.

General (X)HTML markup issues

If a rich text editor is used you may end up with some extraneous markup. If you're unlucky it might be even invalid. Needless to say that humans also do mistakes. And validators sometimes let really horrible mistakes with devastating consequences slip through.

As you can imagine this will lead to massive problems as soon as you try to transform it into a different format. Even if only 1% of the pages require manual interaction, it will make you cry if there are thousands. With that point of view in mind you can probably understand why I'm a big fan of absolute strictness. In a perfect world all browsers would only display an error message if the markup or styling is invalid.

XSLT and alternative markup languages to the rescue

By using a different markup language than your current publishing target markup language you can avoid overly rigid coupling. It also ensures that your content will always be in a transformable state. If the need arises you can output any kind of new markup. E.g. you will be able to switch to HTML6.2 or XHTML3.2 in 2020 without having to touch the markup of any of your 5000 articles.

Just imagine the uproar. Not even 24 hours after IE12 became self-aware and deleted all copies of itself your retro geek page will be the first bigger website to make the switch to the very latest standards (which were already supported by all other browsers). That would be so cool.

All joking (and wishful thinking) aside, there are a lot of markup languages to choose from. The most popular ones are probably Markdown, Textile, Texy (I refuse to put the exclamation point there), and Wikitext. However, those options might be a tad too limited for your taste or they simply may not meet your requirements.

On the XML side there is an infinite amount of options since you can create your own schema there. You can use as many elements and attributes as you need. And if you ever need some new kind of structure you can just create it. You can also use standardized schemata such as DocBook.

In most content management systems the XSL transformation and XML validation introduces an additional step in the pre-templating phase. In Drupal this can be done during the filtering step. Unsurprisingly the XML Content module does just that. If server-sided caching is enabled this additional transformation step will be virtually free. With that extra step in place the pipeline looks like this:

XSLT diagram Figure 2: XSLT can transform any kind of XML into any other kind of XML

With my own schema in place and some automation the markup for the eye-catcher (compare with the markup above) could look like this:

<eyecatcher src="http://kaioa.com/node/84/content_presentation.svgz" alt="Illustration of interchangeable presentation" desc="Interchangeable presentation"/>

The generated markup, however, could look the same. But it could also look completely different if it's desired. Since the original graphic is an SVG, a clickable thumbnail can be created in any size. Right now I use a width of 180 pixels and a maximum height of 180 pixels and the drop shadows ramp these values up to 192 pixels.

Apparently it would be really handy if the rendering, additional effects, and post processing were fully automated. This would allow me for example to change the dimensions later on or to use different effects (e.g. a magnifying glass icon overlay). It would also allow me to get rid of that bitmap altogether as soon as it isn't required anymore.

Closing words

In retrospect it's somewhat funny that it took me that long to realize that virtually everyone (me included) creates non-portable content and that it's actually rather easy to circumvent this issue. A few years ago when I first read about XSLT in the context of web applications I didn't see the point. One could just use XTHML all the way, right?

Well, now I can see that the path from the most accurate representation to some representation (e.g. XHTML 1.1) is a one way route. It won't be possible to utilize more meaningful structures once they are introduced if you were restricted to a specific set (which didn't cover your requirements completely) at the point of creation.

Feb 16 2008
Feb 16

Drupal has seen a number of books written about it in the last 18 months or so, the sign of a healthy platform. The latest of these books is Drupal 5 Themes, by Ric Shreves, published by Packt Publishing. The book covers the process of making a Drupal site look how you want it, known as theming. Theming Drupal is a multi-step process, due to the flexibility and customizability of Drupal itself. This book serves as a fine introduction to the process and methods of Drupal theming; however, it is not without its rough patches.

The book is lean, right around 250 pages, and even that is including a completely superfluous appendix, and some other sections that could have easily be kept out without too much loss of content. Starting out with the obligatory and well done introduction section, we quickly move right in to customizing the themes that come with Drupal, and how to install a contributed theme. While we could have done without the hand-holding as we look at the basic parts of theme configuration, it is a fine introduction to the amount of configuration possible without delving into theming itself. The author takes us through a fictional client’s requirements for a site, and shows how those design elements would be implemented in Drupal, without writing any code.

A brief discussion of the various theming engines that are available is suitably brief enough, as Drupal theming today revolves around PHPTemplate, and, soon, it’s time to start delving into Drupal’s custom templating language. PHPTemplate was written specially for Drupal, and it deftly balances flexibility and power, as it remains easy for developers and designers alike to work with it. Some discussion of block visibility serves as a good example of those places where Drupal allows developers to get down into code without modifying Drupal itself. While it’s a fine discussion, the author fails to mention the , which has lots of other examples for a budding themer to snack on.

We also get a very good idea in Chapter 3 on how just a little bit of well-placed code here and there can make a big difference in Drupal themes. Working with the , the author takes us through the theme’s files to show where the key points of control lie.

Chapter four is a list of core css files and theme functions. It is also completely superfluous. It could be replaced with a couple of grep or find commands, or a tutorial for finding theme functions on , which would have longer-lasting benefits for a Drupal themer. A representative of the content in this chapter is:

theme_admin_block_content
Formats the content of an administrative block.

theme_admin_page
Formats an administrative page for viewing.

It’s 18 pages of your life that you’ll never get back.

That’s followed by what is easily the standout section of the book. Chapter 5 is all about overriding css and theme functions, and is a terrific discussion on what’s available to the themer, and how to get at it. It’s chock full of great information and examples, showing how the default “Garland” theme takes the PHPTemplate engine and overrides parts of it itself, and shows how you and your theme could do the very same thing. It’s an empowering overview of taking control of the theming process.

Chapter six focuses on designing a theme around the Zen, creating a subtheme. Just a bit of code and some new CSS, and we have a completely different looking site. The big problem here is that the theme we come up with is pretty ugly. Nevertheless, it does show how big results can come from a little tweaking.

Now that we’ve taken a pre-written theme and designed a subtheme around it, Chapter seven is all about creating a theme from scratch. Starting with the bare basics and moving into discussions of what variables PHPTemplate makes available, and even how to make your own variables available, this is a grand discussion that shows the full power of the templating engine and how much power is actually there. Again, though, what we’re creating won’t be winning any design awards, but I suppose that’s beside the point.

The last chapter is about theming the various forms in Drupal, and this again is excellent. There are several steps in the process that can be used to change how forms work, and the author expertly goes through each way, showing where each one is most appropriate, and showing fine examples in the process. If the whole book were this well written, it’d be a bible of Drupal theming. Unfortunately, it’s not. The chapter also devolves into unneeded screenshots of most of the Drupal forms, and what functions are used to build them.

The book ends with a totally unnecessary appendix of every CSS selector included in Drupal core. Once again, a nice grep command could easily have been used to build this content.

  • Chapters 5, 7, and 8 are quite useful, even to an old three-digit-uid user like me. Overriding is a big part of Drupal, not just theming, and it’s taken on in great style and fine discussion. PHPTemplate is one of the shining stars in Drupal, everyone who uses it owes Adrian a beverage, and the fact that you can use a few pages to talk about how to create an entire Drupal theme from scratch shows its amazing power. It would be hard to get such things wrong, and this book does not. It shows a good knowledge of PHPTemplate and what it’s very good for. And, though forms aren’t something that get themed a lot, we have in this book a fantastic reference on how, where, and why you’d theme forms in Drupal.
  • Starting with the Zen theme is practically Best Practice when it comes to theming Drupal, and the author here goes into some depth with this theme, showing code and where some good CSS overriding can make a big difference. It’s also a wise move to show that the Zen theme is not the last word in theming, and it is possible and even advantageous to create your own theme from scratch.
  • As noted above, there are several places where I thought the content was superfluous. Chapter four could be reduced to a few grep statements, or a discussion of api.drupal.org. While chapter 8 starts off with some fine discussion, it ends up with a bunch of screenshots of forms in Drupal, and where they are in the code. While there might be some good information in here, the screenshots, and number of them, seem over done. Appendix A could also be reduced to a few grep statements, as it simply lists the CSS selectors in all of the css files within Drupal core.
  • No book on Drupal is complete without a discussion of Drupal contributions. This book does spend a good deal of quality time with the Zen theme, that is the first and last time contributions are mentioned to any depth. Looming very large in their absence are CCK and Views, two modules that practically every Drupal site will need in its lifespan, and two modules which are complex to theme, and yet very themeable. Any book on Drupal theming that does not even mention these two modules, let alone not show any examples of theming them, is woefully incomplete. Yes, they’re not core, but neither is the book called “Drupal 5 Core Theming.”
  • The graphics in the book are of poor quality. The places where lightly-colored text are used (such as the Garland theme) are very hard to read, and the file listing screenshots are badly pixellated. Every screenshot looks like the poor-quality placeholder images were never swapped out for high quality images.
  • The themes we end up designing in the book are, well, ugly. I realize that’s not the point, but I think that, if the book’s going to discuss theming and design a theme or two, I would want the themes to be of good quality.
  • Just a week or so after I received this book on Drupal 5 Theming, Drupal 6.0 was released. Most of the discussion in the book still applies, as the theming exercises themselves focus on the Zen theme and CSS, but large portions of the book, particularly Chapters 5 and 7, which might be the best part of the book, will need to be rewritten, as Drupal 6 has dramatically increased the number of .tpl.php files, and in the process has made overriding these pieces much easier. Themes themselves have changed in Drupal 6, requiring corresponding .info files, requiring registration via hook_theme(), and in the removal of _phptemplate_callback(). Readers coming to Drupal 6 with this book would be well-advised to keep the appropriate open in a tab while reading and experimenting.

There are great parts of this book, and there are horrible parts of this book. While I think that there’s more wrong than right, the parts that are right are very right and worth the price of the book. Someone new to Drupal theming, someone who’s just not sure what to do, someone who thinks they can just change the color in Garland or mess with Bluemarine and call it a day, these people will get a great deal of worthwhile information out of the 250 or so pages. The book is geared towards people with a good background in HTML and CSS, and it does mention that a ‘basic’ knowledge of PHP is helpful. It’s to PHPTemplate’s credit that most themers won’t need to touch too much code, and what needs done is typically simple logic and not difficult. Those places where you can get deep in to PHP and Drupal coding are properly glossed over. Drupal 5 Themes leaves room for an advanced theming book, and that is the appropriate time to have such discussion.

All in all, I do recommend this book to people who are looking at Drupal and want to know how they can change the look of it to fit their design. It does show the power of the Drupal theming layer, and how much Drupal’s output can be changed through, not just CSS, but some simple and well-placed PHP code as well. Drupal 5 Themes is a worthwhile introduction to Drupal theming, and deserves its place among the current crop of Drupal books.

Dec 04 2007
jh
Dec 04

Planet is some RSS aggregator written in Python we're using over at Planet Inkscape. But maybe I should start at the beginning.

Lots of weird requests which resulted in 404s showed up in the log. Things like:

XX at http:/kaioa.com
XX at
XX
XXathttp:/kaioa.com

Where "XX" stands for the node id. E.g. "36" for this blog post.

After investigating it for a bit I found the shocking reason behind this. Well, not really that shocking... it's more on the silly side, really. ;)

My RSS 2.0 feeds look like this:

[...]
<link>http://kaioa.com/node/36</link>
[...]
<guid isPermaLink="false">36 at http://kaioa.com</guid>
[...]

The RSS 2.0 feeds from Planet look like this, however:

[...]
<guid>http://kaioa.com/36 at http://kaioa.com</guid>
<link>http://kaioa.com/node/36</link>
[...]

As you can see the isPermaLink attribute is missing. If it's missing it defaults to true, which in turn causes other readers/aggregators to treat that guid as URL. Ironically Planet does interpret that attribute for itself, but strips it from its own feeds.

isPermaLink="false" is used by Drupal and and WordPress. However, it only negatively affects Drupal's feeds, because WordPress' feeds happen to use guids which are identical to link. But that isn't a given and may change at some point in the future (well, it's unlikely).

Either way it's totally Planet's fault. I tried to track down the issue, but Planet's source is pretty hard to follow. Additionally "rss20.xml.tmpl" and the template stuff in general lack support for the isPermaLink attribute, which means that fixing it won't be that easy.

If you're wondering why I'm blogging about this instead of posting it on Planet's bug tracker... well, they don't have one. D'oh. I already contacted one of the authors, but so far I got no reply.

Aug 24 2007
jh
Aug 24

Changes/Improvements:

  • offset shift fixed

The offset for the next turn wasn't adjusted. Now it uses arrays and replaces the the whole lot at the very end. Well, weird code leads to silly mistakes. ;)

Download: SHJS Filter (6kb - same location)
Project page on Google Code: http://code.google.com/p/shjsfilter/

If there are any problems post em on the issue tracker.

Aug 08 2007
jh
Aug 08

Changes/Improvements:

  • tab size per format
  • spelling+grammar
  • short tip updated
  • long tip updated

New Features:

  • generic code formatting via <code>...</code> and <shjs:generic>...</shjs>
  • default language (per format) via <shjs>...</shjs>
  • custom highlighting themes can be now put into the themes directory - this makes accidential deletion harder ;)

Download: SHJS Filter (6kb - same location)

Aug 03 2007
jh
Aug 03

Changes:

  • one brainless statement removed
  • auto close added

The implication of the latter is that code, which was cut off (teaser view) will be properly highlighted and escaped. The cutoff is indicated via "[...]".

Well, the potential cutoff, that is. If you forget the closing </shjs> tag, you'll also get that "[...]" thingy. Maybe it will be possible to do that in a more sensible fashion with Drupal 6.x.

Download: SHJS Filter (4kb - same location)

Aug 01 2007
jh
Aug 01

A rough overview of the visible improvements:
SHJS Filter Improvements
Ye, I guess I'm pretty happy with it now. :)

Java Example

import java.io.*;
import java.util.zip.CRC32;

public class CRC{
    public static void main(String[]args) throws Exception{
        CRC32 c=new CRC32();
        DataInputStream in=new DataInputStream(new FileInputStream(args[0]));
        byte []buffer=new byte[4096];
        int read;
        while((read=in.read(buffer))>0){
            c.update(buffer,0,read);
        }
        System.out.println("CRC: "+Long.toHexString(c.getValue()).toUpperCase());
    }
}

Python Example

#!/usr/bin/env python
import sys

if len(sys.argv) != 2:
    print 'usage: python dia.py <odd length>'
    sys.exit(1)

try:
    h=int(sys.argv[1])
except ValueError:
    print ('"%s" isn\'t a number')%(sys.argv[1])
    sys.exit(1)

if h%2!=1:
    print ('%i isn\'t odd')%(h)
    sys.exit(1)

for i in range(1,h/2+2)+range(1,h/2+1)[::-1]:
    print(('%s%s')%(' '*(h/2-i+1),'*'*(i*2-1)))

XML Example

<?xml version="1.0" encoding="utf-8"?>
<!-- some jnlp file --> 
<jnlp 
  spec="1.0+" 
  codebase="http://domain.tld/" 
  href="http://kaioa.com/node/4/whatever"> 
  <information> 
    <title>Some Application</title> 
    <vendor>Some Vendor</vendor> 
    <homepage href="http://www.domain.tld/"/> 
    <description>It does this and that.</description> 
  </information> 
  <resources> 
    <j2se version="1.4+"/> 
    <jar href="http://kaioa.com/node/4/example.jar"/>
  </resources>
  <application-desc main-class="tld.domain.Example">
  </application-desc>
</jnlp>

Download
SHJS Filter (4kb)
Note: It has to run before the line break converter.

Dec 01 2005
Dec 01

You might have already noticed, but I'll re-iterate nevertheless: the Drupal project has released Drupal 4.6.4 and 4.5.6 which fix three security vulnerabilities. Everyone running a Drupal site is advised to upgrade, as always.

Multiple people were mighty busy yesterday preparing, finalizing and testing the patches and advisories. I was one of them, although I was more like lurking around trying to look busy ;-) Anyways, I have sent the respective advisories (DRUPAL-SA-2005-007, DRUPAL-SA-2005-008, DRUPAL-SA-2005-009) to the "usual suspects" today: Bugtraq, Full Disclosure, and the php-sec mailing list. The advisories have already been picked up by Secunia and a bunch of other security sites...

Btw: I finally received news that my domain was transferred to my new web hoster today, which led to a short downtime. Everything should be fine now. If you notice any problems, please drop me a note.

Oct 06 2005
Oct 06

The annual report from usability expert Jakob Nielsen:

  1. Legibility Problems
  2. Non-Standard Links
  3. Flash
  4. Content That's Not Written for the Web
  5. Bad Search
  6. Browser Incompatibility
  7. Cumbersome Forms
  8. No Contact Information or Other Company Info
  9. Frozen Layouts with Fixed Page Widths
  10. Inadequate Photo Enlargement

I agree with all of them, especially number 3 (Flash). I'm starting to like most of the AJAX sites popping up around me, but I have yet to find a Flash site which I really like.

About Drupal Sun

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

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

See the blog post at Evolving Web

Evolving Web