Advanced theme settings

In the Drupal administration section, each theme has its own settings page at admin/build/themes/settings/themeName. And this page has a form with standard settings like “Logo image settings” and “Shortcut icon settings.”

In Drupal 6, theme authors can now customize this page by adding additional settings to the form. In Drupal 5, theme authors and theme users will have to install the Theme Settings API module (5.x-2.1 or later) before being able to use the method described below.

Adding form widgets for your custom theme settings

First, create a theme-settings.php file in your theme directory and add a themeName_settings() or themeEngineName_settings() function. The themeEngineName_settings() form is preferred since it allows others to more easily create derivative themes based on your theme. The function should use the Forms API to create the additional form widgets.

For example: to add settings to the Garland theme, a garland_settings() or phptemplate_settings() function would be placed in the theme’s theme-settings.php file.

Directly modifying Garland or Minnelli is strongly discouraged, since they are used for the install and upgrade process.

If a user has previously saved the theme settings form, the saved values will be passed to this function in the $saved_settings parameter. The widgets to add to the form should be returned as a Forms API array.

The comments in the following example explain the details:

<?php
// An example themes/garland/theme-settings.php file.

/**
* Implementation of THEMEHOOK_settings() function.
*
* @param $saved_settings
*   array An array of saved settings for this theme.
* @return
*   array A form array.
*/
function phptemplate_settings($saved_settings) {
 
/*
   * The default values for the theme variables. Make sure $defaults exactly
   * matches the $defaults in the template.php file.
   */
 
$defaults = array(
   
'garland_happy' => 1,
   
'garland_shoes' => 0,
  );

 
// Merge the saved variables and their default values
 
$settings = array_merge($defaults, $saved_settings);

 
// Create the form widgets using Forms API
 
$form['garland_happy'] = array(
   
'#type' => 'checkbox',
   
'#title' => t('Get happy'),
   
'#default_value' => $settings['garland_happy'],
  );
 
$form['garland_shoes'] = array(
   
'#type' => 'checkbox',
   
'#title' => t('Use ruby red slippers'),
   
'#default_value' => $settings['garland_shoes'],
  );

 
// Return the additional form widgets
 
return $form;
}
?>

Note that theme authors can create complex, dynamic forms using advanced Forms API (auto-completion, collapsible fieldsets) and JQuery javascript.

Getting the settings’ values in your theme files

In order to retrieve the settings in the theme’s template.php or .tpl.php files, simply use theme_get_setting('varname'). See the Drupal API for details: http://api.drupal.org/api/6/function/theme_get_setting

For example:

<?php
$happy
= theme_get_setting('garland_happy');
?>

Initializing the default values

Since we can’t guarantee that a user will ever go to the admin/build/themes/settings/themeName page, we have to ensure that the default values for our custom settings get initialized.

The theme settings variables aren’t set until we submit the admin/build/themes/settings/themeName form for the first time, so in our template.php file we need to check whether the variables are set or not. If they aren’t set, we need to set them to the default values. We accomplish that by retrieving one of the variables and seeing if it is null. If it is null, we save the defaults using variable_set() and then force the refresh of the settings in Drupal’s internals using theme_get_setting('', TRUE).

Add the following code near the top of your template.php file:

<?php
/*
* Initialize theme settings
*/
if (is_null(theme_get_setting('garland_happy'))) {  // <-- change this line
 
global $theme_key;

 
/*
   * The default values for the theme variables. Make sure $defaults exactly
   * matches the $defaults in the theme-settings.php file.
   */
 
$defaults = array(             // <-- change this array
   
'garland_happy' => 1,
   
'garland_shoes' => 0,
  );

 
// Get default theme settings.
 
$settings = theme_get_settings($theme_key);
 
// Don't save the toggle_node_info_ variables.
 
if (module_exists('node')) {
    foreach (
node_get_types() as $type => $name) {
      unset(
$settings['toggle_node_info_' . $type]);
    }
  }
 
// Save default theme settings.
 
variable_set(
   
str_replace('/', '_', 'theme_'. $theme_key .'_settings'),
   
array_merge($defaults, $settings)
  );
 
// Force refresh of Drupal internals.
 
theme_get_setting('', TRUE);
}
?>

Note that the variable name “garland_happy” in the first line of the above code would be replaced with a variable name from your custom theme settings and the $defaults array would need to be copied from your theme-settings.php file.

Adding additional settings to a new version of your theme

