Short description

  1. Change the funky THEME_settings($settings) to a standard FAPI form alter THEME_form_system_theme_settings_alter(&$form, $form_state).
  2. The default value for any variable on the theme_settings form can be initialized by placing a line in the theme’s .info file:
    settings[var_name] = value
  3. Re-work theme_get_setting() so that you can read the setting for any theme and not just for the current theme. Also, make sure it reads the default values from the .info file.
  4. Remove theme_get_settings(). It blows. And we hardly use it.

Long description:

In D6, the way to add custom theme settings to a theme’s admin/appearance/settings/THEME form is to use a theme_settings($settings) function in its theme-settings.php file. And let that function return an array of form fields to be included in a specially-designated fieldset. See http://drupal.org/node/177868 for the full gory details. And they are gory! And painful. Take a look at the nightmare you have to do initialize the default values for that form, too. Ugh!

While d7 has already added a &$form parameter to that function, the whole hook really needs to be re-worked. The best solution is simply to let themes do a full form_alter on it. So lets convert the funky THEME_settings($settings) to a standard THEME_form_system_theme_settings_alter(&$form, $form_state).

Now what about the default values for those custom settings?

While a module can set default values for its variables by using the second paramter of variable_get($name, 'default val'), let's not emulate that for themes — its too annoying to have to have the default value repeated everywhere you want to get the settings value.

So, an easy way to set the default values for the custom theme settings would be to add settings[var_name] = value lines to the theme's .info file. And then to retrieve those values, it would make sense to use the existing function, theme_get_setting($setting_name)

Unfortunately, while looking at adding that "default value" functionality to theme_get_settting, we can see a snag…

theme_get_settings($key) and theme_get_setting($setting_name) are not well-written in D6. From their appearances it looks as if theme_get_settings() would retrieve all of the settings for a theme and theme_get_setting() could be used to retrieve just one. Unfortunately, if you look at the implementation details, you'll see that some settings are generated and stored inside theme_get_setting() and can't be retrieved by theme_get_settings(); specifically, the logo and favicon settings. wtf? Worse, while theme_get_settings() can retrieve settings for any theme, theme_get_setting() can only be used to retrieve a setting for the currently active theme; you can't use it to retrieve a setting from any other theme.

Fortunately for us, theme_get_settings() is only used inside theme_get_setting() and inside system_theme_settings(). So I propose we combine the functionality of theme_get_settings and theme_get_setting inside a re-worked theme_get_setting and get rid of theme_get_settings. And then, our now-much-saner theme_get_setting() can check for settings written in the .info file.

