Posts filed under Development
Fresh Paint
The Aten Design Group website has a new look. Well, maybe not an altogether new look, but a revised look. We’ve modified the typography, adjusted the layout, and made some color changes. The blog sports a reversed color scheme, with syntax highlighting for code examples.
The copy has changed, too. We’ve added projects to the portfolio, added text about our technical proficiencies and our iterative process, and added a couple employee profiles.
And then there is the technical platform - you guessed it, that changed also. We moved from a hybrid combination of custom PHP plus WordPress to a shiny clean installation of Drupal. The new framework provides a more robust environment for potential changes in the future, offers site-wide categorization, and provides better user management for delegating content development internally. It’s easier to publish content, faster.
So maybe “Fresh Paint” is a bit of an understatement — it’s more like “New Foundation, New Walls, New Furniture and Fresh Paint.” But what a mouthful.
YouTube API Module in the Wild
Earlier this week I was able to release the first supported version of the YouTube API module I've been working on for the last couple months. Initially I thought I'd be releasing the beta in early July, but instead decided to hold out until I could find some time to work on feedapi integration.
An overview of features in this initial release:
- Video Management
- Creating (uploading)
- Editing
- Deleting
- FeedAPI Integration (think views...)
- Video Feeds
- User Feeds
- Searches
- Standard Feeds (as outlined in the YouTube API Developer's Guide)
- Commenting - reading and submitting.
- Other
- Authentication
- Video Rating
- Favorites
..and a handful of others. Besides the feedapi integration offered by the module, all of the other features require a bit of coding on the end users side. My goal is not to make integration decisions for end users, but to give them all the features necessary to be able to take full advantage of the YouTube public API in just a few lines of code.
Better documentation is on the way™, but for now you can get started by checking out the examples on the modules project page, taking a look at the modules function reference, and getting familiar with the YouTube API Developer's Guide wouldn't be a bad idea either.
Enjoy!
- Video Management
Taming the Drupal 5 Permissions System
A 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
The 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
Web-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. JavaScript editors tend to be buggy, generate bad HTML, and produce inconsistent results between browsers. Requiring clients to learn HTML doesn't work. Often, they'll just say no... and even if they're up to the task, the website will almost certainly end up with broken code. Asking the marketing manager, department head, or whoever to learn HTML is a bit of stretch in the first place -- they just shouldn't have to.
Promoting Poor Habits
Even when JavaScript editors work well, they allow -- even encourage -- bad practice. Clients start thinking that a line of text needs to be just a bit bolder, or a slightly different color, or have more padding. With JavaScript text editors, they can make those changes. Moreover, the fact that the text editor is a WYSIWG, and visual in nature encourages those changes. Clients start focusing on what the text looks like, more than what it is. What starts as an exception or two often becomes widespread inconsistency. Extraneous tags (extra line breaks, span tags, or even font tags with color attributes) creep in.
And this, of course, impairs the quality of content. When tags are added to a particular piece of content to solve specific, isolated design goals, that content looses its ability to be used in broader applications. Then there is the issue of future redesigns. A few years ago, we were looking with a client at a simple redesign for a website and CMS upgrade; the biggest hurdle (and demise of the project) were the countless pages of text littered with font tags, extra paragraphs, and left-over classes pasted in from desktop word processors.
Example
A recent example involves a Drupal CMS multisite implementation providing a centralized admin environment for two distinctly different websites. We originally used TinyMCE, a JavaScript text editor, that was configured to use the visual style from the first website long before the second was ever launched. Headings are Georgia, and blue. Sub-headings are Arial, also blue. Bullets are simple discs
In the second website, though, the styling is different. We used background gifs for ornate bullets, and sub-headings are Georgia and gray.
We discussed the option of styling the editor to match the second site when content was published there -- but what about cases where content might be used in both places? What if the contributors had no idea where their content would be published, and were simply given a subject and tasked to write?
The problem became clear. Using the JavaScript WYSIWYG editor, contributors focussed on appearance rather than structure. This was confusing, particularly with the introduction of the second website.
So we began talking about thinking of content in terms of what it is, not what it looks like.
The Solution
As I mentioned above, requiring clients to learn HTML isn't realistic. And I've explained our problematic experience with WYSIWYG editors. We wanted a tool that doesn't require HTML experience and allows clients to edit text easily and perform simple formatting, while maintaining an emphasis on structure rather than presentation. So we've moved to a solution that uses simple formatting codes to add structure to text.
We use Markdown.
What is Markdown?
According to Wikipedia:
"Markdown is a lightweight markup language, originally created by John Gruber and Aaron Swartz, which aims for maximum readability and "publishability" of both its input and output forms, taking many cues from existing conventions for marking up plain text in email."
Markdown uses simple formatting codes to format text, rather than tags. Instead of <strong>bold</strong>, it's **bold**. Instead of <em>italics</em>, it's _italics_. An H1 heading would appear as:
H1 Heading
==========One of the best things about Markdown is the fact that it makes plain-text more legible, unlike HTML. Take a look at the following examples:
- Text with Markdown formatting Headings read like headings, lists like lists.
- Same text, transformed to HTML Markdown transforms perfectly to semantic XHTML. Additional formatting can be added where it should be, in a CSS file, and can be easily altered for various unique applications.
- HTML output, showing HTML source code Notice that HTML code doesn't improve the legibility of the text. It doesn't read well, doesn't add meaning - or structure - to raw text input.
Markdown was originally developed by Gruber and Swartz as a software tool written in Perl, and can be downloaded from John Gruber's widely read website, http://daringfireball.net. An excellent PHP port was written by Michel Fortin, and is available for download at http://michelf.com/projects/php-markdown/. The Drupal module, built on Michel's PHP port, is available at http://drupal.org/project/marksmarty. Many other publishing tools have their own implementations.
What Clients Think
We have used Markdown in most of our CMS implementations. So far, there has been very little resistance. Those clients that may have resisted the idea at first typically embrace it once they actually use the syntax. Several clients have actually opted to switch, even in cases where we started them with a JavaScript WYSIWYG editor. Markdown is incredibly simple to use, helps avoid the problem of broken (or just bad) HTML creeping into content, and offers flexibility when it is needed.
