What's new in Drupal 7 is that, due to Drupal's block and page caching and the capabilities of hook_page_alter, we now need to attach our stylesheet directly to the render element that w
Trang 1drupal_render() on its contents.
Attaching CSS to render arrays
If you look at the screenshot of the first version, you can see that the default styling
of our block is less then inspiring, so let's tweak that by giving our content some sensible default styling by adding a CSS stylesheet
Since version 5, Drupal has had a drupal_add_css() function to add CSS stylesheets
to pages What's new in Drupal 7 is that, due to Drupal's block and page caching and the capabilities of hook_page_alter(), we now need to attach our stylesheet directly
to the render element that we are creating If we were to use drupal_add_css(), the stylesheet would not be cached with its block and it would also be considerably more difficult to alter the stylesheet if a hook_page_alter() implementation desired
to (For example if it removed the block and wanted to remove the CSS too.)
So instead of calling drupal_add_css() from within our
single_blog_block_view() function, we add it to the returned render array:
// Add a CSS file to style the block.
$block['content']['#attached']['css'][] = drupal_get_path('module', 'single_blog') '/single-blog.css';
We use drupal_get_path() to find the path to our module relative to the website root The #attached array can contain a list of CSS files and JS files to attach to our render element For JavaScript files, just append them to the js array via
Trang 2One thing you'll need to be aware of when writing stylesheets is Drupal's support for
RTL languages, those languages that are read Right To Left, for example Arabic or
Hebrew Users of RTL websites expect everything about that website to flow to-left instead of English's normal left-to-right The convention used by websites that support both RTL and LTR languages is to flip the layout of the design horizontally depending on the directionality of the language
right-A great live example of how right-to-left website layouts are flipped is right-Amnesty International's website; compare the Arabic language version at http://www
amnesty.org/ar with the English language version at http://www.amnesty.org/en Notice how the sidebar changes sides depending on the language:
From a CSS standpoint, this means HTML elements whose left-side styling differs from their right-side styling need to have their styling altered when the current language is RTL If a RTL language is being displayed, Drupal will, for each
stylesheet, look for a supplemental RTL stylesheet to load So, if Hebrew is the active language, Drupal will look for single-blog-rtl.css to load in addition to (and just after) the requested single-blog.css file Since our -rtl stylesheet is loaded
in addition to the standard stylesheet, we simply need to include the rules and properties needed to override the LTR version of our styles To make it easier to keep track of those properties, Drupal modules should place a /*LTR*/ comment next to each property that needs to be overridden
Trang 3Notice that the block-single-blog.contentul rule in the single-blog.css
stylesheet specifies a left padding Since that's the only property that is directional, it's the only one we need to override in the single-blog-rtl.css file
/* $Id$ */
.block-single-blog content ul {
padding-right: 0;
}
Note that if our original left padding was 10px, we would have needed to
override that in our RTL stylesheet by setting padding-left to 0 and then
setting padding-right to 10px The following is a screenshot of version two
of our module block:
If you look at the screenshot, you can see the new More link and how the display of
our block has improved
Trang 4After all these modifications, the second draft of our single_blog_block_view()
function is now complete and should look like this:
// Set the block title.
$block['subject'] = t('Recent blog posts');
// Check if the user can access content.
Trang 5Okay, now it's time to exorcise our lazy-developer habit and practice building
our own theme hook From Chapter 3, you should recall that we'll need to do the
following things:
1 Register the theme hook and define default variables
2 Build the default implementation of our theme hook
3 Re-build the theme registry
4 Build a render element to use the theme hook
Our current implementation of the Recent blog posts block simply shows a list of
blog titles But it would be nice to include the date of each post, as well as the author (if we have multiple people creating posts) So in this third and final version of our module, we're going to create a single-blog-block-item.tpl.php to render the contents of each item in our list of blog posts By convention in Drupal, any CSS, JavaScript, or template files needed by a module should use dashes instead
of underscores in their filenames
Trang 6Before we begin building the required single_blog_block_item theme hook, let's first add all the data we will need for the third version of our module Looking back
at how we generate the items for our list, we can see that all the data we want is in the $node variable
// Create links for each blog entry.
Trang 7of the template file, single-blog-block-item Drupal will automatically add the
.tpl.php to the end of the base name when looking for the file, so we shouldn't include it
Variables versus render element
In Chapter 3, we learned about the differences between using the variables key and using the renderelement key in your hook_theme() One and only one of those keys must be present in each theme hook declaration However it still can be somewhat confusing as to which to use when you are building your theme implementation.There is only one situation in which you could use the renderelement key: if your data could be represented by a single render element or by a single renderable array containing nested render elements If that is not the case, then you must specify the
variables key and specify the variables you will be passed to theme() and their default values
So does our data conform to the render element requirement above? Our $node
variable is just a partial node object and not a render element, so we must use the
variables key and specify the default values for all our variables
As a side note, if we instead look at the way we've built the data element in the
second version of our module (a link#type render element), we can see that we could go ahead and use renderelement as the key if our second version of the module had a hook_theme() implementation
Since the node variable is an object, we set the default value to simply be the
NULL value
Trang 8That's actually the purpose of the preprocess function: to transform raw data into variables needed for a theme hook's template or theme function (Also, recall that preprocess functions should never query for raw data; the raw data should be passed as variables.)
Since we own this theme hook, we'll need to define our preprocess function with
To make it easier to access all the object properties of our node variable, we're going
to first create a $node local variable which we'll use inside the preprocess function: // Create a renderable array for the title.
// in a variable for themer convenience.
$variables['user'] = user_load($node->uid);
Trang 9// Theme the username.
$variables['name'] = theme('username', array(
'account' => $variables['user']));
}
And finally, we'll pass the $user object of the author and theme the username.All that's left is to order the variables the way we desire in our template file! However, since we've made the last change to our module file, let's look at the finished product:
// After you learn Form API in Chapter 5, you'll be able to
// make these settings configurable.
Trang 10// The array key defines the $delta parameter used in all
// other block hooks.
$blocks['recent'] = array(
// The name of the block on the blocks administration page.
'info' => t('Recent blog posts'),
// Set the block title.
$block['subject'] = t('Recent blog posts');
// Check if the user can access content.
if (user_access('access content')) {
// Retrieve the most recent nodes.
$result = single_blog_list(SINGLE_BLOG_LIST_COUNT);
Trang 11// Create links for each blog entry.
// Add a link to the full list of blog entries.
foreach (array_keys($elements['#items']) AS $key) {
// Take the renderable array that we set in
Trang 12// single_blog_block_view() and render it into the string
// that theme_item_list() expects.
// Create a renderable array for the title.
Trang 13// Load the account object with the node's creator and store
// in a variable for themer convenience.
$variables['user'] = user_load($node->uid);
// Theme the username.
$variables['name'] = theme('username', array(
<?phpprintt('string');?> snippet
Lastly, the show(), hide(), and render() functions are special themer-convenience functions that should only be used in template files; they should never be used in preprocess functions, theme functions or anywhere else render() is basically the same thing as the drupal_render() function we've already learned about The
hide() function can be used on a render array's child element earlier in the template before the render array calls render(); this will prevent the child element from being included with the rest of the render array when it is rendered For example (from Bartik's node.tpl.php):
Trang 14// Only display the wrapper div if there are links.
* - $classes: String of classes that can be used to
* style contextually through CSS It can be manipulated
* through the variable $classes_array from preprocess functions.
* The default values can be one or more of the following:
* - single-blog-block-item: The current template type,
* i.e., "theming hook".
* - $date: Formatted creation date Preprocess functions can
* reformat it by calling format_date() with the desired
* parameters on the $created variable.
* - $title: A renderable array that that provides a title and link to the node.
* - $name: Themed username of node author output from
* theme_username().
*
* - $classes_array: Array of html class attribute values
* It is flattened into a string within the
* variable $classes.
*
* Other variables:
* The following variables are provided for contextual information.
* - $node: Partial node object Contains data that may not be safe.
* - $created: Time the node was published formatted in Unix
* timestamp.
Trang 15* - $user: The user object of the node author.
*
* @see template_preprocess_single_blog_block_item()
*/
?>
<div class="<?php print $classes; ?>">
<div class="date"><?php print $date; ?>:</div>
The only variable that we didn't explicitly create in our preprocess function was the $classes variable This is a string that contains useful CSS classes that should
be placed in the outer-most wrapping HTML element in our template file The
$classes variable is created by template_processs() and its corresponding
$classes_array variable is created by template_preprocess() If we want to add additional classes to the $classes string, we should append an array element to the
$classes_array variable during our preprocess function and it will automatically
be added to the $classes string before reaching the template file
The string passed to the t() function, by!username includes the !username
token to give context to translators when trying to translate "by"; see the t() API documentation for more information
The last thing we should do, since we've updated the HTML markup returned by our block, is to also update the stylesheet:
/* $Id$ */
.block-single-blog content ul {
padding-left: 0; /* LTR */
}
Trang 16Congratulations! We're done!
Take a look at our accomplishment shown in the following screenshot:
Trang 17In this chapter we used our previous concepts and built an example theme
implementation using real-world situations In addition to the review of the theming concepts, you should have picked up on some of the strategies commonly used by Drupal developers and learned a little bit about contributing your experiences and knowledge back to the Drupal community
In the next chapter, you'll learn about building admin interfaces for your module
So while the Single Blog module used slightly-funky constants that defined some hard-coded settings, the module in the next chapter will have a rich administrative interface to allow site admins to configure its settings
Trang 18Building an Admin Interface
In this chapter we will create a module with an administrative interface This module will build upon many of the module creation concepts that were introduced in
Chapter 2 Some of the concepts we will cover in this chapter are:
Mapping Drupal functions to menu items using hook_menu()
Creating basic forms with the Form API
Managing Drupal settings using variable_set() and variable_get()
Sending mail using drupal_mail() and hook_mail()
Using Drupal 7's new token system
After this chapter is finished you should have a good handle on many concepts that are at the core of almost every module you will write in the future
The User Warn module
In this chapter we will be creating the User Warn module This module allows administrators to send users a warning via e-mail when that user violates a site's terms of service or otherwise behaves in a way that is inappropriate The User Warn module will implement the following features:
The module will expose configuration settings to site administrators,
including default mail text
This e-mail will include Drupal tokens, which allow the admin to replace and/or add site-specific variables to the e-mail
Site administrators will be able to send a user mail via a new tab on their user profile page
Warning e-mails will be sent using Drupal's default mail implementation
Trang 19Starting our module
We will begin as we did in Chapter 2, by creating a new folder for our module called
user_warn in the sites/default/modules directory in our Drupal installation We can then create a user_warn.info file as shown in the following:
;$Id$
name = User Warn
description = Exposes an admin interface to send behavior warning e-mails to users.
* This module allows site administrators to send a stock warning
* e-mail to a specified user or users through the admin interface
Trang 20The Drupal menu system
Drupal's menu system is deceptively named The name implies that it is responsible for the navigation of your site, and while this is true it does a great deal more At its core, the menu system is responsible for mapping Drupal paths to the functions that generate the contents of the requested page The menu system is also responsible for controlling access to Drupal pages, acting as one of the central gatekeepers of Drupal security
Drupal module developers can map paths to Drupal functions by implementing
hook_menu(), which adds paths to the menu system, assigns them access rules, and optionally creates navigational elements for them
Defining a page callback with hook_menu
For our module we will need to implement two new pages—a configuration page for the User Warn module, and a tab in the user profile area where administrators can go to send the actual e-mails to a specific user These will each require their own
hook_menu() implementation as defined in the following example
This example only scratches the surface of the options available in the
menu system For more details, developers should check out the API
'title' => 'User Warn',
'description' => 'Configuration for the User Warn module.',
'page callback' => 'drupal_get_form',
'page arguments' => array('user_warn_form'),
'access arguments' => array('administer users'),
'type' => MENU_NORMAL_ITEM,
);