Jul 06 2016
Jul 06

Google Summer of Code (GSoC) is into the next phase of coding after the mid-Term evaluations which got over by June 27th. This also reminds students to speed up the coding activities to complete the projects within the schedules provided in the proposal.

I am porting Search Configuration module to Drupal 8 as part of this year’s summer of code. GSoC is definitely turning out to be a venue for the young students from universities around the world to work on real-world projects under the experience of well-known developers, learning new technologies, making use of version control systems, with regular meetings and finally building up a software which is going to help a large section of the society.

I blog regularly, sharing my project progress. If you would like to have a glimpse of my past activities on this port, please visit this link.

Drupal 8 has introduced the concept of Html twigs in place of the PHP templates. So, the PHP template files have to be now ported to the Html environment. The .tpl.php template file is replaced by the .html.twig file for the module templates.  Templates are simply text files which can give outputs in Html, Latex, CSV or XML formats.

To print some data, we usually take the service of echo statements in PHP.  The print statements are replaced by {{ }} in Html twigs.

<?php echo t(‘Let’s start the process.’); ?>

is replaced by:

{{ ‘Le’s start the process’|t }}

The variable names have to be converted to simple names. For instance,

$page[‘title’]

becomes

{{  title }}

The PHP logics have to be replaced by {% %} syntax. This is applicable to all the logical statements.

<?php if ($page[‘title]): ?>

…..

<?php endif; ?>

is transformed as:

{% if form %}

……

{% endif %}

Also, the variables are replaced by simple names.

<?php if ($logo): ?>

is transformed as:

{% if logo %}

These were some of the basic transformations to get started into created the HTML twigs.The use of the Html twigs has made the templates look very simple and easily understandable. It is really easy to get the templates converted to the Html twigs. This is always one of the crucial requirements of porting modules from Drupal 7 to Drupal 8.

Stay tuned for further updates on this port process.

Jun 30 2016
Jun 30

I feel really excited to have cleared the mid-Term requirement for my project in Google Summer of Code (GSoC). The results of the mid-Term evaluations were announced June 28, 00:30 IST. This was the evaluation for the first phase of GSoC. In this evaluation process, set up by GSoC organisers, students and mentors have to share their feedback about the current progress of the project. Mentors need to give a pass/ fail grade. Students can continue coding once they clear the evaluations successfully.

I have been working on Porting Search Configuration module to Drupal 8. Please go through my previous posts if you would like to have a look into the past activities in this port process.

Last week I worked on testing some of the units of this module using the Php unit tests framework. Testing is an important process when it comes to any software development process. It plays a crucial role for any software. It helps us to understand the improve our software to the required level by making use of various test cases. We input various values and check whether the tests are passed according to the requirement. If any condition fails to our expectations, we need to make the required changes to suit the application needs.

Php unit tests are generally used to test some units of an application. To check whether the functions implemented gives the expected output, behaviour of the functions in various test cases, giving different types of arguments as inputs to check the errors or flaws for improving the application.

We need to install the Php unit for this process. You could follow this documentation for this process. Furthermore, they give a detailed analysis of the Php Unit Tests.

Once the installation is completed, we can start writing the unit tests for the functionalities we have implemented. The tests are generally stored in the tests/src/Unit directory of the module. The name of the unit test file will be of the format xyzTest.php. All tests are suffixed by ‘Test’. ‘xyz’ can be replaced by the functionality you are going to test.

The following is a simple test to check the sum of two numbers: sumTest.php

<?php
class SampleTest extends PHPUnit_Framework_TestCase
{
  public function testSum()
  {
    $this->assertEquals(2+2, 4);
  }
}
?>

As mentioned in this above code snippet, we need to create a class, with class name suffixed by ‘Test’ which is an extension of PHPUnit_Framework_TestCase. Now, we need to write the tests inside as member functions. The functions starting with the name test are executed. Here we are checking the sum of the two numbers. This is a very simple demonstration.

The tests are run by using the command PHPUnit. i.e,

$ phpunit tests/src/Unit/sumTest.php

The output generated on running the above test is:

PHPUnit 5.4.6 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 252 ms, Memory: 13.25MB

OK (1 test, 1 assertion)

Stay tuned for future updates on this module port.

Jun 21 2016
Jun 21

Google Summer of Code (GSoC), has entered into the mid-Term evaluation stage. This is a 1 week period from 21- 27 June, were students and mentors present the progress of their projects. Based on the reports submitted, students are made pass/ fail.

I have been working on porting Search Configuration to Drupal 8 in the past few weeks. If you would like to have a quick glimpse of my past activities on this port process, please go through these posts.

last week, I could learn some Drupal concepts which were really helpful for my project. In the previous versions of Drupal, the role permissions were stored in a role_permissions table in the Database. But now, in Drupal 8, the role permissions are directly stored in the role configuration entity.

So, as described above, in D7 and its preceding versions, role permissions were stored in a role_permissions database which had the role Id and the corresponding permissions. The permissions distributed to a role was retrieved in D7 using:

$permissions = role->getPermissions();

But, in D8, this is done by the

$permissions = role->getPermissions();

Another instance is that, to grant certain permissions to roles.

In D7 it was controlled by,

user_role_grant_permissions($rid, array(‘ access content’));

The role configuration entity remodels this functionality in D8 to:

$role->grantPermission(‘ access content’);

In connection with the term permissions, the most important aspect in Drupal is a hook: hook_permissions(). This hook, obviously as you might have guessed, distributes the permissions to various users; decides whether a particular user should be allowed to access a page or a content, granting and restricting the access.

This hook has been replaced in Drupal 8 by a module.permissions.yml file. This file contains the permissions and its specifications. We can write a driver function in a php file to add the dynamic permissions. This can be achieved by making a driver class in the php file and adding the behaviour of the permission we need in the member functions of the class. We also have to link this PHP file with our yml file to keep it active. This is done by adding a callback function in the yml file which references this php file.

To display special characters in a plain text string for display as HTML format, Drupal earlier versions used the function check_plain.  This had the general syntax:

check_plain($text); // where $text was the string to be processed.

This function has got deprecated in Drupal 8. This has been replaced by the \Drupal\Compoent\Utility\Html::escape($text).

Jun 14 2016
Jun 14

Google Summer of Code (GSoC’ 16) is entering into the mid-Term evaluation stage. I have been working on the porting search configuration module for Drupal for the past three weeks.

Search configuration module helps to configure the search functionality in Drupal. This is really an important feature when it comes to a content management system like Drupal. I am almost mid-way in the port process as indicated in the timeline of Google Summer of Code.

It is really a great feeling to learn the Drupal concepts this summer. I would like to take this opportunity to share with you some key aspects I had to deal with in the past week.

Once a module is installed and later on if some changes are amended, we need not rewrite the Drupal code. Instead, Drupal gives up the option to make use of a hook, function hook_update_N which helps us to write the update needed and the database schema gets updated accordingly. Currently, since my module is just going into its first release after this port process, I need not write an update function. I just have to make this update in connection with the earlier code. The same hook works for both Drupal 7 and 8.

Another feature is the hook_node_insert, this hook is invoked to insert a new node into the database. So, here we are writing into the database a drupal record. In Drupal 7, this insertion was done by the drupal_write_record(). But,in D8, it has been replaced by the merge query and the entity API. The merge queries support the insert and update options to the database.

In connection with the nodes, another hook function is the hook_node_update. This functionality updates the node contents which has been inserted into the Drupal record (database). This takes in an argument, obviously,  the node has to be passed, for which the updating is intended.

The hook_uninstall gets invoked as the name suggests, in the uninstall process of the modules. The hook removes the variable used by the module so as to free the memory. It also modifies the existing tables if required. The D7 version of Drupal used the  variable_del for removing the variables.

For instance,

variable_del($nameOfVariable);

// Drupal 7 code

This has been replaced by the delete() of the configuration API.

i.e,

\Drupal::service('config.factory')->getEditable('search_config.settings')->delete();

search_config.settings is the default configuration file.

I will post the updates on this port process regularly. Please wait for the future posts.

Jun 07 2016
Jun 07

Google summer of code (GSoC) seems to be a venue for students to get in touch with new technologies and be a part of many interesting open source organisations. Thanks to google for co- ordinating this initiative.

The last week was really a productive one for me in all aspects. I could manage time better to focus more towards my project. The climate here seems to have improved a lot. It’s now rainy here which has reduced the hot and humid climate to a large extent. My geographical location, Kerala, the southern part of India usually faces a fair climate.

If you are searching for a flashback of my previous GSoC’ 16 ventures, please have a look at these posts.

So, as you were expecting, now let’s talk about my activities in the second week of GSoC. The second week commenced with a bit more elaborative planning of the tasks to be carried out in the coming days. My main intention for the week was to discover more Drupal hooks and adapt it to my project code.