After you have released a 1.0 version of your theme, you will eventually want to add some additional custom settings to the 2.0 version. The process is mostly straight-forward. But pay close attention to the initialization code in the third step:

  1. In your theme-settings.php file, add the new settings to the $defaults and $form variables.
  2. In your template.php file, add the settings to the $defaults variable in the Initialize theme settings code.
  3. In your template.php file, update the initialization code to check for the existence of one of your new settings. For example, if you added several settings, including a garland_slippers setting, you would change the first line of the Initialize theme settings code to read:
    if (is_null(theme_get_setting('garland_slippers'))) {

This will ensure that the defaults for your newly-added custom settings get added to the saved values of the old custom settings.

Preferred function name correct?

thesaint_02 - June 9, 2008 - 15:45

The text says:

The themeEngineName_settings() form is preferred since it allows others to more easily create derivative themes based on your theme.

Is this really correct? What about sub-themes. If they want to implement their own settings and follow this advice, they will produce a function name conflict. I think the sentence should read

Use themeEngineName_settings() for base themes and themeName_settings() for derived themes.

Using these variables...

dman - September 1, 2008 - 03:52

In Drupal 5 you may have wanted to access theme-specific variables in _phptemplate_variables(). That has been deprecated, and the Drupal 6 way of passing or modifying theme $variables (variables available to your templates) is through theme preprocess functions.

.. just cross-referencing here, because this is the page I thought this information would be on :-)

.dan.

How to validate?

sebagr - November 12, 2008 - 15:34

I'm working with theme-settings.php and am seeking for a way to validate these settings.

I tried using something like phptemplate_settings_validate() but this does not work for me.

I also tried to use $form['#validate'], but this doesn't seem to work either.

Do anybody know how to perform validation on theme specific settings?

It would be nice to have the same validation process we have with the standard Forms API when building modules, wouldn't it?

You can use

xuecan - January 27, 2009 - 12:04

Upload a file

Angelinsky7 - April 6, 2009 - 16:34

Maybe it's a stupid question but how can i upload a file
in the theme settings ???

i'v already tried :

http://drupal.org/node/85922
http://drupal.org/node/111782
http://api.drupal.org/api/file/developer/topics/forms_api.html
etc...

The problem seems that i can't validate my form because there is another handler on all the theme settings forms... (i'm maybe wrong...)

My reason to do that : i want a second logo (on the left of the page) that the webmaster would change with a upload... EXACTLY like the regular logo...

Is there a better solution ???

Thanks a lot (if i'm not in a good place to put that i'm sorry, but i've found nothing in the forum)

Angel

Angel

here.

dman - April 6, 2009 - 22:10

This worked fo me. An additional logo (Appeared in the footer everywhere)
Not sure why I had to put the submit handler into the #validate pass :-/
It was copied from the existing theme settings funcs.
'campaign_' is the theme name.

<?php
/**
* Implementation of THEMEHOOK_settings() function.
*
* @param $saved_settings
*   array An array of saved settings for this theme.
* @return
*   array A form array.
*/
function campaign_settings($saved_settings) {
 
$settings = theme_get_settings('campaign');

 
$form['sublogo'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Secondary logo'),
   
'#description' => t("May be displayed in the footer or under a column.")
  );
 
$form['sublogo']['use_sublogo'] = array(
   
'#type' => 'checkbox',
   
'#title' => t('Use a sublogo.'),
   
'#default_value' => $settings['use_sublogo'],
  );
 
$form['sublogo']['sublogo_path'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Path to sublogo'),
   
'#default_value' => $settings['sublogo_path'],
  );

 
$form['sublogo']['sublogo_upload'] = array(
   
'#type' => 'file',
   
'#title' => t('Upload sublogo image'),
  );
 
$form['#submit'][] = 'campaign_settings_submit';
 
$form['sublogo']['sublogo_upload']['#element_validate'][] = 'campaign_settings_submit';

  return
$form;
}

/**
* Capture theme settings submissions and update uploaded image
*/
function campaign_settings_submit($form, &$form_state) {
 
// Check for a new uploaded file, and use that if available.
 
if ($file = file_save_upload('sublogo_upload')) {
   
$parts = pathinfo($file->filename);
   
$filename = (! empty($key)) ? str_replace('/', '_', $key) .'_sublogo.'. $parts['extension'] : 'sublogo.'. $parts['extension'];

   
// The image was saved using file_save_upload() and was added to the
    // files table as a temporary file. We'll make a copy and let the garbage
    // collector delete the original upload.
   
if (file_copy($file, $filename)) {
     
$_POST['use_sublogo'] = $form_state['values']['use_sublogo'] = TRUE;
     
$_POST['sublogo_path'] = $form_state['values']['sublogo_path'] = $file->filepath;
    }
  }
}
?>

.dan.

Thanks a lot... It worked

Angelinsky7 - April 8, 2009 - 16:39

Thanks a lot...
It worked very well....

Angel

Angel

can't get this working all the way

emilyf - May 27, 2009 - 18:45

@dman, this works great for the upload, but i can't seem to access this image in my page.tpl.php file...i have tried <?php print $sublogo;?> as well as adding <?php $vars['sublogo'] = theme_get_setting('sublogo->sublogo_path');?> in the img src area and nothing seems to work...how can i access this file path?

did you try?

dman - May 28, 2009 - 00:35

<?php
print theme('image', theme_get_setting('sublogo_path'));
?>

or
<img src="<?php print url(theme_get_setting('sublogo_path')); ?>" />

... If you looked at your HTML source or your 404 log you'd see the problem pretty quick.

.. the above code is without even rolling it through the $vars, although using hook_preprocess_page() to set the var is probably best practice.

.dan.

Instead of writing your own submit function

yakker - October 28, 2009 - 20:16

First, a huge thanks for posting this dan - the book chapter is a bit obscure by itself, and I needed to do a theme settings upload too.

I am not sure of what I'm about to write here and don't mean any kind of criticism, so: you more educated themers out there, please respond to set things straight. ;)

