-
Archives for Development
Taming the Drupal 5 Permissions System
Posted by Brad on Jun 26, 2008A few weeks ago I wrote a short how-to on allowing various roles to view unpublished content without having the ‘administer nodes’ permission in Drupal 5. At that time I promised to write a follow-up article on taming the Drupal 5 permissions system, allowing for additional circumvention of the monolithic ‘administer nodes’ permission. A few weeks, and one cramped cross-country flight later, that article (and the code behind it for that matter!) has finally ‘landed’ as it were.
The first step is once again to query the database for a list of all content types in the system, and then loop through them and output appropriately granular permissions for the various publishing options we want to support.
/** * Build a list of permissions for publishing, promoting, making * sticky, and creating revisions for each content type in the system. */ function publisher_perm() { $perms = array(); //get a list of all content types in the system $types = db_query("select type from {node_type} order by type"); $i=0; //loop through and add permissions the perms array. while ($type = db_result($types, $i++)) { $perms[] = 'Publish '. $type .' nodes'; $perms[] = 'Promote '. $type .' nodes to front page'; $perms[] = 'Make '. $type .' nodes sticky'; //it's recommended you turn on 'view revisions' for roles using this permission //to get the full 'revision' effect. Otherwise there is no hope of feedback for //users when creating new revisions. $perms[] = 'Create new revisions for '. $type .' nodes'; } return $perms; }Once we’ve done that we have the ability to assign these permissions on a per-role basis. However, this does nothing for making the permissions to show up on the node editing form, so we’ll have to create a implementation of hook_form_alter to add the checkboxes into the node edit forms where appropriate. That’s going to look like this:
function publisher_form_alter($form_id, &$form) { //determine if the form being created is a node creation/edit form if (arg(0) == 'node' && (arg(2) == 'edit' || arg(1) == 'add')) { $node = $form['#node']; //determine if the current user has rights to publish, promote, create revisions, or make a node of this type sticky. if (!user_access('administer nodes') && (user_access('Publish '. $node->type .' nodes') || user_access('Promote '. $node->type .' nodes to front page') || user_access('Make '. $node->type .' nodes sticky') || user_access('Create new revisions for '. $node->type .' nodes'))) { //Now we'll steal some code from node.module, wrapping the various options in our user_access callback. //I've left the '#access' key in place, but commented it out, for easy comparison to the original node.module code. $form['options'] = array( '#type' => 'fieldset', //'#access' => user_access('administer nodes'), '#title' => t('Publishing options'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 25, ); if (user_access('Publish '. $node->type .' nodes')) { $form['options']['status'] = array('#type' => 'checkbox', '#title' => t('Published'), '#default_value' => $node->status); } if (user_access('Promote '. $node->type .' nodes to front page')) { $form['options']['promote'] = array('#type' => 'checkbox', '#title' => t('Promoted to front page'), '#default_value' => $node->promote); } if (user_access('Make '. $node->type .' nodes sticky')) { $form['options']['sticky'] = array('#type' => 'checkbox', '#title' => t('Sticky at top of lists'), '#default_value' => $node->sticky); } if (user_access('Create new revisions for '. $node->type .' nodes')) { $form['options']['revision'] = array('#type' => 'checkbox', '#title' => t('Create new revision'), '#default_value' => $node->revision); } } } }Now we just need to add in a submit callback to set these values on the node object, or we can write some ugly queries which set these values in the database direc… wait, what?! we don’t? Nope, we don’t. We’re done. The beauty of this method is that the forms submit handler, node_form_submit(), will still do all the hard work for us. Node.module does it’s access control for this functionality by adding a ‘#access’ key to the ‘Publishing Options’ fieldset when creating a node add/edit form, meaning in form node.module originally built gets processed by the Forms API it will omit the publishing options if the current user doesn’t have sufficient privileges. There’s no reason for it to do a follow-up permissions check after the form is submitted, since the built in security in the Forms API will disregard any values submitted which weren’t in the form after it was originally built (preventing malicious users from adding additional fields to the form). What that boils down to in less-nerdy terminology is this; if these values show up in a form after it’s submitted then the fields were put there by a module, and node.module isn’t concerned with whether it created the form fields, or whether they come from another module.
It’s interesting to compare this to the method I wrote about to allow users to view unpublished content. In both cases we’re modifying access rules to functionality that core provides. With the view_unpublished module we were able to selectively override the menu items responsible for displaying nodes. With this module, we selectively override elements in the node forms to display when conditions we set are met. In both cases, the Drupal core provided a great road-map for how to properly achieve our goals, and our modifications boil down to overriding the default access checks core uses. It’s a testament to the Drupal core that it’s flexible enough to allow these modifications from the outside-in, without requiring developers to destroy their upgrade path to get the features they need.
-
Allowing Content Editors to View Unpublished Content in Drupal 5
Posted by Brad on Jun 04, 2008The Problem
A couple weeks ago we received a call from a client who was having problems giving content editors access to nodes which were in the unpublished state. The particular client was recently upgraded from a Drupal 4.7 to Drupal 5.7. In addition to upgrading the site, we also redesigned and integrated a second Drupal site into the same installation. One of the goals for this redesign was to be able to easily provision content editors for each site, allowing their internal teams full control over the content that appeared on their site, and their site alone. The problem we ran into when trying to do this was that the “administer nodes” permission was too much of a catch-all for us to be able to effectively designate permissions how we needed. Particularly of concern was the inability for content editors to access unpublished content, since many of the nodes on the site are used for internal purposes and never intended for general public consumption. After the break we’ll have our solution in all it’s nerdy glory.
The Cause
The node.module implementation of hook_menu maps the path node/nid to the node_page_view() function with this menu item:
$items[] = array( 'path' => 'node/'. arg(1), 'title' => t('View'), 'callback' => 'node_page_view', 'callback arguments' => array($node), 'access' => node_access('view', $node), 'type' => MENU_CALLBACK, );What we want to avoid here is call to node_access, which will always return false when a given user is viewing unpublished content which they didn’t create, while not having the ‘administer nodes’ permission.
The Solution
The first step is to build permissions for viewing unpublished content of each type in the system:
/* * Build a permissions list for viewing unpublished nodes of all content types. * Also, provide a 'use view_unpublished module' permission which determines if this module * will even attempt to override the default node/nid path. */ function view_unpublished_perm() { $perms = array('use view_unpublished module', 'view all unpublished content'); $types = db_query("select type from {node_type}"); $i = 0; while ($type = db_result($types, $i++)) { $perms[] = 'view unpublished '. $type .' content'; } return $perms; }Once we have our nice view unpublished content checkboxes for each content type, we can do the heavy lifting:
/* * Selectively overrides the node/nid path to set access => true when a user has permission * to view unpublished content */ function view_unpublished_menu($may_cache) { $items = array(); if (!$may_cache) { if (is_numeric(arg(1)) && arg(0) == 'node' && user_access('use view_unpublished module') && !user_access('administer nodes')) { $node = node_load(arg(1)); if ($node->status == 0 && (user_access('view unpublished '. $node->type .' content') || user_access('view all unpublished content'))) { $items[] = array( 'path' => 'node/'. arg(1), 'title' => t('View'), 'callback' => 'node_page_view', 'callback arguments' => array($node), 'type' => MENU_CALLBACK, 'access' => true, ); } } } return $items; }the view_unpublished_menu() function simply checks to see if there’s an incoming request for a path in the form of node/nid. In the event that it does, the node is unpublished, and a role you belong to has permissions to view unpublished nodes of that content type, the menu path gets overridden with access set to true. This completely circumvents the call to node_access() which was giving us problems. It should be noted that you may have to adjust the module’s weight to make sure it’s implementation of hook_menu is called after the one in node.module, so you can correctly override the menu path set in node.module.
-
What It Is, Not What It Looks Like
Posted by Justin on Apr 21, 2008Web-based Text Editing
Website owners and managers need a simple way to edit the text within their websites. They need to add headings, make bold text, bulleted lists, and so forth. It’s a fundamental need in Content Management implementations, and perhaps the most used feature of any CMS.
In spite of this, the solutions widely available today are horrible. more »
-
On Becoming a Drupal Development Shop
Posted by Justin on Apr 16, 2008Two years ago we began working with Drupal. Before then, our content management implementations were dominated by our own PHP/MySQL-based CMS, which itself was the result of several years of development, revision, and rewriting. Since we began working with Drupal two years ago, we haven’t launched a single project using our own CMS. And we never will again.
We are primarily a design company. The user experience is at the center of every project we take on, every design concept we create. The goals vary widely from client to client. When clients look to us to provide content management, we need a product that is flexible and efficient – one that allows us put the user experience first, without having to reinvent a solution for each project.
Our own CMS delivered in these two areas, but added a problematic maintenance burden to the development cycle. As I mentioned before, we are primarily a design company. Developing and maintaining a content management solution is not our core business.
Two years ago, when a new client asked us to review several open-source CMS products and provide a recommendation, we did some research and suggested Drupal. Since then, more than a dozen Drupal implementations have followed.
Here’s why:
- Drupal provides a presentation layer, via a robust theme engine, for customizing the HTML output of virtually any piece of the application. We can put the user experience first.
- There is an incredibly active Drupal community. The product’s maintenance is distributed across a passionate group of 1000+ developers.
- The Drupal community cares about standards. The generated HTML code is a bit bloated at times, as happens with any generalized solution, but by-and-large its HTML output conforms to modern web standards.
- There are literally thousands of contributed modules providing a vast range of additional functionality.
- Often, the functional requirements for a project can be met without writing a single line of code. Specifically, the out-of-the-box flexibility provided by the Views and CCK modules is amazing.
- Drupal offers a robust application development framework for add-on functionality.
- Drupal is built on PHP with current support for MySQL and PostgreSQL databases, with plans for pluggable database support in version 7. Drupal is built on technologies that are among the most widely used and supported on the web today.
Over the last two years we have deployed solutions using Drupal that range from simple, “brochure-ware” websites; to websites that integrate with enterprise CRM systems and deliver real-time data for hundreds of projects worldwide; to informational websites for educational institutions; to community websites featuring user-generated content for prominent, widely recognized cultural heritage institutions. The applications have varied widely, and the final products have delivered on goals every time.
We’re hooked.
-
We’re Hiring
Posted by Justin on Oct 29, 2007Aten Design Group seeks a talented Flash developer to assist in creating rich online experiences. Projects will include online games, product configurators, interactive mapping applications, and more. Our clients include some of the world’s best known non-profit and cultural heritage institutions.
Freelance Position Requirements:
- Extensive ActionScript experience
- Ability to work well in a team setting
- Flexible schedule to permit fluid collaboration with our team
- Ability to work from our office in Denver, CO is preferred but not required
Interested?
Email work@atendesigngroup.com with your portfolio, work experience, and rates.
-
Bloat
Posted by Justin on Aug 15, 2007We recently launched another Drupal-powered website, and one minor requirement was a custom node-type that included a date field.
We needed to output a simple date… say, something like:
<div class="date">Saturday, August 18, 2007</div>Turns out Drupal likes dates a bit fatter:
<div class="field field-type-datestamp field-field-date"> <div class="field-items"> <div class="field-item"> <span class="date-display-single"> Saturday, August 18, 2007 </span> </div> </div> </div>Which led to yet another conversation between myself and Ken regarding Drupal, some of the disconnects between that CMS and our process, and their solutions.
Needless to say, there remains no one-size-fits-all solution. The features offered by Drupal, its vocal and active community, and its highly modular architecture make it an incredibly effective product for a wide range of implementations. On the other hand there are significant sacrifices — for one, ridiculous HTML-bloat. Or, when it’s worth it, there is the time lost trimming that same bloat.
-
Designing for CodeIgniter
Posted by Justin on Apr 06, 2007Last week we launched our very own redesigned website. Early in the design process we discussed options for handling content management, and as the launch date neared, we refined our requirements.
Basically, the site would need:
- Dynamic portfolio pieces, with the top-most piece published automatically to the home page
- Static (X)HTML for the about and contact pages
- A contact form
- A blog
Initially we considered Drupal, a feature-rich open-source CMS we’ve used in the past with great results. For this case, Drupal seemed too feature rich.
We Opted to Build Instead.
Before the redesign I had a small amount of experience using CodeIgniter for some internal projects. The framework is lightweight, well-structured, and provides a broad sprinkling of functionality via its libraries. I was happy for a chance to use it to build a site from the ground-up, and eager to see just how quick and painless it would prove to be.
Quick and Painless
The results were good. The portfolio came together in a couple of evenings — just a few hours altogether, including an admin section for adding, editing, and ordering the various pieces.
I started coding the blog on the flight back from An Event Apart Boston, and was 90% finished when we touched down on the runway. Once back in the office, I wrapped up the comments functionality.
Along the way, we made several significant changes to the (X)HTML/CSS structure of the site, moving it from a fixed design (1024×768) to a fluid-width design that shrinks nicely to 800×600 screen resolutions. CodeIgniter’s clean view/controller abstraction facilitated those changes perfectly.
Some Helpful Modifications
It’s worth mentioning a few extensions we created that made the job even easier. We coded extensions to:
- Automatically call the set_fields() validation function when set_rules() is used, to reduce code and increase automation
- Preload the validation object with field values from any given object - specifically to preload forms with fields from a database select
- Prep validated POST arrays for database entry by comparing array keys to database table fields
- Include active-record functionality into the base Model class
We’re happy to provide mentioned extensions if anyone’s interested… just ask us for them using the contact form or by leaving us a comment.