Wondering, what are hooks?

Hooks, in the simple definition, are PHP functions which have an inbuilt meaning given by Drupal to interact with modules. They also give us the freedom to extend the functionalities. The api.drupal.org gives wonderful explanations about the various hooks in action and their modifications that have come in the different Drupal versions.

Topics I have covered:

I would like to take this opportunity to share with you some of the concepts I could grasp from the previous week of GSoC.

  • hook_install
    • This performs the setup tasks when the module is installed.
  • hook_schema
    • This hooks the database schema for the module. This is invoked once the module is enabled. This resides in the .install file of the module.
  • hook_theme
    • This is for enhancing the module’s theme implementations.
  • hook_permission
    •  This hook defines the user permissions of the module; granting and restricting permission to the roles.
  • Configuration API
    • The Drupal variables are replaced by the configuration API.  You need to define the properties and default values in the new format.

Hoping to learn more Drupal concepts in the days ahead. I will be posting the updates regularly. Stay tuned for more Drupal concepts.

May 31 2016
May 31

The Google Summer of Code 2016 (GSoC’ 16) coding period kicked off from last week, May 23rd. Each selected student had a one-month long community bonding period with their respective organisation. I had written my community bonding experience with Drupal in my previous blog post.

I am working on porting search configuration module to Drupal 8 under the mentorship of Naveen Valecha, Neetu Morwani and Karthik Kumar. I learnt the basics of Drupal, implementing Drupal forms and other fundamentals required for my project in the initial phase.

I was really thrilled to get into the coding rounds of GSoc’ 16. I was successful in making a good progress in the initial week. I could start the coding phase effectively by porting some important features. I had regular discussions with my mentors throughout the previous week. They were really helpful in guiding me in the proper way by reviewing the code and sharing their valuable feedback.

Here I would like to share with you some of the changes which I have come across in Drupal 8 from its preceding versions which I have worked out in the previous week for my project.

  • In D8, the .info files are replace by .info.yml files. The .info files contained the basic information regarding the respective module. The .info.yml generally contains the following attributes of the module:
    • name
    • description
    • core
  • The Drupal 7 variables have been converted to the Drupal 8 state system. The previous versions had variable_get and variable_set functions to store variables and their default configurations. They were used to store and retrieve data. They are presently removed in D8.
  • Moreover, we need to store the default configuration of the variables. While developing a new module, it comes in module/config/schema/ will contain the default variable configurations.
    • The default configuration is the default property of the system with its type and the corresponding default value linked to it.
  • Another transformation that has taken place is that the global $user has been substituted by the current_user service.
    • So, the previous definition,
      • global $user;
    • is replaced by
      • $user = \Drupal::currentUser();

These were some of the important concepts that I had to deal with in the previous week for the porting process. It was really a wonderful learning time for me. Hope to enjoy the coming weeks to learning new concepts and implementing it for my porting.

Hope all the selected students had a wonderful start to their coding period. All the best for the coming weeks.

Sep 19 2012
Sep 19

To get things started, in this lesson we'll create a new module, and use hook_views_api() to let Views know we want to use its API.

Sep 12 2012
Sep 12

This lesson gives an overall summary of the entire Coding for Views series. We explain the basic knowledge that each chapter will cover and what you can expect to learn from watching this entire series.

Feb 28 2012
Feb 28

In the first part of this introduction to EntityFieldQuery, we looked at how simple it was to quickly construct a query with only a few lines of code. In this second part of the series, we’ll take a look at some more practical ways to put EntityFieldQuery to use.

Our Example Module

For the purposes of this series, I’ve created a simple module, EntityFieldQuery Example, that demonstrates some uses of EntityFieldQuery. It’s hosted on Github; you can grab the source code if you’d like to see the code in more detail or if you’d like to install it yourself.

Some quick notes about what the module contains: in addition to the callbacks and block code described below, the module installs three node types, efq_article, efq_page, and efq_photo. These are very simple node types: efq_article and efq_page each contain a body field for text, while efq_photo contains a single image field. In addition, all three content types contain a US States field, which we’ll be using to construct our example queries.

I recommend using Devel content generate to quickly generate content for your examples, although you can of course create it manually if you like. For the purposes of my examples, I created 200 nodes using the following command: drush genc --types="efq_page,efq_article,efq_photo" 200.

When you uninstall this module, it will clean up after itself, removing all created content. I’m thinking of teaching this technique to my cats.

Extending EntityFieldQuery

One of the first things to to remember about EntityFieldQuery is that it is a proper PHP class. The means that you can extend its behavior to suit your needs. For example, when using EntityFieldQuery, you might find that you are often trying to find nodes. If you are trying to find content that you will be listing in public pages and blocks, you probably want it to all be published. Additionally, lists of content tend to be published with the most recent content first.

Let’s create our own version of EntityFieldQuery, NodeEntityFieldQuery, with these properties already set:

class

NodeEntityFieldQuery

extends

EntityFieldQuery

{
// define some defaults for the class
public function

__construct

() {
// now we don’t need to define these over and over anymore
$this
->entityCondition(‘entity_type’, ‘node’)
->propertyCondition(‘status’, 1)
->propertyOrderBy(‘created’, ‘DESC’);
// define a pager
$this->pager();
}

  public function excludeNode($nid) {
    // code snip; we’ll come back to this.
  }

}

This is fairly straightforward. Each time we create a new NodeEntityFieldQuery, a number of things will be already pre-set for us, namely that we’re searching for published nodes and we want them returned in a reverse chronological order. Additionally, we instantiated a pager, since we’ll most likely want one. If we find ourselves using NodeEntityFieldQuery over and over again, we will save ourselves some time by having all these values pre-set.

Also note that we can extend the class with our own methods. We’ll come back to that method later.

A Simple Listing Page

Let’s start by creating a simple listing page. This is going to list all nodes of our three example content types in reverse chronological order, as well as providing a pager. First, we’ll need to define a menu item and callback for our page:

/**
 * Implements hook_menu().
 */

function efq_example_menu() {
  $items[‘efq’] = array(
    ‘title’ => ‘EntityFieldQuery example: recently published content’,
    ‘page callback’ => ‘efq_example_listing’,
    ‘access arguments’ => array(‘access content’),
  );
  return $items;
}

Now we can start our callback to provide the listing for us. We’re going to keep this very basic to start with: we’re just listing all the nodes of our three types, that are published, in reverse chronological order.

function

efq_example_listing