I was able to put the upload check/copy code directly into my theme_settings function instead of writing a separate submit function.

I'm not sure what is best practice, but if you look atsystem_theme_settings() and system_theme_settings_submit(), similar form element creation and upload/copy code occurs in system_theme_settings(), not the submit function. Because system_theme_settings() includes a line of code to require_once() code from your own theme-settings.php file, you can essentially write your own theme_settings() function from your theme-settings.php file as though it were just adding to system_theme_settings (not replacing it).

But if you put your upload/copy code into a separate submit function and call it instead of system_theme_settings_submit(), aren't you bypassing it and thereby disabling all the other stuff that would be done by system_theme_settings_submit (such as the variable sets)?

Again - really not sure of this, but I've got my uploads working with file_save_upload, and file_copy sitting in my theme_settings() function (no separate submit function).

<?php

function opus_settings($saved_settings){

$opus_defaults = array(
   
'default_background' => 1,
   
'background_path' => 'sites/default/themes/opus/images/backgrounds/pencildisplay.jpg',
);

$settings = array_merge($opus_defaults,$saved_settings);

// Check for a new uploaded background and use that instead.
 
if ($file = file_save_upload('background_upload', array('file_validate_is_image' => array()),FILE_EXISTS_REPLACE)) {
   
$parts = pathinfo($file->filepath);
   
$filename = 'backgrounds/';
   
$filename .= ($key) ? str_replace('/', '_', $key) .'_bg_'. $parts['basename'] : $parts['basename'];

   
// The image was saved using file_save_upload() and was added to the
    // files table as a temporary file. We'll make a copy and let the garbage
    // collector delete the original upload.
   
if (file_copy($file, $filename, FILE_EXISTS_REPLACE)) {
     
$_POST['default_background'] = 0;
     
$_POST['background_path'] = $file->filepath;
     
drupal_set_message($message = t('Your new background image has been saved at: ').$file->filepath, $type = 'status', $repeat = TRUE);
    }
  }

$form['default_background'] = array(
     
'#type' => 'checkbox',
     
'#title' => t('Use the default background image'),
     
'#default_value' => $settings['default_background'],
     
'#tree' => FALSE,
     
'#description' => t('Check here if you want the theme to use the background supplied with it.')
    );

$form['background_path'] = array(
 
'#type' => 'textfield',
 
'#title' => t('Background image path'),
 
'#default_value' => $settings['background_path'],
  );

$form['background_upload'] = array(
 
'#type' => 'file',
 
'#title' => t('Upload new background image'),
  );

  return
$form;
}
?>

Then, in template.php using template_preprocess_page() I defined my background path variable that I can use in page.tpl.php. This was necessary because the regular theme settings rely on a "toggle" setting to determine the default path for uploads (logo, favicon). I don't want this - my theme always has a background - so I needed a way for the theme to respond to the check mark in "Use the default background image" that didn't involve rewriting $settings['background_path'].

