Monday, 9 July 2012

Search API, Facet API and Display Suite

Just spent most of the day tracking down a nasty little issue involving these three modules.

Let's face it Search API with Facet API (and all the Search API support modules) are brilliant, the core Search is very difficult to customise and you usually have to end up with nasty core hacks if you want to do anything clever.

Search API on the other hand is lovely, and Facet API is just great with almost everything extendible. And, of course, you can display search results as a view, which adds that level of delightfulness.

Display Suite is also awesome (I may have already mentioned this).

But if you have all three together - displaying search results and facet blocks on a Display Suite configured page, you may run into the problem of the facet blocks refusing to appear.

The reason is really simple: there are no facets to display.

But you say (well, I screamed in my head) I'm displaying search results through a view, I'm looking at them right now and I know they have facets.

I finally, eventually, lit upon this issue in Facet API http://drupal.org/node/1392288 which explains the problem but doesn't come up with any solid solution. The issue is that if the page displays the blocks before the search query has been run they will be empty because search results are needed before the facets can be calculated.

Simple? Yes. Solvable? Not easily. There's no way of telling Display Suite what order you want the blocks and content displayed (it would be a nice touch but not worth for just this problem, or maybe a way to defer content rendering of blocks to the end). There is talk of having the facet run the query if it's not been run, but that's in the future if it ever happens.

The solution is legal but ugly.

The way to ensure the query is run first is to do it in hook_init() in my case by rendering the view and saving it. Then adding the view to the output when required. Yucky but it works.

UPDATE: Typical really, what I hadn't done is fully explored the DS Extras module which allows Views pages to be configured. This is very handy but the issue above still remains.

Wednesday, 4 July 2012

Changing view_mode mid-stream