() {
// instantiate the query using our extended query object
$query = new

NodeEntityFieldQuery

();
// let our query know which node types we want
$query
->entityCondition(‘bundle’, array(‘efq_article’, ‘efq_page’, ‘efq_photo’)); // execute the query
$result = $query->execute();
$output = array();
// return the nodes as teasers
if (!empty($result[‘node’])) {
$output[‘nodes’] =

node_view_multiple

(

node_load_multiple

(array_keys($result[‘node’])), ‘teaser’);
$output[‘pager’][‘#markup’] =

theme

(‘pager’, $query->pager);
}
else {
$output[‘status’][‘#markup’] =

t

(‘No results were returned.’);
}
return $output;
}

That’s all we need to do to provide us with a listing page with a pager. It’s not exactly Drupal For Dummies, but it really isn’t terribly difficult, and in particular it’s very few lines of code to get that result.

Adding Arguments

Of course, it would be more interesting if we could filter this list some. Let’s add some arguments to our callback. Our current URL is at efq. Let’s set it up so that we can use URLs in the form efq/%state/%node_type as well. We’ll have to set up our callback such that both state and node_type are optional, and to be aware of the arguments if they’re present.

Our new callback isn’t substantially different:

function efq_example_listing($state = NULL, $node_types = array(‘efq_article’, ‘efq_page’, ‘efq_photo’)) {
  // instantiate the query using our extended query object
  $query = new NodeEntityFieldQuery();
  // set up some basic parameters
  $query
    ->entityCondition(‘bundle’, $node_types);
  // if $state is defined, add that fieldCondition to the query
  if (!empty($state)) {
    $query->fieldCondition(‘field_us_state’, ‘value’, $state);
  }
  // execute the query
  $result = $query->execute();
  $output = array();
  // return the nodes as teasers
  if (!empty($result[‘node’])) {
    $output[‘nodes’] = node_view_multiple(node_load_multiple(array_keys($result[‘node’])), ‘teaser’);
    $output[‘pager’][‘#markup’] = theme(‘pager’, $query->pager);
  }
  else {
    $output[‘status’][‘#markup’] = t(‘No results were returned.’);
  }
  return $output;
}

In our new callback, we look for a value for $state, and if it is present, we add a fieldCondition to our query. Additionally, we set up $node_types with a default value of an array of all of our content types, and look for a changed value. Note that the condition code is flexible:

  $query
    ->entityCondition(‘bundle’, $node_types);

$node_types can be an array, as is the default, or it can be a string, which is how it will come in from the URL. That’s fine; the condition will adjust to the type of argument you pass.

Building a Content-sensitive Block

So now we have a listing of our content, and it can accept arguments. It would be nice also if, on our individual nodes, we could find other content for our given state. Let’s construct a block that shows the five most recent items from that node’s state. Additionally, we should probably provide some links back to the listing pages, again based on state.

Constructing the framework for the block itself is basic Drupal:

/<

strong

>
*

Implements hook_block_info

().
*/
function

efq_example_block_info

() {
$blocks[‘content_by_state’] = array(
‘info’ =>

t

(‘Other Content In This State’),
‘cache’ =>

DRUPAL_NO_CACHE


);
return $blocks;
} /</

strong

>
*

Implements hook_block_view

().
*/
function

efq_example_block_view

($delta = ) {
$block = array();
switch ($delta) {
case ‘content_by_state’:
$block =

efq_example_content_by_state

();
break;
}
return $block;
}

Now of course we need to populate that block. Here’s the function that will do that for us (don’t worry, we’re going to break this down):

/**
 * Produces content for a block, based on the state of the host node.
 */

function

efq_example_content_by_state

() {
$block = array();
// if we don’t have a node to draw from, in our current setup, we won’t have a state and can’t continue
if ($node =

menu_get_object

()) {
// get the state value
$field_name = "field_us_state";
if (!empty($node->$field_name)) {
$items =

field_get_items

(‘node’, $node, $field_name);
$state = $items[0][‘value’];
}
// only continue if we have a state value
if ($state) {
// instantiate the query
$query = new

NodeEntityFieldQuery

;
// limit the query to our established node types
$node_types = array(‘efq_article’, ‘efq_page’, ‘efq_photo’);
$query->entityCondition(‘bundle’, $node_types);
// add the state value
$query->fieldCondition($field_name, ‘value’, $state);
// add a small range
$query->range(0, 5);
// remove the current node from the query
$query->excludeNode();

      // execute the query
      $result = $query->execute();
      $output = array();
      $block[‘subject’] = t(‘Other Content for @state’, array(‘@state’ => $state));

if (!empty($result[‘node’])) {
// return the nodes as teasers
$nodes =

node_view_multiple

(

node_load_multiple

(array_keys($result[‘node’])), ‘teaser’);
$block[‘content’][‘nodes’] = $nodes;
// let’s include links to the content listing for convenience
$node_types = array(
‘efq_article’ => ‘Articles’,
‘efq_page’ => ‘Pages’,
‘efq_photo’ => ‘Photos’,
);
$links = array(

l

(

t

(‘All Content for @state’, array(‘@state’ => $state)), "efq/$state"));
foreach ($node_types as $node_type => $node_type_name) {
$links[] = array(

l

(

t

(‘All @node_type_name for @state’, array(‘@node_type_name’ => $node_type_name, ‘@state’ => $state)), "efq/$state/${node_type}"));
}
$item_list = array(
‘#items’ => $links,
‘#type’ => ‘ul’,
‘#theme’ => ‘item_list’,
);
$block[‘content’][‘links’] = $item_list;
}
else {
$block[‘content’][‘status’][‘#markup’] =

t

(‘No results.’);
}
}
}
return $block;
}

Let’s break this down a bit.

// if we don’t have a node to draw from, in our current setup, we won’t have a state and can’t continue
if ($node =

menu_get_object

()) {
// get the state value
$field_name = "field_us_state";
if (!empty($node->$field_name)) {
$items =

field_get_items

(‘node’, $node, $field_name);
$state = $items[0][‘value’];
}
// only continue if we have a state value
if ($state) { // ….

The beginning of this is quite straightforward. If we don’t have a node, there’s no point in continuing. Also, we need the node to have a state value, or else there isn’t any point in looking for state content for our block.

      // instantiate the query
      $query = new NodeEntityFieldQuery;
      // limit the query to our established node types
      $node_types = array(‘efq_article’, ‘efq_page’, ‘efq_photo’);
      $query->entityCondition(‘bundle’, $node_types);
      // add the state value
      $query->fieldCondition($field_name, ‘value’, $state);
      // add a small range
      $query->range(0, 5);

This looks very similar to our page listing query. We define a new query via NodeEntityFieldQuery, let the query know what kinds of nodes we’re looking for, and give it the value for the state field. Additionally, we’re limiting the number of results returned to the 5 most recent, since this is a block.

One thing you might want to consider is that you may not want the results listed in the block to include the node you’re currently on. This makes sense: if you see the same page you’re already reading listed in a sidebar, it can feel sloppy, or else like you’re having your time wasted.

Luckily, it is not hard to do this. Remember when we extended EntityFieldQuery to create NodeEntityFieldQuery? Remember this bit?

  public function excludeNode($nid) {
    // code snip; we’ll come back to this.
  }

We can add our own methods to our new class. Let’s do that now.

  /**
   * If we’re on a node, and if the entity_type is node, exclude the local node from the query
   */

  public function excludeNode($nid) {
    if (!$nid) {
      $object = menu_get_object();
      $nid = $object->nid;
    }
    if (!empty($nid) && $this->entityConditions[‘entity_type’][‘value’] === ‘node’) {
      $this->propertyCondition(‘nid’, $nid, ‘<>’);
    }
    return $this;
  }

This is a fairly simple method. If you pass in a node ID it will use it; otherwise it will attempt to use the node ID on the page you’re currently on. If you’re using EntityFieldQuery to search for nodes, and there is a node ID, this method will tell your query to exclude the current node. The key item is this line:

      $this->propertyCondition(‘nid’, $nid, ‘<>’);

Note the “<>” operator. Normally if you’re looking to match a value or set of values, you can leave the operator out of your propertyConditions, fieldConditions, etc., because they’re set to “=” or “IN” by default, depending on whether you have a single value or multiple that you’re matching against. If you want to use a different operator for your condition, you have to enter it explicitly.

Now that we have our method, we just need to add a quick method call to our block callback function, and we’re set:

      // remove the current node from the query
      $query->excludeNode();

Most of the rest of the callback looks quite similar to our page listing callback:

// execute the query
$result = $query->execute();
$output = array();
$block[‘subject’] =

t

(‘Other Content for @state’, array(‘@state’ => $state)); if (!empty($result[‘node’])) {
// return the nodes as teasers
$nodes =

node_view_multiple

(

node_load_multiple

(array_keys($result[‘node’])), ‘teaser’);
$block[‘content’][‘nodes’] = $nodes;

We execute our query. We set the block title, since that is going to be the same whether we had content or not. If there are results from our query, we load them and then slot them into the block content.

We do generate a set of links to allow the user to get back to the page listings easily:

        // let’s include links to the content listing for convenience
        $node_types = array(
          ‘efq_article’ => ‘Articles’,
          ‘efq_page’ => ‘Pages’,
          ‘efq_photo’ => ‘Photos’,
        );
        $links = array(l(t(‘All Content for @state’, array(‘@state’ => $state)), "efq/$state"));
        foreach ($node_types as $node_type => $node_type_name) {
          $links[] = array(l(t(‘All @node_type_name for @state’, array(‘@node_type_name’ => $node_type_name, ‘@state’ => $state)), "efq/$state/${node_type}"));
        }
        $item_list = array(
          ‘#items’ => $links,
          ‘#type’ => ‘ul’,
          ‘#theme’ => ‘item_list’,
        );
        $block[‘content’][‘links’] = $item_list;

And finally, because we’re relatively thorough, we write a simple message to the block if not results are returned:

      else {
        $block[‘content’][‘status’][‘#markup’] = t(‘No results.’);
      }

This Looks Kind Of Familiar…

If you’re an old Drupal hand, you might be wondering why I wouldn’t just build this in Views. Indeed, this might look a bit masochistic if you’re used to Views. However, there are a few things to consider:

  1. EntityFieldQuery is core. If you are interested in keeping your installation as lean as possible, this is something to consider. Views is a contributed module, and so it automatically adds size and overhead to your Drupal install.

  2. EntityFieldQuery is small. The amount of code required to generate an equivalent listing in Views, compared to what we built above, is much, much greater. To be fair, your Views code will almost certainly be exported and managed via Features, and so in effect you’re not writing the Views code anyway. But, that highlights a different point: writing code this way keeps you in code, rather than requiring you to work in a point-and-click interface. For the average developer, this is a very good thing.

  3. EntityFieldQuery is code. You might want to have certain things exposed to change and not others. For example, on a recent project, we were building a site for a client that had site builders on staff. These people were comfortable working with Views, and we wanted to make sure that they had the ability to create and modify views. However, we wanted to also make sure that there was some core functionality that was left untouched. If we had built that core functionality with Views, it would have been exposed to change. By building that core functionality with EntityFieldQuery, we kept it strictly in code, which protected it.

Summing Up and Coming Up

There is a lot of code in this post. If you are interested in understanding this better, I recommend installing the sample module, creating some test content, and playing with the code. I think much of the code is a lot easier to understand in context.

In our next post, we’ll look at some more advanced EntityFieldQuery techniques. We’ll also look at scenarios when EntityFieldQuery is a good candidate for the job, and some scenarios when EntityFieldQuery is not what you want to use.

Nov 22 2011
Nov 22

If you are working with code based deployments instead of using CMS to push out changes, you might have already implemented something similar.

There are 2 approches to implementing this:

1) Define and save date formats

function mymodule_custom_date() {
  $date_format_monthday = 'm-d';
 
  $format = array();
  $format['format'] = $date_format_monthday;
  $format['type'] = 'custom';
  $format['locked'] = 0;
  $format['is_new'] = 1;
  date_format_save($format);  
 
  $format_type = array();
  $format_type['title'] = 'month_day';
  $format_type['type'] = 'month_day';
  $format_type['locked'] = 1;
  $format_type['is_new'] = 1;
  date_format_type_save($format_type);
 
  variable_set('date_format_'. $format_type['type'], $date_format_monthday);  
}

2) Implement date format and date type hooks

function example_date_formats() {
  $date_format_monthday = 'm-d';
 
  $format = array();
  $format['format'] = $date_format_monthday;
  $format['type'] = 'custom';
  $format['locked'] = 1;
  $format['is_new'] = 1;
 
  return $format;
}
function example_date_format_types() {
 
  $format_type = array();
  $format_type['title'] = 'month_day';
  $format_type['type'] = 'month_day';
  $format_type['locked'] = 1;
  $format_type['is_new'] = 1;
 
  return format_type;
}

Related blog posts: 


Bookmark and Share
Sep 13 2011
Sep 13

All rights, including copyright, in the content of this website is owned by Janak Singh. In accessing this website, you agree that you may only use the blog content for your own personal non-commercial use.
You are not permitted to copy, adapt or change in any way the portfolio content, photos, images of these web pages for any commercial or non-commercial purpose whatsoever without prior written permission.

Jan 11 2011
Jan 11

Recently I started to notice I very high number of LRU Nuked Objects on my websites, which was essentially wipping the entire Varnish Cache. I run Varnish with 4GB File cache and site ocntent is mostly served by external "Poor Man CDN". So, in theory my site content should not be anything near 4GB, however, Varnish Cache was running out of memory and "Nuking" cached objects.

Sizing your cache

Here is what Varnish Cache man pages have to say:

Watch the n_lru_nuked counter with varnishstat or some other tool. If you have a lot of LRU activity then your cache is evicting objects due to space constraints and you should consider increasing the size of the cache.

Either I can increase the Varnish Cache size or be a little smarter in handling the resource.

I am using the Drupal's Compress Page feature along with Aggrigate JS and CSS features. However, Drupal will only compress the HTML output, which can result to quite large addgiated CSS and JS files. If Varnish is caching the uncompressed output, this will result in considerably more memory usage.

A much better and effective solution is to let Apache's mod_deflate handle the compression instead of Drupal. Drupal compression is oftenten a prefered choice as Drupal would compress>cache once compared to Apache compressing the files on every request. However, if you are using Varnish, which will handle the cache element, Apache would only have to do the hard work once. Also, the added advantage is that you have a granular control over which files are getting compressed.

So, did that make a difference?

LRU Nuked Objects vs Expired Items in Varnish Cache

Varnish Cache Memory Usage

Number of objects in Varnish Cache

Varnich Cache Hit Rate

Bandwidth Usage

Conclusion:

  • Varnish Cache now uses less memory
  • Handles more cached objects
  • No nuking cached items
  • Higher cache hit rate
  • Site is using less bandwidth to serve the same content.

Simplez ;)

Related blog posts: 


Bookmark and Share
Jan 10 2011
Jan 10

Following the release of Drush 4 and picking up on a couple of suggestions made by Moshe Weitzman and Nick Thompson on my previous Drush 3 Automated backup post, it was time for a revised post.

Drush 4 makes it really REALLY easy to backup all your sites, no more bash scripting etc.

Install / Update to Drush 4

Follow Drush installation guide

drushrc.php Setup

In the Drupal root directory add drushrc.php with the following setup:

/*
 * Customize this associative array with your own tables. This is the list of
 * tables whose *data* is skipped by the 'sql-dump' and 'sql-sync' commands when
 * a structure-tables-key is provided. You may add new tables to the existing
 * array or add a new element.
 */
$options['structure-tables'] = array(
  'common' => array(
    'cache', 
    'cache_filter', 
    'cache_menu', 
    'cache_page', 
    'history', 
    'sessions', 
    'watchdog',
    ),
  );

Note:
You may need to adjust the structure-tables array to suit your requirements.

Test Drush

drush --root=/var/www/html/drupal @sites st -y

If you see the status output, proceed to the next step.

Drush Backup Command

 drush --root=/var/www/html/drupal @sites sql-dump --result-file --gzip --structure-tables-key=common

This puts the the backups in timestamp named directories within ~/.drush-backups folder.

Crontab

For the automated nightly backups, you can setup the cron jobs with the above mentioned command.

Related blog posts: 


Bookmark and Share
Dec 29 2010
Dec 29

If you are using Drupal and have allowed anonymous user to leave comments on the site, you will notice that the core comments module adds two cookies named comment_info_name and comment_info_mail. These cookies allow the comments form to be auto populated with name and email on any subsequent comments from the same user.

For majority of users this is not something of a concern. Whist this is useful, if any cookies are passed back from client side in the request, Varnish will simply request the page from the back end instead of requesting the page from the cache. This will result in many cache MISSes as the cookie is set for 365 days (!!!!)

Code responsible for setting the comment cookies in Drupal

function comment_form_validate($form, &$form_state) {
  global $user;
  if ($user->uid === 0) {
    foreach (array('name', 'homepage', 'mail') as $field) {
      // Set cookie for 365 days.
      if (isset($form_state['values'][$field])) {
        setcookie('comment_info_'. $field, $form_state['values'][$field], time() + 31536000, '/');
      }
    }
  }
  comment_validate($form_state['values']);
}

Varnish VCL tweak to allow comment cookies whilst serving from cache

We can configure Varnish to not remove the cookie when receiving a request:

sub vcl_recv {
        // remove comment_info cookies
        set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(comment_info.+)=[^;]*", "");
 
        // Remove a ";" prefix, if present.
        set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
 
        // Remove empty cookies.
        if (req.http.Cookie ~ "^\s*$") {
                        unset req.http.Cookie;
        }
}

This will allow the cookie to be set clientside but varnish will remove it when looking up the page in cache.

simplez...

Related blog posts: 


Bookmark and Share
Dec 17 2010
Dec 17

Me: Happiness is a test suite that passes.
@philiph: Do you practice test-driven development for your happiness?
Me: Why, yes, actually, I do. It gives me a tangible sense of accomplishment and minimizes my mouse-clicking. =)

Developers find their own balance of how much project structure works with them. Some people like seat-of-their-pants coding. Others want detailed architecture diagrams. Me, I’m getting the hang of agile development practices, and I really enjoy using them.

Test-driven development, for example. Yes, I could just plunge ahead and write Drupal code, and I could test it by clicking on buttons and typing into forms. I don’t particularly like using the mouse or doing repetitive actions, so I write tests for functionality and occasionally for web interaction. Tests also mean that  I can check small pieces of functionality before I have to build a web interface. And when something breaks – not if, but when – tests help me narrow down the error.

It’s so satisfying to see the tests pass, too.

There are tests that exercise functionality, and tests that set up test data just the way we like it so that we can demonstrate features or try things out using the web interface. One of my team members showed me a wonderful technique for repeatable, well-structured test data by using a spreadsheet to generate PHP code. I’ve been extending the pattern for other things.

Drupal + Simpletest is awesome. It can’t handle everything, but it makes my Drupal life better. Happy developers write happy code!

Dec 03 2010
Dec 03

Although CCK automatically does some basic validation on your fields that you add to your Drupal content types, there are some cases where you'd like to do some additional validation for your site. One use case that I ran into recently was a basic text field that was being used to house hyperlinks for one of my websites. The text field had already been in place and working perfectly for months. Rather than do something drastic like replacing the field altogether with a field provided by the "Link" module, I decided to do a hook_form_alter to add in my own custom validation function.

Basically you just create a custom module for your site called "form_alterations" or whatever you like. Here's the code for adding in this sort of functionality:

<?php
/**
* Implementation of hook_form_alter().
*/
function form_alterations_form_alter(&$form, &$form_state, $form_id) {
  switch (
$form_id) {
    case
'announcement_node_form':
     
// Simply add an additional link validate handler.
     
$first = array_shift($form['#validate']);
     
array_unshift($form['#validate'], $first, 'form_alterations_link_validate');
      break;
  }
}
/**
* FAPI #validate handler. Make sure the hyperlink they used is correctly formatted.
*/
function form_alterations_link_validate($form, &$form_state) {
  if (!empty(
$form_state['values']['field_web_address'][0]['value']) && !strstr($form_state['values']['field_web_address'][0]['value'], 'http://')) {
   
form_set_error('field_web_address', t('Please enter a full web address starting with "http://".'));
  }
}
?>

hook_form_alter allows you to use the Drupal Form API to make changes to existing forms. In the code above, I'm adding in a call to my custom function called "form_alterations_link_validate" after the basic CCK validation takes place. Then, within the function itself, I check to make sure that the user entered a value and that it contains "http://" at the beginning of what was entered. If you use the code above, please just be sure to change the line with "case 'announcement_node_form':" so that it modifies the form for your node type. My node type was called "announcement".

Oct 04 2010
Oct 04

Following on from previous post about VCL tweaks to improve hitrate; there are occasions when a website should not be served from both www.foobar.com and http://foobar.com. In some instances Google will deem the content to be duplicate copy of each other and a website can suffer from dupe content penalty.

In such cases, it is often best to redirect (301) the incoming request to either automatically add or remove www to the domain name. So, if a request comes in for http://foo.com, you can get Apache to redirect to http://www.foo.com. Something like:

Apache redirect example

<IfModule mod_rewrite.c>
   Options +FollowSymLinks
   Options +Indexes
   RewriteEngine On
   RewriteBase /
   RewriteCond %{HTTP_HOST} ^foo\.com$
   RewriteRule ^(.*)$ http://www.foo.com/$1 [R=301,L]
</IfModule>

This works great BUT it requires a redirect at the Apache level, wasting precious Apache resources. Wouldnt it be great if Varnish could do the redirect instead and lookup the requested page in cache without waking Apache? Here is how:

Concept is simple:
- varnish checks incoming request.
- match criteria for host
- throw an error
- catch the error
- redirect

Varnish 301 Redirect VCL example

sub vcl_recv {
 
  // rediercts for subdomain, add www
  if (req.http.host == "foo.com") {
    error 301;
  }
 
  // rediercts for subdomain, remove www
  if (req.http.host == "www.barbaz.com") {
    #set req.http.host = "barbaz.com";
    error 301;
  }
}
 
 
sub vcl_error {
 
  // 301 if the domain does not contain www
  if (obj.status == 301 && req.http.host == "foo.com") {
    set obj.http.Location = "http://www.foo.com" req.url;
    set obj.status = 301;
    return(deliver);
  }
 
  // 301 redirect if domain contains www
  if (obj.status == 301 && req.http.host == "www.barbaz.com") {
    set obj.http.Location = "http://barbaz.com" req.url;
    set obj.status = 301;
    return(deliver);
  }
}

Related blog posts: 


Bookmark and Share
Sep 30 2010
Sep 30

The default Varnish config for Pressflow by Four Kitchens is an excellent starting point and gets you up and running with relatively little pain and effort. Having done a fair amount of Varnish tweaking for my personal and work websites, I came across a couple of varnish tweaks that resulted in a phenominal improvement in Varnish Hit rate.

Vary User Agent

It seems Varnish by default will cache every page for every user agent if a Vary: User-Agent header was sent. Unless the website is designed to behave differently for each user agent, this is a wasted resource and will result in a very HIGH miss rate. Most sites will only include style sheets for IE and every other browser.

Unless the sites are optimised for mobile versions, we only need to configure varnish to cache for IE and every other agent, 2 caches.

Varnish can be configured to something like this to reduce number of unique cache hashes:

sub vcl_recv {
  if (req.http.user-agent ~ "MSIE") {
    set req.http.user-agent = "MSIE";
  } else {
    set req.http.user-agent = "Mozilla";
  }
}

Normalizing Vhost namespace

Since a website can be accessed from http://www.foo.com and http://foo.com Varnish will generate a separate page for each one of these urls. This combined with the above User Agent issue, number of hash combinations would be fairly high:

If you need to serve the same web site from multiple URLs, Varnish can be configured like this:

if (req.http.host ~ "^(www.)?foo.com") {
  set req.http.host = "foo.com";
}

Further Reading

Related blog posts: 


Bookmark and Share
Sep 27 2010
Sep 27

Install EPEL Repo

Memcache can be found in the EPEL repo. Install EPEL repo:

rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/x86_64/epel-release-5-4.noarch.rpm

Install Memcache

yum install memcached
pecl install memcache
yum install zlib-devel

Auto start memcache on reboot

chkconfig --level 2345 memcached on

Add PHP settings for Memcache

nano /etc/php.d/memcache.ini

extension=memcache.so
memcache.hash_strategy="consistent"

Start Memcache and Apache

/etc/ini.d/httpd restart
/etc/init.d/memcached start

Memcache Check

Check if memcache module is loaded in php

php -i | grep memcache

You should see an output similar to this:

/etc/php.d/memcache.ini,
memcache
memcache support => enabled
memcache.allow_failover => 1 => 1
memcache.chunk_size => 8192 => 8192
memcache.default_port => 11211 => 11211
memcache.default_timeout_ms => 1000 => 1000
memcache.hash_function => crc32 => crc32
memcache.hash_strategy => consistent => consistent
memcache.max_failover_attempts => 20 => 20
Registered save handlers => files user memcache

done ;)

Memcache Drupal settings

  • Download and install the Drupal memcache module
  • Update your settings.php file
    $conf = array(
       // The path to wherever memcache.inc is. The easiest is to simply point it
       // to the copy in your module's directory.
       'cache_inc' => './sites/all/modules/memcache/memcache.inc',
       // or
       // 'cache_inc' => './sites/all/modules/memcache/memcache.db.inc',
    );
     
     
    $conf = array(
      'memcache_servers' => array('localhost:11211' => 'default',
                                  'localhost:11212' => 'pages'),
      'memcache_bins' => array('cache_page' => 'pages'),
    );
    

Further reading:

Memcache API and Integration
Installing Memcached on RedHat or CentOS

Related blog posts: 


Bookmark and Share
Sep 25 2010
Sep 25

After having run janaksingh.com website on Drupal 6 with Apache+PHP+MySql, I wanted to move to Pressflow so I could harness the added advantages of Varnish.

This is not an in-depth installation guide or a discussion about Varnish or Pressflow, but quick setup commands for my own reference. Please see links at the end if you wish to explore Varnish or Pressflow setup in greater depth.

I wanted:
Varnish > APC > Apache > Pressflow setup..

Here is what I did:

Install Apache

yum install httpd
For a detailed installation tutorial, please see: http://janaksingh.com/blog/drupal-development-osx-vmware-fusion-and-cent...

Configure Apache to run on port 8080

sudo nano /etc/httpd/conf/httpd.conf
edit the httpd.conf file to allow apache to run on port 8080

Listen 8080
NameVirtualHost *:8080

Reconfigure all my VHosts to run on port 8080

nano /etc/httpd/conf.d/1_varnish.conf

Disable proxy_ajp file

mv /etc/httpd/conf.d/proxy_ajp.conf /etc/httpd/conf.d/proxy_ajp.conf_dis
<VirtualHost *:8080>
 DocumentRoot /var/www/html/varnished
 ServerName varnished.local
 
 ErrorLog /var/log/httpd/varnished_com_error_log
 LogFormat Combined
 TransferLog /var/log/httpd/varnished_com_access_log
</VirtualHost>

Install EPEL Repo

rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/x86_64/epel-release-5-4.noarch.rpm

Install Varnish

Varnish is bundled in EPEL Repo
yum install varnish

Auto start varnish

sudo /sbin/chkconfig varnish on --level 2345

Configure Varnish to run on port 80

Varnish setup and config

nano /etc/sysconfig/varnish
DAEMON_OPTS="-a :80 \
             -T localhost:6082 \
             -f /etc/varnish/varnished.vcl \
             -u varnish -g varnish \
             -s file,/var/lib/varnish/varnish_storage.bin,1G"

Setup Varnish VCL File for Pressflow

Use example VCL config file from https://wiki.fourkitchens.com/display/PF/Configure+Varnish+for+Pressflow and modify to your needs

nano /etc/varnish/varnished.vcl
backend default {
  .host = "127.0.0.1";
  .port = "8080";
  .connect_timeout = 600s;
  .first_byte_timeout = 600s;
  .between_bytes_timeout = 600s;
}
 
sub vcl_recv {
  if (req.request != "GET" &&
    req.request != "HEAD" &&
    req.request != "PUT" &&
    req.request != "POST" &&
    req.request != "TRACE" &&
    req.request != "OPTIONS" &&
    req.request != "DELETE") {
      /* Non-RFC2616 or CONNECT which is weird. */
      return (pipe);
  }
 
  if (req.request != "GET" && req.request != "HEAD") {
    /* We only deal with GET and HEAD by default */
    return (pass);
  }
 
  // Remove has_js and Google Analytics cookies.
  set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(__[a-z]+)=[^;]*", "");
 
  // To users: if you have additional cookies being set by your system (e.g.
  // from a javascript analytics file or similar) you will need to add VCL
  // at this point to strip these cookies from the req object, otherwise
  // Varnish will not cache the response. This is safe for cookies that your
  // backed (Drupal) doesn't process.
  //
  // Again, the common example is an analytics or other Javascript add-on.
  // You should do this here, before the other cookie stuff, or by adding
  // to the regular-expression above.
 
 
  // Remove a ";" prefix, if present.
  set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
  // Remove empty cookies.
  if (req.http.Cookie ~ "^\s*$") {
    unset req.http.Cookie;
  }
 
  if (req.http.Authorization || req.http.Cookie) {
    /* Not cacheable by default */
    return (pass);
  }
 
  // Skip the Varnish cache for install, update, and cron
  if (req.url ~ "install\.php|update\.php|cron\.php") {
    return (pass);
  }
 
  // Normalize the Accept-Encoding header
  // as per: http://varnish-cache.org/wiki/FAQ/Compression
  if (req.http.Accept-Encoding) {
    if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
      # No point in compressing these
      remove req.http.Accept-Encoding;
    }
    elsif (req.http.Accept-Encoding ~ "gzip") {
      set req.http.Accept-Encoding = "gzip";
    }
    else {
      # Unknown or deflate algorithm
      remove req.http.Accept-Encoding;
    }
  }
 
  // Let's have a little grace
  set req.grace = 30s;
 
  return (lookup);
}
 
 sub vcl_hash {
   if (req.http.Cookie) {
     set req.hash += req.http.Cookie;
   }
 }
 
 // Strip any cookies before an image/js/css is inserted into cache.
 sub vcl_fetch {
   if (req.url ~ "\.(png|gif|jpg|swf|css|js)$") {
     // This is for Varnish 2.0; replace obj with beresp if you're running
     // Varnish 2.1 or later.
     unset obj.http.set-cookie;
   }
 }
 
 sub vcl_error {
   // Let's deliver a friendlier error page.
   // You can customize this as you wish.
   set obj.http.Content-Type = "text/html; charset=utf-8";
   synthetic {"
   <?xml version="1.0" encoding="utf-8"?>
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
   <html>
     <head>
       <title>"} obj.status " " obj.response {"</title>
       <style type="text/css">
       #page {width: 400px; padding: 10px; margin: 20px auto; border: 1px solid black; background-color: #FFF;}
       p {margin-left:20px;}
       body {background-color: #DDD; margin: auto;}
       </style>
     </head>
     <body>
     <div id="page">
     <h1>Page Could Not Be Loaded</h1>
     <p>We're very sorry, but the page could not be loaded properly. This should be fixed very soon, and we apologize for any inconvenience.</p>
     <hr />     <h4>Debug Info:</h4>
     <pre>
 Status: "} obj.status {"
 Response: "} obj.response {"
 XID: "} req.xid {"
 </pre>
       <address><a href="http://www.varnish-cache.org/">Varnish</a></address>
       </div>
     </body>
    </html>
    "};
    deliver;
 }

Restart Apache and Varnish

/etc/init.d/httpd restart
/etc/init.d/varnish restart

Further reading:

Configure Varnish for Pressflow
Modules that break caching, and how to fix them
Using Pressflow behind a reverse proxy cache
Example Varnish VCL for a Drupal / Pressflow site
Varnish configuration to only cache for non-logged in users
Varnish best practices
Drupal guide to Managing site performance

Related blog posts: 


Bookmark and Share
Sep 05 2010
Sep 05

I was recently trying to figure out exactly how long my users' sessions were lasting on an existing Drupal site that I had built. Generally it seemed like users were remaining logged in for an extremely lengthy period of time. I did some research and finally discovered that this setting is actually configured in the default settings.php file that ships with Drupal. There's a line in the settings file that initially reads:

ini_set('session.cookie_lifetime',  2000000);

Basically this means that, by default, when a user is logged into the site, they will receive a cookie from the server that won't expire until two million seconds have passed (that's just over 23 days). I'm not sure quite why the default setting is for 23 days. Maybe for development sites this would be a good idea so that your developers don't have to log in all the time, but for most of my production sites, I actually prefer that the user only be logged in for a maximum of about 10 hours at a time. To change the setting to a more reasonable ten hours, you would use:

ini_set('session.cookie_lifetime',  36000);

Also, if you want the user to be logged out as soon as the browser is closed, you can change the setting to:

ini_set('session.cookie_lifetime',  0);

May 13 2010
May 13

I'm a huge fan of the Webform module (and building Drupal forms in general), and I just today noticed a feature I hadn't previously taken advantage of. This is the ability to programmatically add what are called "pre-built" option lists that can be used in your webforms.

Webform pre-built options list in action

Let's say that you want the user to select his/her age from a dropdown. Age possibilities range from 1-100. Without a pre-built option list, you'd have to type in 0|0, 1|1, 2|2, etc. into the "options" field. You can now do this using the webform api and a small custom module for your site.

In your custom module, you have to declare your new options list. Here's an example:

<?php
/**
* Implementation of hook_webform_select_options_info().
* See webform/webform_hooks.php for further information on this hook in the Webform API.
*/
function YOURMODULENAME_webform_select_options_info() {
 
$items = array();
 
$items['one_onehundred'] = array(
   
'title' => t('Numbers 1-100'),
   
'options callback' => 'YOURMODULENAME_options_one_onehundred'
 
);
  return
$items;
}
?>

After that, you need to write a function that spits out the options list. For this example, that looks like:

<?php
/**
* Build an options list for use by webforms.
*/
function YOURMODULENAME_options_one_onehundred() {
 
$options = array();
  for (
$x=1; $x <= 100; $x++) {
   
$options[$x] = $x;
  }
  return
$options;
}
?>

After that, just enable your custom module, and you should be able to use your new pre-built options list in your webforms. Check out the webform_hooks.php file (included in the Webform module directory) for more information.

Note: This is for Drupal 6 and Webform 3.0-beta 5.

Mar 04 2010
Mar 04

Ever wanted to implement or alter meta tags on a custom page or a view? As most of you would have already heard of the excellent nodewords 6.x-1.11 (module which allows users to define their own meta tags for nodes and much more), we will use some of the very useful API functions of this module.

You can actually use the nodewords API to alter, inject and define your own meta data on virtually any page.

Say you have a view and you want to dynamically set the meta tags for that view? easy..

Step 1: Implement the hook_nodewords_tags_alter() hook

Write your own module or add to your existing custom module a new function like this. This is an implementation of hook_nodewords_tags_alter()

 
function mymodule_nodewords_tags_alter(&$tags, $parameters) {
  $args = explode('/', $_GET['q']);
  // define your own logic to make it url specific
  if($args[0] == 'foo' and $args[1] == 'bar'){ // we have a valid url
 
    //if you are using alternative logic to dynamically 
    //load up the meta data, implement that here
 
    //finally set the tags
    $tags['description'] = "this is my custom meta description";
    $tags['keywords'] = "this is my custom meta keywords";
  }
}

Note: if you are using an older version of Nodewords module (without the API), please check http://janaksingh.com/blog/adding-meta-tags-taxonomyterm-override-view-p...

Related blog posts: 


Bookmark and Share
Jan 05 2010
Jan 05

For years I have been using phpBB for a busy community website that has over 162490 registered members. It has been a great ride, members are happy with the functionality and ease of use BUT from a development and security point of view it has been a painful experience.

As anyone who has ever installed, hacked and setup phpBB forum will tell you that due to the lack of hooks or modular nature of phpBB you have to hack core to add any additional functions. Prior to Drupal this was "acceptable loss" to me, but I have "come to my senses" and it is something that I would hate to do now.

For one of the work websites (www.mychild.co.uk) that is currently running on Drupal 5, we decided to expand the website and expand it to incorporate a forum. Fun times!! After much deliberation and discussions about pros and cons of each package, here is a "must have" that we decided upon:

Must have features

  • Forums must share the same login as Drupal site
  • MUST look and feel like a phpBB/VB forum
  • Private Messenging
  • Private Forums (moderate only - access by role)
  • Email notifications of updates/comments
  • Avatars
  • Post count (in profile and forum posts)
  • Star Ranking (based on post count)
  • Rank Badge (based on role)
  • Custom folder icons (keeping the look and feel close to phpBB)
  • New posts since last visit
  • Custom breadcrumb
  • Banner blocks
  • Ability to cross promote other drupal content types (based on term relationships)
  • Attachments in comments (with limit)
  • Quote
  • Banwords
  • Offsite links open in a new window
  • Advanced search

It quickly became apparent that we were no longer looking at a standalone Forum package for which I could bridge the login/session with Drupal. Since we wanted to bring majority the Drupal and custom functionality over to the forums Drupal Forums was the way ahead.

So, how do you go about achieving the above with Drupal? Easy. Well, sort of easy. Drupal Forums are very basic out of the can. It is basically a node with comments enabled. Without going into coding specifics at this stage, here is a brief outline on how we added some more meat to the Drupal Forums, I installed the following helper modules to mimic a "proper forum":

Modules used

  • Advanced Forum
  • Author Pane
  • BBCode
  • Comment upload
  • Extended user status notifications
  • Flat comments
  • Quote
  • Upload max files
  • Signatures for forums
  • User badges
  • User Stats
  • Word Filter
  • Smileys
  • Subscriptions
  • jQuery Update
  • External Links
  • Forum Access
  • Abuse
  • Apache Solr
  • Statistics
  • Private messages

The above modules provided around 80% of the missing functionality. The remaining bits and pieces were down to writing my own custom modules and a custom theme for Advanced Forum. If the website had been running Drupal 6, we could have pushed the boat out even further, but that is for another day.

If you have any specific questions or coding queries, please post a comment and I will try to answer.

Now, for some screen shots:

Forum Home Page

Forum Node & User Info

Forum Stats

User Profile

Forum Private Msg

Your thoughts, feedback and comments are most welcomed.

Related blog posts: 


Bookmark and Share
Dec 29 2009
Dec 29

In a site I was recently working on, I found myself hard-coding links to files stored within my Drupal site's "files" directory. If you've ever coded these links before, you know that they normally look something like:
/sites/[sitename]/files/[someotherdirectory]/image.jpg

This can be somewhat painful to add to the site, especially if you have any plans to ever change the domain name of your site. It would really be ideal if you could leave the domain name out of the equation and just write the link in the format:
/files/[someotherdirectory]/image.jpg

This is actually possible to do using the .htaccess file that appears in the root of your Drupal installation. Basically you just need to add a single line:
RewriteRule ^files/(.*)$ /sites/%{HTTP_HOST}/files/$1 [L]

After you implement this rule, you should be able to start writing simpler links to your "files" directory.

Happy coding!

Image of the rewrite rule and the alternate links. See PDF on DIWD Site.

P.S. - Giving credit where credit is due, I actually first heard about this tip from a presentation by James Van Dyke at the 2008 Do It With Drupal conference in New Orleans.

Dec 10 2009
Dec 10

At the recently Drupal Camp Prague 09, I was introduced to ApacheSolr as a replacement to the standard Drupal search or the Google CSE.

On most sites the basic Google CSE setup is sufficient, however for some of the more serious "work" websites my colleague Nick and I got experimenting with the Drupal's implementation of ApacheSolr module.

Here is a quick and rough writeup on how it was implemented on work and my personal (janaksingh.com website).

Please feel free to share your experiences and tips.

Basic Installation

1) Install Java on CentOS if you havent already

2) Install ApacheSolr drupal module

cvs -z6 -d:pserver:anonymous:[email protected]:/cvs/drupal-contrib checkout -d apachesolr -r DRUPAL-6--2 contributions/modules/apachesolr/

3) Checkout the ApacheSolr PHP Lib inside the ApacheSolr module folder

svn checkout -r22 http://solr-php-client.googlecode.com/svn/trunk/ SolrPhpClient

4) Create a temp folder in your home directory