<?php
 
/* get background settings */

$opus_default_background_path = 'sites/default/themes/opus/images/backgrounds/pencildisplay.jpg';

if(
theme_get_setting('default_background')==1){
 
$opusbackground= base_path() . $opus_default_background_path;
} else {
 
$opusbackground= base_path() . theme_get_setting('background_path');
}
$vars['opusbackground'] = $opusbackground;
?>

FYI

dman - October 28, 2009 - 20:36

<?php
  $form
['#submit'][] = 'campaign_settings_submit';
?>

Is an array of submit actions. It doesn't replace or remove any other functionality, it does this as well.
Form API is nice like that.

Processing the form submission in the same function as building the form can work, and is the way I've seen many many old one-file PHP scripts structured. In some ways it's more concise if you are coding more stream-of-consciousness than architectured. But that was last century.

In Drupal we have Form API that allows us to separate the structure from the process, and allow other Drupal features to interact with it if needed. We can automate form submissions with drupal_execute and other magic. Having those two phases separate - building the form & submitting the form - is pretty sensible.

You can do it the old way if you want. It's just not the Drupal way.

I do see what you mean about system_theme_settings(). That's odd for it to be there. Not sure whether there is a reason or if it's left over from pre-FAPI days. It's manipulating $_POST directly too. That can't be right..

.dan.

Thanks!

yakker - November 23, 2009 - 22:40

Dan,

Thanks for the response - I'm sorry it has taken so long to see it here.

In any case, I appreciate you taking the time to explain - would really love to get my head around doing things the Drupal Way. I don't have a foundation in programming (making me a bit of a liability sometimes), so the information about the context and overall approach really helps.

Cheers!
Chris

Initializing the default values question

artwork - April 19, 2009 - 09:03

I have some question about this.
1. What is $theme_key return in doc it said active theme. If it just a theme why need to replace it in variable get

global $theme_key;
  // Save default theme settings.
  variable_set(
    str_replace('/', '_', 'theme_'. $theme_key .'_settings'),
    array_merge($defaults, $settings)
  );

2. What is these lines of codes doing ?
  // Don't save the toggle_node_info_ variables.
  if (module_exists('node')) {
    foreach (node_get_types() as $type => $name) {
      unset($settings['toggle_node_info_' . $type]);
    }
  }

3. What is this do?
  // Force refresh of Drupal internals.
  theme_get_setting('', TRUE);
}

Thank you,
Sarun

array_merge() statement appears backwards

erikwebb - July 9, 2009 - 15:23

Assuming you want your defaults to override the Drupal defaults, isn't the variable_set() function wrong?

The example uses this -

  // Save default theme settings.
  variable_set(
    str_replace('/', '_', 'theme_'. $theme_key .'_settings'),
    array_merge($defaults, $settings)
  );

According to PHP's manual for array_merge(),

If the input arrays have the same string keys, then the later value for that key will overwrite the previous one.

Therefore, you need your $defaults array to appear second, and double-checking that $settings is an actual array. I did this instead -

  // Get default theme settings.
  if (!is_array($settings = theme_get_settings($theme_key))) $settings = array();
  // Save default theme settings.
  variable_set(
    str_replace('/', '_', 'theme_'. $theme_key .'_settings'),
    array_merge($settings, $defaults)
  );

Erik

The original is correct.

seanr - October 9, 2009 - 19:53

The original is correct. Yours would overwrite the saved settings with the defaults, making it impossible for people to change the settings.

Necessary to initialise default values?

AndyF - August 24, 2009 - 13:34

Since we can’t guarantee that a user will ever go to the admin/build/themes/settings/themeName page, we have to ensure that the default values for our custom settings get initialized.

[snip]

Add the following code near the top of your template.php file

I'm making my first theme as a subtheme of Zen. Looking at Zen's own template.php (zen/zen/template.php) there's no initialisation, just lots of uses of theme_get_setting. So I'm wondering if maybe it's no longer necessary to initialise vars before use... or maybe the initialisation's hiding somewhere else...

A.

It just depends on whether

binford2k - November 23, 2009 - 09:22

It just depends on whether your code can handle null values if the variable isn't initialized.

For example, I use if($siteID = theme_get_setting('wsuidentity_site_id')) {...} in one of my themes. If it's set then it does something, if not, it just goes happily along its way.

 
 

Drupal is a registered trademark of Dries Buytaert.