BTMash

Blob of contradictions

Powering multiple gallery blocks through one via the use of CCK and Views alter

Thu, 02/17/2011 - 00:40 -- btmash

A couple of months ago, I presented at a LA User Group meetup on getting rid of 60 - 70 odd gallery blocks on calarts.edu and getting them all powered by one block. I hadn't had a chance to properly present all the material at that time (and I didn't get a chance to share some/most of the code we'd used to get it all up and running) - I've decided to type out the whole history behind how the idea and the implementation that came about. Its a long read (and forgive me for the formatting of the coded portions - I need to figure out how to get the filters to account for this).

History

Soon after the site had launched, we had gone back to the drawing board to see where things stuck out like a sore thumb and realized that, based on the way we were powering image galleries as blocks, we had a few issues. To start with the simple problem, it starts with creating a new gallery which, while somewhat complicated, was powerful and easy enough for content editors to get through. However, if they wanted to show the gallery as a block on a series of pages:

  • Go to the views edit area and create a new view display
  • Go to the block edit area and adjust the block to show up on certain pages

What should have been relatively simple to set up upon creation of a gallery became a nightmare as:

  1. We use features to manage our views and content editors would have to deal with code push from our testbed -> staging -> production in such a scenario
  2. We wanted to close down access on views to site administrators
  3. Having so many blocks and so many views displays really bogged down the browser to a crawl

Proposal

In short, it just wasn't sustainable in the long run (imagine larger numbers like 100, 200...500, or 1000 galleries - you are now dealing with 1000 blocks and 1000 views displays). So upon thinking long and hard about the issue, our idea involved a few points:

  1. Create a view block display which takes a bunch of gallery IDs as the arguments.
  2. Create a new cck field for galleries where the content editor could then type out (in the same way as for a block) which pages or paths the content would appear (or not appear - we used the methodology offered by the Context module).
  3. Alter the view so that for a given page, check which galleries fit the current path, add it into the view as arguments.
  4. Pray?

This proposal solved a number of issues:

  • Close down on access to views ui.
  • Possibly close down on access for a content editor to manipulate the blocks.
  • For users with access to views ui or the block list page, it would allow for the page load to be much more manageable.
  • Content editors deal with one screen for modifying gallery information as opposed to 3 or more.
  • Inadvertently, it also allowed for the gallery block to be on a consistent area of the site.
  • Inadvertently, it also allowed for easily chaining up multiple galleries together on one path.

While the main site designer felt that the last point may be a possible issue, we decided that in the event that users want to have the gallery available in a different region, we can do so via the old method. So with that, I was given the task of making it all work. The other possible point of contention may be that the block cannot be cached (except on a per-page basis). Since the majority of the users that visit our site are anonymous, this was not an issue.

Implementation

I won't really explain the implementation for the image gallery since I basically use the same recipe as what Jeff Eaton wrote about a few years ago. The only difference this time is that I added a CCK text-area field called gallery_display_paths:

// Exported field: field_gallery_display_paths
  $fields['gallery-field_gallery_display_paths'] = array(
    'field_name' => 'field_gallery_display_paths',
    'type_name' => 'gallery',
    'display_settings' => array(
      'weight' => '-1',
      'parent' => '',
      '4' => array(
        'format' => 'default',
        'exclude' => 1,
      ),
      '5' => array(
        'format' => 'default',
        'exclude' => 1,
      ),
      'label' => array(
        'format' => 'above',
      ),
      'teaser' => array(
        'format' => 'default',
        'exclude' => 1,
      ),
      'full' => array(
        'format' => 'default',
        'exclude' => 1,
      ),
      '2' => array(
        'format' => 'default',
        'exclude' => 0,
      ),
      '3' => array(
        'format' => 'default',
        'exclude' => 0,
      ),
      'token' => array(
        'format' => 'default',
        'exclude' => 0,
      ),
    ),
    'widget_active' => '1',
    'type' => 'text',
    'required' => '0',
    'multiple' => '0',
    'module' => 'text',
    'active' => '1',
    'text_processing' => '0',
    'max_length' => '',
    'allowed_values' => '',
    'allowed_values_php' => '',
    'widget' => array(
      'rows' => '10',
      'size' => 60,
      'default_value' => array(
        '0' => array(
          'value' => '',
          '_error_element' => 'default_value_widget][field_gallery_display_paths][0][value',
        ),
      ),
      'default_value_php' => NULL,
      'label' => 'Path visibility settings',
      'weight' => 0,
      'description' => 'Enter one page/path per line as Drupal paths. The \'*\' character is a wildcard. Example paths are blog for the blog page and blog/* for every personal blog. Start a line with \'~\' if you do not wish for this to appear on a certain set of paths. Example path would be ~node and ~node/* to not appear on any node page). <front> is the front page.',
      'type' => 'text_textarea',
      'module' => 'text',
    ),
  );