cd ~
mkdir temp
cd temp

5) Download ApacheSolr in the temp folder
SVN Method

svn checkout http://svn.apache.org/repos/asf/lucene/solr/branches/branch-1.4 apache-solr

TAR Method
Grab the tarball from http://www.apache.org/dyn/closer.cgi/lucene/solr/

wget http://mirrors.ukfast.co.uk/sites/ftp.apache.org/lucene/solr/1.4.0/apache-solr-1.4.0.zip

tar -xzf apache-solr-1.4.0.zip
mv apache-solr-1.4.0 apache-solr

I have moved my "apache-solr" folder to "/usr/local/share/" but I am not sure if this matters

6) Rename 2 default files in apache-solr/example/solr/conf/

sudo mv solrconfig.xml solrconfig.bak
sudo mv schema.xml schema.bak

7) Copy the schema.xml and solrconfig.xml from the drupal apachesolr module folder into apache-solr/example/solr/conf/

Multisite / Multicore Setup

8) If you want multisite seach (multicore), here is what I did:

  • duplicate the example folder to "sites"
  • create a folder for each new website
  • copy the conf folder from sites folder into each one of the website folders
  • create a solr.xml file in the "sites" folder and defined a core for each of the sites.. here is what my sole.xml file looks like:
    <solr persistent="false">
      <!--
      adminPath: RequestHandler path to manage cores.
        If 'null' (or absent), cores will not be manageable via request handler
      -->
      <cores adminPath="/admin/cores">
        <core name="website1_core" instanceDir="website1.com" />
      </cores>
    </solr>
    