In my current contract I have to display a hierarchy of complex taxonomy terms - each one as a page with relevant data hanging off it. There are three levels to the taxonomy: Level 1 displays one set of data and links, Level 2 is never displayed on its own (which is something I'll have to take care of) and Level 3 has a different page structure again.

Different page layouts means Display Suite (http://drupal.org/project/ds) and that's great. It works fine when you want to configure a different layout for a node - as long as it's the same for every node type. Or entity bundle.

DS works fine for taxonomy terms in just the same way as for nodes. It is one of my favourite modules.

But I needed to be able to change the view_mode on the fly: to check what level the taxonomy term is and change the view_mode based on that. Which is when I ran into trouble - and it's not DS's fault. Though I never had to do it apparently this was not a problem in D6 but in D7 there is a bug which makes it tricky to change view_mode. You can read all about it here: http://drupal.org/node/1154382

This issue gives some hints as to the solution (hook_entity_prepare_view() is not it), but there is a link to this blog here. The solution described  is for changing build mode for nodes based on the current theme (so you can change things if you're using a mobile theme).

My solution is the same except slightly more generic, instead of intercepting hook_node_...() hooks, I intercept hook_entity_...() hooks.

I'm just going to throw my code at you because I know you can work out what to do in your own situation.


/**
 * Implements hook_entity_prepare_view().
 *
 * We have to play silly games to change the view_mode, first we intercept
 * hook_entity_prepare_view() and establish what view_mode we want, and
 * save it. But there's a bug which means this has no effect on the output
 * so...
 */
function page_g2g_entity_prepare_view($entities, $entity_type, $langcode) {
  if ($entity_type!='taxonomy_term') {
    return;
  }


  foreach ($entities as $id => $entity) {
    if ($entity->vocabulary_machine_name!='gtg_tags' || $entity->view_mode!='full') {
      // wrong vocabulary or not a "full page"
      continue;
    }


    // Change the display dependent on the number of parents
    switch (count(taxonomy_get_parents_all($entity->tid))) {

      case 1:

        $entity->view_mode = 'g2g_level_1';
        break;

      case 2:

        // Hm. Need to do something else here, maybe
        // do a redirect to the parent term.
        break;

      case 3:

        $entity->view_mode = 'g2g_level_3';
        break;
    }
  }
}


/**
 * Implements hook_entity_view_alter().
 *
 * ...we intercept before the full build is enacted. You can test and see that
 * even though we changed the view_mode in the term itself, it hasn't transferred
 * to the build theme. So having verified we want to do it with this entity
 * we transfer it. And now it gets changed.
 */
function page_g2g_entity_view_alter(&$build) {
  if (isset($build['#entity_type']) && $build['#entity_type']=='taxonomy_term') {
    $build['#view_mode'] = $build['#term']->view_mode;
  }
}


Arguably you don't need the first hook, you could do it all in the second call. But it's a matter of elegance and splitting actions into their appropriate locations.

Tuesday, 29 May 2012

Then three come all at once

I have been doing some work on the field_extract module, a few minor fix-ups most of which wouldn't be noticed and added support for the entityreference field. You can find this module here: http://drupal.org/project/field_extract

Someone I worked with recently has put out my "deeplink" module which allows otherwise hidden content to be made available on a specific trackable URL. My version was Drupal 6 (that's what I was working on at the time) he's doing the D7 upgrade, you can find it http://drupal.org/project/deeplink

But deeplink needs my Controls module, and that's a baby that needs explanation. And that explanation is available on the project page http://drupal.org/project/controls there's both a D6 and D7 version both written by lil ole me.

Briefly: Controls is an API module which provides a similar function to CTools plugins (they have a lot in common), but requires virtually no setting up and is much easier to use. Now I always say that to people but I did seriously wonder whether it was true, so for the last commercial project I worked on I didn't use Controls, I went back to using CTools plugins instead. I desperately wished I hadn't.

So I'll stick by my statement - I think Controls is easier to use and in some ways more versatile than CTools plugins.


However they are also more easily abused. It's something for developers working on an end-client's website. Anyway I'll let you be the judge.


Thursday, 12 April 2012

Entities vs Nodes

For the last six months I've been working on a Drupal 6 site professionally, but developing a couple of personal Drupal 7 sites. However I haven't been going into D7 in any great detail - except for developing entities.

Now I've moved contracts and I'm developing a new Drupal 7 site to replace an older non-Drupal site with lots of additional facilities. And the Drupal implementation is entirely up to me.

Being an OOP person at heart I love the concept of entities but when working with a commercial website you have to make some serious decisions. My personal preferences have to give way to the reality of building a website that delivers the spec and can be maintained and extended by other developers in the future.

So how do you decide what should be a node and what should be an entity?

There's a line in this blog post which says:
You can now actually create data structures specific to your application domain with their own database table (or any other storage mechanism really) plus a standardised way to add fields to them. No need to turn nodes into something they are not.
Which is technically accurate but does not always provide clear guidance, so here's my step-by-step analysis method to decide whether a specific data structure should be a node or not:

1. Is it content? Is the item definitely stuff that gets turned into HTML and displayed for the user? If you were building a review site, a review would definitely be content, so that's a node.

2. Does it need to have revisions? The node module provides revisions and the associated modules make it easy and powerful. Building revisions into custom entities is hard. So if it has to have revisions then it has to be a node.

3. Is there additional "property" (as opposed to "field") information? I had a situation where I wanted to define a "proxy", and a proxy needs a web address, a port number, maybe a username and password. These are fundamental properties of a proxy. You could add these as fields for a node, of course, but it makes more sense for them to be properties of a proxy entity. So this should be considered as an entity. (Another way of looking at this is: is there a need for a new table containing information specific to this data structure? If so, think entity.)

4. Is the structure normally invisible to the user? That should be an entity.

5. Would using an entity instead of a node obscure the function? Perhaps this is tricky to answer, after all dividing functionality out to a new object should never make things more complex. But it's worth asking the question.

Any other ways of to help make the decision?

Remember: there is virtually no overhead in creating a new entity. And there are huge advantages in additional functionality that the core, Entity API, Views, Features and other entity-related modules can give you.

Thursday, 2 February 2012

Cleaning the watchdog


As a Drupal developer - do you do this?

Do you go through watchdog, find every error and fix it? You probably should - and here's why:

If there's even just a Notice, it means something's not right - checking it out might reveal something far more important.

Every error that goes to Watchdog is consuming valuable processing time. I had an instance quite recently where an error in a third party module was generating 10,000 notices per page. And the original author never noticed. That's ten thousand database writes. As you might imagine fixing it speeded things up quite dramatically.

When you can load any page on your website and have no reports added to the watchdog - you know your site is good.

Optimising JavaScript
On a similar note, I inherited a site which had a lot of JavaScript (all jQuery) which had to run on start up. It wasn't a public-facing site so all the JavaScript wasn't a problem what was a problem was that start-up time on IE7 was sloooooooooow - at least 10 seconds maybe more.

I spent a lot of time justifying this by saying "Well IE7 is slow and its JavaScript runs like a pig" which might be true but the client wasn't buying it and wanted it fixed. Fair enough.

Eventually I came to look at the code and it was possibly the most abysmally written code I have ever seen.

Imagine this code was baking 100 cookies, here's what it did.

  • Fetch the ingredients from the cupboard;
  • Get out enough for 1 cookie;
  • Put the ingredients back in the cupboard;
  • Mix the ingredients;
  • Cut the cookie;
  • Cook the cookie until done;

Repeat 100 times.

It worked, but it was stupid and used the most inefficient jQuery selectors imaginable, and even when it knew what object it wanted to act on (because it had already found it) it would make jQuery find it again, using exactly the same, useless, selector. The same coder, I later discovered, who in another script had written $('#x').parent().parent().parent().parent().parent().parent().parent().parent();

I re-coded it to make 100 cookies in a single batch. Start-up time <1sec.

This is a good site for jQuery optimisation: http://hungred.com/useful-information/jquery-optimization-tips-and-tricks/

Monday, 30 January 2012

Little debugging aid

I had been having a real problem tracking down a PHP error in Drupal which was passing an array to htmlspecialchars() in check_plain() instead of a string. I needed to use debug_backtrace() but the issue was related to Views which meant that the arguments being passed at higher levels were catastrophically huge.

It was a real problem, using krumo just resulted in out-of-memory errors. And the problem was also happening inside AJAX calls so any attempt to simply print the output was doomed.

I needed a way to get a function backtrace which was not too verbose, didn't crash the machine and would work in an AJAX call.

The solution is the following routine which takes a debug_backtrace() extracts only the information we need and outputs to a file. It's not Drupal-specific but you may need to set up the directory:


/**
 * Backtrace to a file for when nothing else works
 */
function debug_backtrace_file($fname = 'backtrace') {
  $file = fopen("c:\\tmp\\{$fname}.log", 'ab');
  if (!$file) {
    return;
  }
  fputs($file, '=============== ' . date('Y-m-d H:i:s') . " ===============\n");
  $line = __LINE__;
  foreach (debug_backtrace() as $entry) {
    $function = $entry['function'];
    if ($function!=__FUNCTION__) {
      if (isset($entry['class']) && $entry['class']) {
        $function = "{$entry['class']}::$function";
      }
      fputs($file, sprintf("function %s at line %d in file %s\n", $function, $line, isset($entry['file'])?$entry['file']:t('unknown')));
    }
    $line = $entry['line'];
  }
  fclose($file);
}


Happy bug hunting.

Thursday, 26 January 2012

Where the **** is it?

You know how it is? You have this complex and massive PHP structure with recursive elements and somewhere in all that is a value you're looking for. Views object I'm looking at you!

Here's a PHP routine, which is not Drupal specific, that will dig down into an object to find the property or array item you're looking for - and it avoids recursion by keeping a list of structures it's already searched by MD5ing the serialized version of the structure.

The parameters are the initial structure to search; the property you're looking for - it can be partial if you're not sure what the property is called; $id would usually be the identifier of the structure you're supplying and $depth should be ignored. It returns an array of strings that show the way into the structure to find the property. It's quite common to get multiple results.

One thing to watch out for: If your structure is recursive (like a Views object) and you start the search deeper into the structure with the idea that you'll shorten the search? Well you might, but if it's recursive you might also end up going into one of the recursed objects because the routine hasn't seen it before.

Anyway, with that in mind, here it is:


function common_locate($struct, $seek, $id = '', $depth = 0) {
  static $structs = array(), $results = array();
  if (is_array($struct) || is_object($struct)) {
    $struct = (array) $struct;
    foreach ($struct as $key => $value) {
      if (strpos($key, $seek)!==FALSE) {
        $results[] = "$id => $key = $value";
      }
      if (is_array($value)) {
        $idx = str_replace('0', 'g', md5(serialize($value)));
        if (!isset($structs[$idx])) {
          $structs[$idx] = "$key:$depth";
          common_locate($value, $seek, "{$id}[$key]", $depth+1);
        }
      }
      elseif (is_object($value)) {
        $idx = str_replace('0', 'g', md5(serialize($value)));
        if (!isset($structs[$idx])) {
          $structs[$idx] = "object:$key:$depth";
          common_locate($value, $seek, "{$id}->$key", $depth+1);
        }
      }
    }
  }
  return $results;
}