[edit: changed hook_form_theme_settings_alter() to the correct hook_form_system_theme_settings_alter() per the comment I made in #20 below.]

Comments

johnalbin’s picture

Status: Active » Needs review
StatusFileSize
new832 bytes
new19.22 KB

Here are some notes I wrote as I implemented this patch. It should explain my rationale for making various changes.

  1. In D6, the custom theme settings were wrapped in a fieldset that said “Theme-specific settings — These settings only exist for the THEMENAME theme and all sub-themes based on it.”

    Aren’t we already on the theme’s settings page (admin/appearance/settings/THEMENAME)?

    Plus, the top of the page already says “These options control the display settings for the THEMENAME theme. When your site is displayed using this theme, these settings will be used. By clicking "Reset to defaults," you can choose to use the global settings for this theme.”

    Which means the whole fieldset intro text is… well… completely redundant. So I’ve removed the entire fieldset. Themes are no longer restricted to where in the form they can add stuff.

  2. theme_get_setting($setting_name, $refresh) still has a $refresh parameter for its static storage. Ugh. I've converted this to $cache = &drupal_static(__FUNCTION__, array()); like we use in many other places in D7.
  3. The str_replace('/', '_', $key) in system_theme_settings is a hold-over from Drupal 5 when the "key" for a sub-theme was different. For example, Minnelli's key was "garland/minnelli" which is why we needed to replace the "/" in Drupal 5. We no longer need that, so I've removed the 3 occurrences of it.
  4. The following code that was previously in theme_get_setting() was completely misplaced.
        // Only offer search box if search.module is enabled.
        if (!defined('MAINTENANCE_MODE') && module_exists('search') && user_access('search content')) {
          $cache[$theme]['toggle_search'] = 1;
        }
    

    The check for maintenance mode is unnecessary since the search box is set to '' within template_preprocess_maintenance_page(). The check for the search module is already performed in system_theme_settings where the search toggle is displayed. [edit: but actually, we still need to check for the search module before the search box is displayed.] And the user_access check shouldn't be done in a function whose purpose is to return what the setting is (not who has perm for that setting), so I've moved the user access check into template_preprocess_page().

  5. Why are 3 of the toggle_* variable defaulting to OFF inside D6's theme_get_settings? toggle_slogan, toggle_node_user_picture, and toggle_comment_user_picture. Especially since we are manually turning 2 of them back on inside the default.install. Lets just turn them all ON by default.

I've attached two sample themes which should make testing this functionality easier. They both implement the new .info "settings" values, the THEME_form_theme_settings_alter function, and call theme_get_setting().

johnalbin’s picture

Assigned: johnalbin » Unassigned

I'm hopping on a plane to Paris in four hours, but if anyone wants to take this patch and run with it, please do! I'll arrive in Paris on Monday evening and see where we are then.

senpai’s picture

Subscribing for review

sociotech’s picture

John,

I'll look at this patch today. Sounds promising!

sociotech’s picture

Applied the patch to HEAD and tested the following functionality with the included themes.

- Verified that theme settings appear for both themes.
- Verified that theme settings from the base theme are inherited by the sub-theme.
- Verified that default values are retrieved from the .info file.
- Verified that, once changed, base and sub-theme theme settings are independent of each other.

- Verified adding my own theme settings using the THEME_form_theme_settings_alter() function, with default values set in the .info file.
- Verified that my own theme settings were correctly inherited.

That's what I can say with my initial testing. I'll try to do some more testing tomorrow.

As far as I can tell at this point, it's looking good for D7 code freeze. This is a critical step forward for true theme inheritance. Great work!

joshmiller’s picture

StatusFileSize
new19.21 KB

Patch applied with fuzz. Attached is simply chasing HEAD. I will follow it up with a code review and testing review.

Josh

robloach’s picture

Subscribing.com/org.net

joshmiller’s picture

Status: Needs review » Needs work

User Interface Review

  • The following notices happen everywhere...
    • Notice: Undefined index: search_theme_form in drupal_retrieve_form()
    • Warning: call_user_func_array(): First argument is expected to be a valid callback, 'search_theme_form' was given in drupal_retrieve_form()
  • I think these notices are due to the form id not being passed correctly.
  • Also, when I installed and enabled, Minelli, Liza, and Cabaret -- both options for the themes were evident on the Cabaret theme configuration page, but not on the Liza configuration page. I don't think that is what is intended.
  • I love the themes default settings being set in theme.info

I did quickly look through the code... and found no coding style issues

Zarabadoo’s picture

I have grabbed a super fresh copy of HEAD and applied the patch posed in #6 and then installed. I did not get any notices or warnings that were mentioned above and things looked to be functioning perfectly.

As for my opinion, I like the way this works and is a definite improvement over 6's settings.

johnalbin’s picture

Status: Needs work » Needs review
StatusFileSize
new19.21 KB

Also, when I installed and enabled, Minelli, Liza, and Cabaret -- both options for the themes were evident on the Cabaret theme configuration page, but not on the Liza configuration page. I don't think that is what is intended.

Let me explain that now since I didn't do it before. Cabaret is a sub-theme of Liza. And each of those themes adds 1 checkbox to their custom settings form. So Liza shows just its own checkbox. And Cabaret correctly shows its base theme’s checkbox (from Liza) and its own checkbox.

The following notices happen everywhere...
Notice: Undefined index: search_theme_form in drupal_retrieve_form()

Oh! Looks like I was slightly wrong here: “The check for the search module is already performed in system_theme_settings where the search toggle is displayed.” The check for search module also needs to be done in template_preprocess_page(). Good catch!

Also, I found a small bug with the bit of code that was doing “allow theme_get_setting() to work without having to pass the theme name to it” In Caberet's theme-settings.php, I had $form['liza_example']['#default_value'] = theme_get_setting('liza_example'); but that should have been completely unnecessary since when configuring Caberet, liza's theme-settings.php setup should be using the default values for Caberet and not the default values for Liza.

Re-rolled with those 2 fixes.

johnalbin’s picture

To make this patch even easier to review, let me write the proposed upgrade docs here:

In Drupal 6, themes could add custom form elements to their “configure theme settings” page at admin/build/themes/settings/THEMENAME. Themes would need to create a theme-settings.php page in their theme directory and use a function with the following syntax:

/**
 * 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) { }

In Drupal 7, much more flexibility is given to themes to modify the entire theme settings form. In a theme’s theme-settings.php, themes should now use a THEMENAME_form_system_theme_settings_alter(&$form, $form_state) function. This gives the same power to themes that modules have if they use hook_form_system_theme_settings_alter(). See the “Forms API Quickstart Guide” and “Forms API Reference” on http://api.drupal.org/api/7, as well as the hook_form_FORM_ID_alter() docs to learn the full flexibility of Forms API. Note that themes can no longer use the phptemplate_ prefix to the function; you’ll need to use the actual name of your theme as the prefix.

Here’s an example if you had a “foo” theme and wanted to add a textfield whose default value was “blue bikeshed”:

function foo_form_system_theme_settings_alter(&$form, $form_state) {
  $form['caberet_example'] = array(
    '#type'          => 'textfield',
    '#title'         => t('Widget'),
    '#default_value' => theme_get_setting('foo_example'),
    '#description'   => t("Place this text in the widget spot on your site."),
  );
}

In order to set the default value for any form element you add, you’ll need to add a simple line to your .info file: settings[SETTING_NAME] = DEFAULT_VALUE. For our foo theme, you’d need to edit the foo.info file and this line:

settings[foo_example] = blue bikeshed

In any of your theme’s php files, you can retrieve the value the user set by calling:

$foo_example = theme_get_setting('foo_example');

[edit: changed hook_form_theme_settings_alter() to the correct hook_form_system_theme_settings_alter() per the comment I made in #20 below.]

joshmiller’s picture

I have reviewed this new patch and have found no more problems. JohnAlbin wanted RobLoach to review, but I think he is not around today, so I will try and find more themers to review... if they are happy then RTBC.

Josh

joshmiller’s picture

Talked to mortendk and zarabadoo (sp?) in the hall and mortendk loves the idea of a form_alter() for theme form. Default values being set in .info was also a high point. zarabadoo plans to review and possibly RTBC it.

Zarabadoo’s picture

Status: Needs review » Reviewed & tested by the community

I have looked at this and it all seems to be functioning properly without errors. As said before, I approve completely and will mark this as a reviewed and tested patch.

robloach’s picture

Sorry for missing you guys at the code sprint, Josh and John. My flight left a bit earlier then I had thought. I got a chance to take a look at the patch though, and have to say that it really helps out the themer's experience when getting theme-specific settings. RTBC++.

dries’s picture

Personally, I'm not a big fan of storing defaults in .info files. That seems to be a new feature? Why wouldn't we specify the defaults in _hook_alter() like we do elsewhere? If we want to aim for consistency, we should, IMO.

Also, should there be a theme.api.php where we keep such examples?

Status: Reviewed & tested by the community » Needs work

The last submitted patch failed testing.

sociotech’s picture

Dries,

I think being able to store theme defaults in .info files would be a big win for sub-themers. That way, a sub-theme could easily change the base theme defaults without having to add its own theme-settings.php file. It's a lot more accessible and flexible, and it should help encourage the creation of more themes in general.

And conceptually, .info already stores defaults for standard theme settings in the form of which Features to display (logo, name, slogan, etc.). The difference with this patch is that it would also include advanced (custom) theme settings as well. From a themer's point of view, it feels like a very "natural" place to put theme setting defaults for a particular theme or sub-theme.

This feature and the rest of this patch are important steps in cleaning up the theming/inheritance system for D7, so it can enable easier theme creation.

sociotech’s picture

Um, I'm assuming that this patch is making it into D7 before the end of the code slush. Is that a correct assumption? Is there anything else that needs to be done in order to get this committed?

johnalbin’s picture

Status: Needs work » Needs review
StatusFileSize
new19.09 KB

Here's a mostly-straight re-roll of #10 with the addition of a hook_form_system_theme_settings_alter docblock in system.api.php.

Also, the correct name of the hook is hook_form_system_theme_settings_alter, not hook_form_theme_settings_alter like I previously had. Not sure how I managed to bork that in the previous patch.

As for not storing default values in .info files, I have a few points:

  1. Dries, I don't understand what you mean by specifying variable “defaults in _hook_alter() like we do elsewhere”. Can you explain?
  2. I agree that .info files can get bloated, but they are also extremely easy to edit. Themers are embracing .info files because of this ease; .info bloat is not a concern for them.
  3. Many themes in D6 use Zen's example of storing default theme setting values in .info files. So there is precedence in contrib for this.
  4. #145164: DX: Use hook_variable_info to declare variables and defaults was trying to get rid of the awful variable_get('var_name', 'default_value') construct. Unfortunately, it went nowhere. So the only thing the theme layer could try to be "consistent with" is the variable_get('var_name', 'default_value'). So we could have theme_get_setting('var_name', 'default_value'); however…
  5. Theme default values are much more complex then a module's default variable values because of the base theme/sub-theme inheritance. There's the system default value, the base theme default value and the sub-theme default value. Module's only have the module default value (==system default value).

    If we were to use a base theme is doing something on behalf of a sub-theme, it would be much more efficient if the sub-theme only had to change the default value for that variable in one place (the .info file in this patch) and not have to override and or rebuild the entire theme function/preprocess/whatever in order to change the default variable value. That's super inefficient.

sun’s picture

Status: Needs review » Reviewed & tested by the community

Looks like there was some kind of confusion regarding theme settings variable defaults in .info files. Apparently, that's a hidden "feature" we already have since D6 or earlier, because everything in .info is taken over into the registry.

This patch seems to bring theme_get_setting() more in line with variable_get(), so it returns a default value. That is good for themers, and that will also be good for the final variable defaults API change we all wait for and hopefully see in D8.

johnalbin’s picture

StatusFileSize
new18.92 KB

Removed 3 lines of comments accidentally left in the hook_form_system_theme_settings_alter docblock after a copy and paste. Identical patch otherwise.

dries’s picture

Status: Reviewed & tested by the community » Fixed

I'm still not a fan of abusing info files for settings -- I warned against that trend 3 years ago but was assured it wouldn't happen.

Either way, given that it was already a hidden feature in Drupal 6, I decided to commit this patch.

dave reid’s picture

Is it really necessary that we document hook_form_system_theme_settings_alter()? We don't document any other hook_form_FORM_ID_alter() and it seems this would be more appropriate for the 'upgrading themes from D6 to D7' handbook.

gábor hojtsy’s picture

Status: Fixed » Needs work
Issue tags: +Needs documentation

Was not documented in the update guide.

johnalbin’s picture

Status: Needs work » Fixed
Issue tags: -Needs documentation

@Gábor: It has been now! :-)

@Dave: I'm working right now on a patch that moves the hook_form_system_theme_settings_alter() documentation from the @hooks group to the @themeable group and creates a includes/theme.api.php file. I've got a local api test setup to make sure everything is working properly. I'll post it to a new issue though. Thanks for pointing out that I missed Dries' request to put the docs in a new file!

robloach’s picture

c960657’s picture

Status: Fixed » Needs review
StatusFileSize
new2.4 KB

There is still a call to theme_get_settings() in search.test - I don't know how the test bot missed that.

dries’s picture

Somehow, the patch doesn't apply. Requesting retest.

Status: Needs review » Needs work

The last submitted patch failed testing.

sun’s picture

That's because I removed the 'toggle_search' setting, which is in the context of these lines.

sociotech’s picture

Now that theme_get_settings() is gone, what is the preferred method for getting all of a theme's settings in one call?

jarek foksa’s picture

Saving custom settings from admin panel does not work if form element is put inside fieldset. Am I doing something wrong here?

theme-settings.php:

function mytheme_form_system_theme_settings_alter(&$form, $form_state) {

  $form['sample_fieldset'] = array(
    '#title' => 'This is sample fieldset', 
    '#type' => 'fieldset', 
    '#tree' => TRUE,
  );

  $form['sample_fieldset']['sample_setting'] = array(
    '#title' => 'This is sample select form', 
    '#type' => 'select',
    '#default_value' => theme_get_setting('sample_setting'),
    '#options' => array(
      'option_1' => 'Option 1',
      'option_2' => 'Option 2',
    ),
  );

}

mytheme.info:

settings['sample_setting']   = 'option_2'
johnalbin’s picture

I answered sociotech's question in IRC. He asked because in D6, theme_get_settings() was required as part of a completely-hacky way to get the defaults merged with the saved settings in the db. This issue removes the need for that D6 hack, so theme_get_settings() is no longer needed.

@jarek: really? I've used fieldsets in D6 theme-settings.php; I don't see why they wouldn't work in D7.

@Dave Reid: Here's where I'm working on adding a theme.api.php file and moving hook_form_system_theme_settings_alter() out of the @hook documentation. #610408: Create theme.api.php to help consolidate theme-related docs.

jarek foksa’s picture

I just had to remove '#tree' => TRUE from fieldset definition. I don't know forms API very well yet, I'm guessing that #tree didn't make sense in this context anyway. So my issue probably was not related to reimplementation of theme_get_setting()

So far I have built quite big form with custom theme settings - works flawlessly.

c960657’s picture

Status: Needs work » Fixed

The last theme_get_settings() call was removed in #550742: Remove Search box from theme system, default to block system instead, so I am setting this back to “fixed”.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

johnalbin’s picture

Component: theme system » documentation
Status: Closed (fixed) » Active

Needs docs.

jhodgdon’s picture

Component: documentation » theme system
Status: Active » Needs work
Issue tags: +Needs documentation

What sort of documentation does this need -- can you be more specific?

Also, updating component etc. to how we indicate that a patch whose code has been fixed now needs documentation.

sociotech’s picture

StatusFileSize
new728 bytes

I've been converting themes to D7 and noticed that my base theme's settings weren't being inherited by sub-themes.

I also checked it out with a Garland sub-theme and found the same problem.

So, I traced it back to this line (1155) in theme.inc (HEAD 1.582) in the theme_get_setting() function which is supposed to get all the base themes, which are then used to loop through merging theme settings:

// Create a list which includes the current theme and all its base themes.
if (isset($theme_object->base_themes)) {

But the $theme_object doesn't have an entry for base_themes, just base_theme. So it fails the test, never gets the base themes, and the base themes are never checked for settings to inherit.

Following back to the list_themes() function in theme.inc that generates the $theme_object, it doesn't look like it ever generates the base_themes information.

I tried the following code to fix the problem in list_themes() and found that it allowed base theme settings to be inherited:

$theme->base_themes = system_find_base_themes(system_list('theme'), $theme->name);

Can anyone else replicate this behavior? Unless I'm missing something, this seems fairly major.

I've attached a patch for testing.

sociotech’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, list_themes_base_themes.patch, failed testing.

sociotech’s picture

Status: Needs work » Needs review
StatusFileSize
new986 bytes

New patch chasing HEAD (theme.inc 1.583) that uses internal theme list (modified to use theme names as keys) and should remove dependency on db causing Drupal install failure.

sociotech’s picture

johnalbin’s picture

Status: Needs review » Needs work

This is a symptom of a deeper problem. See #761608: Missing theme settings values because list_themes() has inconsistent theme object data.

So the patch in #43 is the wrong fix. I'm still working on figuring out the right fix over in that issue. :-)

sociotech’s picture

John,

Anything I can do to help out with this issue?

treksler’s picture

looking at the following code,

<?php
function foo_form_system_theme_settings_alter(&$form, $form_state) {
  $form['caberet_example'] = array(
    '#type'          => 'textfield',
    '#title'         => t('Widget'),
    '#default_value' => theme_get_setting('foo_example'),
    '#description'   => t("Place this text in the widget spot on your site."),
  );
}
?>

how exactly does theme_get_setting('foo example') here know which theme we are editting?
AFAICS, it does not.

It looks like no matter what theme you are editting, it will always save these settings in the current theme
Shouldn't theme settings files do

<?php
function foo_form_system_theme_settings_alter(&$form, $form_state, $theme) {
  $form['caberet_example'] = array(
    '#type'          => 'textfield',
    '#title'         => t('Widget'),
    '#default_value' => theme_get_setting('foo_example', $theme),
    '#description'   => t("Place this text in the widget spot on your site."),
  );
}
?>

and shouldn't this

      // Call theme-specific settings.
      $function = $theme . '_form_system_theme_settings_alter';
      if (function_exists($function)) {
        $function($form, $form_state);
      }

be this

      // Call theme-specific settings.
      $function = $theme . '_form_system_theme_settings_alter';
      if (function_exists($function)) {
        $function($form, $form_state, $theme);
      }

or perhaps more simply we could just have the theme settings files call

<?php
function foo_form_system_theme_settings_alter(&$form, $form_state) {
  $form['caberet_example'] = array(
    '#type'          => 'textfield',
    '#title'         => t('Widget'),
    '#default_value' => theme_get_setting('foo_example', 'foo'),
    '#description'   => t("Place this text in the widget spot on your site."),
  );
}
?>
treksler’s picture

also, should we not have also changed
$themes[$key]->prefix . '_engine_settings'
to
$themes[$key]->prefix . '_form_system_theme_settings_alter'

right now there is no way to update the themesettingsapi module because we can't tell whether we are dealing with phptemplate_engine_settings($settings) or phptemplate_engine_settings($form, $form_state)

it would be much clearer if we had phptemplate_engine_settings($settings) and phptemplate_form_system_theme_settings_alter($form, $form_state)

xjm’s picture

Tracking.

aquariumtap’s picture

re: #40, I think we've identified where the base_themes array is being lost over at #761608: Missing theme settings values because list_themes() has inconsistent theme object data.

dvessel’s picture

johnalbin’s picture

Status: Needs work » Fixed

Ok. There's a patch over in #761608: Missing theme settings values because list_themes() has inconsistent theme object data to work on the follow-up bug mentioned in comment #40 above. Needs reviews!

And Joon has another related issue over in #943212: hook_form_system_theme_settings_alter() vs. THEME_form_system_theme_settings_alter() name-clash

I think we can safely close this issue back up and follow-up in those other issues.

Status: Fixed » Closed (fixed)
Issue tags: -Needs documentation

Automatically closed -- issue fixed for 2 weeks with no activity.

Anonymous’s picture

Issue summary: View changes

changed hook_form_theme_settings_alter() to the correct hook_form_system_theme_settings_alter() per the comment I made in #20 below.

liquidcms’s picture

Issue summary: View changes

Unfortunate that with all these changes they missed allowing these theme variables being allowed to be set in the settings.php file.