9) Finally, in the Drupal ApacheSolr module, update the "Solr path" to the same core name you defined for the site in the sole.xml file. If you are using the example setup as outlined in the readme file that ships with the ApacheSolr Drupal module, you need not change anything as the default path the module ships with is correct. You only need to change this if you define your own cores.

Testing

If you are using the example site, test your installation as follows:

cd apache-solr/example
sudo java -jar start.jar

If you are using the multicore setup from step 8, test as follows

cd apache-solr/sites
sudo java -jar start.jar

Check the ApacheSolr admin page by visiting http://mydomain:8983/solr/admin/ or if you are using multicore http://mydomain:8983/solr/mycore_name/admin/

Auto start ApacheSolr on server reboot

You can follow the instructions on Want to start Apache solr automatically after a reboot? but depending on how you defined the cores above you will need to alter the path eg:

SOLR_DIR="/opt/apache-solr/example"

or
SOLR_DIR="/opt/apache-solr/sites"

Dont forget to change permissions on the sole bash file you created (etc/init.d/solr) and remember to install the script using chkconfig (all outlined in the guide above)

Security

By default, ApacheSolr does not ship with any kind of port protection, you are advised to secure your server ports by using iptables or a dedicated firewall (if you have one). For CentOS iptable guidelines click here

Further Reading