After that, I created two views. One view that would return to me a list of published and accessible galleries on the site; the other was a gallery view block which would show me photos associated with gallery IDs as the argument. The first view was important as this allows for galleries to play with the domain access module (and realistically, it could follow any additional rules we add down the line (so we don't need to maintain the sql queries - hooray!). Once these were set up, we implemented hook_views_pre_view. This is important as this hook is called prior to the view being built out; it lets you add arguments and any other tables/whatnot to join in an easy manner.

/**
* Implementation of hook_views_pre_view().
*/
function my_media_gallery_views_pre_view(&$view, $display_id, &$args) {
  if ($view->name == 'gallery_carousel_side') {
    // Let me find out some info...
    $path = drupal_get_path_alias($_GET['q']);
    $nids = my_media_gallery_get_matching_references($path);
    if (count($nids) < 1 && $_GET['q'] !== $path) {
      $nids = my_media_gallery_get_matching_references($_GET['q']);
    }
    if (count($nids) > 0 && count($args) < 1) {
      $args[] = implode('+', $nids);
    }
  }
}

Barring a unhelpful comment, what the above does is figure out matching references based on the actual and aliased paths and the function looks like:

/**
* Get all matching galleries based on a given path
*/
function my_media_gallery_get_matching_references($requested_path) {
  $gallery_paths = views_get_view_result('gallery_paths');
  $nids = array();
  foreach ($gallery_paths as $gallery_path) {
    // The view provides the name of the field and I directly use that.
    $temp_paths = $gallery_path->node_data_field_gallery_display_paths_field_gallery_display_paths_value;
    $temp_paths = explode("\n", $temp_paths);
    $positives = FALSE;
    $negatives = FALSE;
    foreach ($temp_paths as $temp_path) {
      $check_path = trim($temp_path);
      if (strpos($check_path, '~') === 0) {
        $temp_check_path = trim(substr($check_path, 1));
        if (!empty($temp_check_path) && drupal_match_path($requested_path, $temp_check_path)) {
          $negatives = TRUE;
        }
      }
      else if (!empty($check_path) && drupal_match_path($requested_path, $check_path)) {
        $positives = TRUE;
      }
      if ($positives) {
        $nids[$gallery_path->nid] = $gallery_path->nid;
      }
    }
    if ($negatives) {
      unset($nids[$gallery_path->nid]);
    }
  }
  return $nids;
}

Where view gallery_path is the list of galleries that can be seen by the current user on the website. I'll mark down what all the above means at some point but I don't its not particularly difficult to follow (its consists of checking for matching paths and paths that say 'DO NOT PUT THIS GALLERY ON THE CURRENT PAGE!'). There is some level of hardcoded assumptions (name of the view, name of the particular field); however, I also packaged all of this up with features so the view is in the code and I can breathe with a sigh of relief for it all to work well and with minimal changes (other than the filters or argument that may power getting the data).

Conclusion

Like any story (would this count as a short story?), we need to reach a conclusion. Honestly speaking, the content editors and co-workers are very happy with the solution. We are on the verge of shutting down views ui (only to remain enabled on our dev and test servers), we have a bunch of fairly interesting image selections for pages (by chaining together galleries for pages), and enabling new galleries do not require code pushes. And we won't have to worry about 1000+ blocks coming from this functionality (other views and their setup are an entirely different story!)

I have been trying to figure out how to expand on what I wrote so that it can be part of a module users could use to power such blocks on their site. Any ideas on where to go about for such an implementation are very much welcome :)