Related blog posts: 


Bookmark and Share
Nov 16 2009
Nov 16

having done some more work on making Drupal Froums look nice and work more like a forum, I noticed a potential bug in the way Drupal handles pagination on comments. If you have a node with many comments and you have enabled comment pagination, you will notice:

  • After posting a comment in, you are redirected back to page 1, on a multipaged forum topic this is really annoying.
  • Permalinks to comments do not work because the comment_form_submit does not pass in the query (page number)
  • Changing the comments per page will generate a different permalink for each comment, without creating a dedicated callback and redirecting the user to the specific post (comment/permalink/comment_ID), there doesnt seem to be a fix for this

It is easy enough to redirect the user to (node/NODE_NID?page=PAGE_ID#COMMENT_ID) after a comment gets submitted. Here is what I did:

function mymodule_form_alter($form_id, &$form) {  
  if ($form_id == 'forum_node_form' && $form_id == 'comment_form'){
    $form['#redirect'] = mymodule_comment_redirect($form['nid']['#value']);
  }  
}
 
function mymodule_comment_redirect($nid){
  global $user;
  $uid = $user->uid;
 
  //get the last comment ID
  $sql = "SELECT cid FROM {comments} WHERE nid = %d AND uid = %d ORDER BY timestamp DESC LIMIT 1";
  $cid = db_result(db_query($sql, $nid, $uid));  
 
  //work out totals etc
  $sql2 = "SELECT count(cid) as total FROM {comments} WHERE nid = %d";
  $comment_total = db_result(db_query($sql2, $nid));
  $comment_default_per_page = variable_get('comment_default_per_page', 10);
  $last_page = floor($comment_total/$comment_default_per_page);
 
  //return the redirect url with fragment pager id
  return array('node/'. $nid, "page=".$last_page, "comment-$cid");
}

Related blog posts: 


Bookmark and Share
Sep 03 2006
Sep 03

In the first section of this article I'll demonstrate how to create a drag/drop portal in a few lines of JavaScript code, using the excellent Prototype and Scriptaculous JavaScript libraries. In the second section, I'll explain how to integrate this code into Drupal as a server backend for storing user settings. You may check the frontend here (tested with Firefox 1.5, IE6, and Opera 8.5), and download a reusable JavaScript Portal class and Drupal module for the backend at the bottom of this post.

Portal Frontend

Let's start with the XHTML structure of our portal, the portal will provide three columns for users to arrange their portlets, a list of available portlets to choose from, and the actual portlets. Each column has .portal-column as its class, and each portlet's class is .block (I'll refer to portlets as blocks from now on). The block list is going to be a special portal column identified by #portal-column-block-list as an id.

<div id="portal">
  <div class="portal-column" id="portal-column-0">
    <h2>Column 0</h2>
  </div>
  <div class="portal-column" id="portal-column-1">
    <h2>Column 1</h2>
  </div>
  <div class="portal-column" id="portal-column-2">
    <h2>Column 2</h2>
  </div>
  <div class="portal-column" id="portal-column-block-list" style="display: none;">
    <h2 class="block-list-handle">Block List</h2>
    <!-- Blocks go here -->
  </div>
</div>

The block list is not displayed until the user clicks on "Add content". Now let's take a look at each block's structure:

<div class="block">
  <h3 class="handle">
  <a class="block-toggle"><span>toggle</span></a>
    <!-- Title -->
  </h3>
  <div class="content">
    <!-- Content -->
  </div>
</div>

Toggle is a link to hide and show block's content.

Let's take a quick look at the relevant parts of the CSS code that styles our portal elements (I'll omit margin, padding, background and such):

#portal .portal-column {
  float: left;
  width: 30%;
}
#portal-column-block-list {
  position: absolute;
  width: 200px;
  top: 180px;
  left: 10px;
  z-index: 10;
}
#portal .block .block-toggle {
  background-image: url(block-slide.png);
  float: right;
  cursor: pointer;
}
#portal .block .block-toggle span {
  display: none;
}
#portal .block-list-handle, #portal .handle {
  cursor: move;
}

We will use a three-column fluid layout. The block list is absolutely positioned (and will become draggable as we will see shortly). The block toggle button is styled as an image of a triangle, and mouse cursor on block handles is set to move, providing visual feedback that blocks are draggable.

Now for the fun part, making our portal interactive! I'll use the Sortable component of the Script.aculo.us library. This component represents a container with a list of draggable elements inside it, which makes it ideal for our portal interface. The JavaScript code starts with iterating over all elements with .portal-column class in the #portal div, making them sortables:

var sortables = document.getElementsByClassName(
  'portal-column', 'portal'
);
sortables.each(function (sortable) {
  Sortable.create(sortable, { 
    containment: sortables,
    constraint: false,
    tag: 'div',
    only: 'block',
    dropOnEmpty: true,
    handle: 'handle',
    hoverclass: 'block-hover',
  });
});

Sortable.create makes a sortable out of the element passed to it. It takes a list of options. tag and only specify the tag and class of draggable elements inside the sortable. Explanation of other options is available at Sortable.create documentation page.

Next step is activating the toggle button on blocks. To do this, we will iterate over elements with the .block class, and assign an event listener to each toggle:

var blocks = document.getElementsByClassName(
  'block', 'portal'
);
blocks.each(
  function (block) {
    var content = Element.childrenWithClassName(
      block, 'content', true
    );
    var toggle = Element.childrenWithClassName(
      block, 'block-toggle', true
    );
    Event.observe(
      toggle, 'click', 
      function (e) { Effect.toggle(content, 'Slide'); },
      false
    );
  }
);

For each block, we retrieve the content and toggle elements inside it using Element.childrenWithClassName, then create an event listener that hides/shows content whenever toggle is clicked. We pass 'Slide' to Effect.toggle for a nice drawer effect.

The final step in creating our portal frontend is creating the "Add content" button (#portal-block-list-link) that shows the block list, and making the block list draggable:

Event.observe(
  'portal-block-list-link', 'click', 
  displayBlockList, 
  false
);

new Draggable('portal-block-list', {
    handle: 'block-list-handle'
  }
);

function displayBlockList(e) {
  Effect.toggle('portal-column-block-list');
  Event.stop(e);
}

And that's it! This is all what we need to make a portal with draggable portlets. You may see a the portal frontend with some sample portlets here. The page is extracted from Drupal and newsportal theme.

At the bottom of this post, there is a packaged version of this portal prototype. portal.js contains a reusable class for creating such portal interfaces. It has a constructor tht takes Prototype-like options object. The following options are available:

Option Default Description portal portal Id of the containing div for the portal. column portal-column Class of portal columns. block block Class of portal blocks (portlets). content content Class of block's content div. handle handle Class of block's handle. hoverclass block-hover Hover class for blocks. toggle block-toggle Class of block's toggle button. blocklist portal-column-block-list Id of block list. blocklistlink portal-block-list-link Id of block list toggle button. blocklisthandle block-list-handle Class of block list handle. saveurl (none) URL to submit column state to.

In the next section, I'll outline how to save user settings to database by doing Ajax requests. Drupal will be the backend that handles data storage, integrating the code into any well-structured CMS that features the concept of blocks should be quite similar.

Portal Backend

Saving Settings

To monitor and submit user changes to server, we will use Sortable's onUpdate event handler. This function is called whenever a sortable's state is changed (by receiving or losing a block). This callback is defined in the options object passed to Sortable.create:

Sortable.create(sortable, { 
  /* Previously mentioned options here */
  onUpdate: function (container) {
    if (container.id == 'portal-column-block-list') {
      return; // no need to save block list state.
    }
    var url = 'http://www.example.com/portal/save';
    var postBody = container.id + ':';
    var blocks = document.getElementsByClassName(
      'block', container
    );
    postBody += blocks.pluck('id').join(',');
    postBody = 'value=' + escape(postBody);

    new Ajax.Request(url, {
        method: 'post',
        postBody: postBody
      }
    );
  }
});

The function creates a query of the form: value=container:block1,block2,block3 (...) and submits it to the server. This is done by iterating over the blocks of the changed container. It's worth noting that Sortable has a serialize method with similar functionality. However, it requires special naming convention for element ids. I decided to write my own serialization function to make the code more flexible.

On the server side, Drupal will receive a POST request with the container state. Here is one way to handle it:

<?php

$query = split(':', $_POST['container']);
$container = $query[0];
$blocks = array();

if (!in_array($container, _portal_column_list())) {
  return;
}

$available_blocks = _portal_block_list('list');
foreach (split(',', $query[1]) as $block) {
  if (array_key_exists($block, $available_blocks)) {
    $blocks[] = $block;
  }
}

db_query("DELETE_FROM {portal_user_settings} WHERE user = '%s' AND container = '%s'", $uid, $container);

if ($blocks) {
  db_query("INSERT_INTO {portal_user_settings} (user, container, blocks) VALUES ('%s', '%s', '%s')", $uid, $container, join(',', $blocks));
}

The code extracts container and blocks ids into $container and $blocks, then verifies that submitted ids are actually available to portal users (to prevent malicious queries from submitting random strings into the database). _portal_column_list() and _portal_block_list('list') return arrays of available column and block ids respectively. If everything is correct, data is saved to the database.

Loading Settings

To load the settings on the client side, Drupal generates a JavaScript associative array that encompasses the settings and embeds it into the page:

<?php

$result = db_query("SELECT container, blocks FROM {portal_user_settings} WHERE user = '%s'", $uid);
$settings = array();
while ($row = db_fetch_object($result)) {
  $blocks = array();
  foreach (split(',', $row->blocks) as $block) {
    $blocks[] = "\"$block\"";
  }
  $blocks = join(', ', $blocks);
  $settings[] = "\"$row->container\": [$blocks]";
}
$settings = join(', ', $settings);

$output = '';
$output .= "\n<script type=""><!--\n";
$output .= "var settings = \{$settings};\n";
  /* More JavaScript code added here */
$output .= "--></script>\n";

return $output;

The resulting array will look something like:

var settings = { container1: [block1, block2], container2: [block3], ... };

settings variable is now available for our JavaScript code to rearrange blocks when the portal loads according to user preference:

for (var container in settings) {
  settings[container].each(function (block) {
  $(container).appendChild($(block));
});

As you see, the code is pretty self-explanatory. It iterates over settings, and appends each block to its container.

This should cover the server side section. The code demonstrated here covers saving and loading user's portal settings. Attached below is the Drupal module from which code excerpts were taken. It's designed to nicely integrate with Drupal blocks. Any block can be used as a portlet, and developers need only implement hook_block to add portlets. Admins may specify what blocks are available as portlets, and both anonymous and registered users may maintain their own portals. The code is designed to gracefully degrade if JavaScript is disabled.

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