Index: nodetypetheme.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/nodetypetheme/nodetypetheme.admin.inc,v
retrieving revision 1.1
diff -u -r1.1 nodetypetheme.admin.inc
--- nodetypetheme.admin.inc	5 Jun 2009 14:42:19 -0000	1.1
+++ nodetypetheme.admin.inc	13 Sep 2010 11:49:49 -0000
@@ -9,27 +9,46 @@
 {
   $form = array();
   $themes = array(NODETYPETHEME_DEFAULT => '');
-  $themesinfo = list_themes();
-  $types = node_get_types('names');
-  
-  while($themeinfo = array_shift($themesinfo))
-  {
-  	$themes[$themeinfo->name] = $themeinfo->info['name'];
-  }
-  unset($themesinfo);
+  $themes += nodetypetheme_get_enabled_themes();
   asort($themes);
   $themes[NODETYPETHEME_DEFAULT] = t('System default');
-  
+
+  $types = node_get_types('names');
   foreach($types as $type => $name)
   {
-    $form['nodetypetheme_' . $type] = array(
+    $form['content_type'][$type]['nodetypetheme_' . $type] = array(
       '#title' => $name,
       '#type' => 'select',
       '#default_value' => nodetypetheme_get_theme($type),
       '#options' => $themes,
     );
+    $form['content_type'][$type]['nodetypetheme_use_on_edit_page_' . $type] = array(
+      '#title' => t('!type - use custom theme on edit page', array('!type' => $name)),
+      '#type' => 'checkbox',
+      '#default_value' => variable_get('nodetypetheme_use_on_edit_page_' . $type, FALSE),
+    );
   }
-  
-  return system_settings_form($form);
+
+  $form = system_settings_form($form);
+  unset($form['#theme']);
+
+  return $form;
 }
 
+/**
+ * List the themes which are currently enabled.
+ * 
+ * @return Array
+ * An array of theme names.
+ */
+function nodetypetheme_get_enabled_themes()
+{
+  $enabled_themes = array();
+  $themes = list_themes();
+  foreach($themes as $theme) {
+    if($theme->status) {
+      $enabled_themes[$theme->name] = $theme->info['name'];
+    }
+  }
+  return $enabled_themes;
+}
\ No newline at end of file
Index: nodetypetheme.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/nodetypetheme/nodetypetheme.module,v
retrieving revision 1.1
diff -u -r1.1 nodetypetheme.module
--- nodetypetheme.module	5 Jun 2009 14:42:19 -0000	1.1
+++ nodetypetheme.module	13 Sep 2010 11:49:49 -0000
@@ -3,6 +3,18 @@
 define('NODETYPETHEME_DEFAULT', 'default');
 
 /**
+ * Implementation of hook_enable
+ */
+function nodetypetheme_enable()
+{
+  // the module should always be weighted heavier than system
+  // (to ensure that system's "Use administration theme for content editing"
+  // setting can be overridden).
+  $system_weight = db_result(db_query("SELECT weight FROM system WHERE name='system' AND type='module'"));
+  db_query("UPDATE {system} SET weight=%d WHERE name='nodetypetheme' AND type='module'", ++$system_weight);
+}
+
+/**
  * Returns the theme specified for a node type, using variable_get().
  *
  * @param $type Node type
@@ -21,7 +33,14 @@
   $output = '';
   switch ($path) {
     case "admin/help#nodetypetheme":
-      $output .= '<p>' . t("Allows administrators to set themes based on node types.") . '</p>';
+      $output .= '<p>' . t("Administrators can configure nodes to display with a different theme, based on node-type.") . '</p>';
+      break;
+
+    case 'admin/settings/nodetypetheme':
+      $output .= '<p>' . t('A custom-theme can be chosen per node-type.') . '<br />';
+      $output .= t('The theme will apply to all <em>node/nnn</em> pages and their local-tasks, excluding node-edit.') . '<br />';
+      $output .= t('The custom theme will also be applied to <em>node/add</em> and <em>node/nnn/edit</em> pages if "Use custom theme for node-edit pages" is checked.') . '</p>';
+      $output .= '<p>' . t('Only themes which are enabled are available to select.  You can enable themes on the !theme_settings.', array('!theme_settings' => l('theme settings page', 'admin/build/themes'))) . '</p>';
       break;
   }
   return $output;
@@ -32,12 +51,35 @@
  */
 function nodetypetheme_init()
 {
-  if((variable_get('site_offline', 0) == 1 && !user_access('administer site configuration')) || arg(0) != 'node' || !is_numeric($nid = arg(1)) || arg(2)) return;
-  $node = node_load($nid);
-  $theme = nodetypetheme_get_theme($node->type);
-  if($theme == NODETYPETHEME_DEFAULT) return;
-  global $custom_theme;
-  $custom_theme = $theme;
+  /**
+   * Override the theme on:
+   * - node/nnn
+   * - node/nnn/xxx
+   * - node/nnn/edit <-- where "nodetypetheme_use_on_edit_page_" is set
+   * / node/add/foo  <-- where "nodetypetheme_use_on_edit_page_" is set
+   */
+  // check the node-type for node/add pages.
+  if(arg(0) == 'node' && arg(1) == 'add' && arg(2)) {
+    $type = str_replace('-', '_', arg(2));
+  } elseif($node = menu_get_object()) {
+    $type = $node->type;
+  }
+
+  // we've discovered a node-type:
+  if(isset($type)) {
+    $node_theme = nodetypetheme_get_theme($type);
+    if($node_theme != NODETYPETHEME_DEFAULT)
+    {
+      // use the custom theme on all 'view' pages,
+      // use the custom theme on the 'edit' page only if the override-flag is set.
+      $node_edit_page = (arg(1) == 'add' || arg(2) == 'edit');
+      if( (!$node_edit_page) || ($node_edit_page && variable_get('nodetypetheme_use_on_edit_page_' . $type, FALSE)))
+      {
+        global $custom_theme;
+        $custom_theme = $node_theme;
+      }
+    }
+  }
 }
 
 /**
@@ -66,3 +108,50 @@
   return array('administer node type theme');
 }
 
+/**
+ * Implementation of hook_theme
+ */
+function nodetypetheme_theme($existing, $type, $theme, $path){
+  if($type == 'module') {
+    return array(
+      'nodetypetheme_form' => array(
+        'arguments' => array('form' => array()),
+      )
+    );
+  }
+}
+
+/**
+ * Theme implementation for the nodetypetheme admin-form.
+ */
+function theme_nodetypetheme_form(&$form) {
+  
+  $headers = array(
+    t('Content-type'),
+    t('Custom theme'),
+    t('Use custom theme for node-edit pages'),
+  );
+
+  $rows = array();
+  foreach(element_children($form['content_type']) as $type)
+  {
+    $fields = $form['content_type'][$type];
+    // Use the table-headers instead of form field-titles.
+    unset($form['content_type'][$type]['nodetypetheme_' . $type]['#title'],
+          $form['content_type'][$type]['nodetypetheme_use_on_edit_page_' . $type]['#title']
+          );
+ 
+    $row = array(
+      $fields['nodetypetheme_' . $type]['#title'],
+      drupal_render($form['content_type'][$type]['nodetypetheme_' . $type]),
+      drupal_render($form['content_type'][$type]['nodetypetheme_use_on_edit_page_' . $type]),
+    );
+    $rows[] = $row;
+  }
+
+  $output = '';
+  $output .= theme('table', $headers, $rows);
+  $output .= drupal_render($form);
+
+  return $output;
+}
Index: nodetypetheme.test
===================================================================
RCS file: nodetypetheme.test
diff -N nodetypetheme.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ nodetypetheme.test	13 Sep 2010 11:49:50 -0000
@@ -0,0 +1,463 @@
+<?php
+// $Id$
+
+class NodeTypeThemeTestCase extends DrupalWebTestCase {
+
+  protected $pageNode;
+  protected $storyNode;
+  protected $admin_user;
+
+
+  /**
+   * getInfo() returns properties that are displayed in the test selection form.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'NodeTypeTheme',
+      'description' => 'Ensure that the nodetypetheme module can be operated and changes the theme correctly.',
+      'group' => 'NodeTypeTheme',
+    );
+  }
+
+  /**
+   * setUp() - run before every test:
+   * > create an admin user
+   * > add page + story nodes
+   * > enable the requisite themes
+   * > configure node-type-theme
+   */
+  public function setUp() {
+    // Enable any modules required for the test.
+    parent::setUp('nodetypetheme');
+
+    // create a user who can administer the site, and the nodetypetheme module.
+    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer nodes', 'administer node type theme'));
+    $this->admin_user = $admin_user;
+    $this->drupalLogin($admin_user);
+
+    // ensure Garland, Marvin and Minnelli are enabled.
+    $edit = array();
+    $edit['status[garland]']  = TRUE;
+    $edit['status[marvin]']   = TRUE;
+    $edit['status[minnelli]'] = TRUE;
+    $this->drupalPost('admin/build/themes', $edit, t('Save configuration'));
+
+    // configure default nodetypetheme behaviour
+    $edit = array(
+      'nodetypetheme_page'  => 'default',
+      'nodetypetheme_story' => 'marvin',
+      'nodetypetheme_use_on_edit_page_page'  => FALSE,
+      'nodetypetheme_use_on_edit_page_story' => FALSE,
+    );
+    $this->drupalPost('admin/settings/nodetypetheme', $edit, t('Save configuration'));
+
+    // create a page-node
+    $this->pageNode = $this->drupalCreateNode(array(
+      'title' => 'Page node',
+      'type' => 'page',
+    ));
+
+    // create a story-node
+    $this->storyNode = $this->drupalCreateNode(array(
+      'title' => 'Story node',
+      'type' => 'story',
+    ));
+
+  }
+
+  protected function tearDown() {
+    parent::tearDown();
+  }
+
+  /**
+   * Test that someone with the permission to administer node-type-theme has
+   * access to the admin page.
+   */
+  protected function TestAdminPagePermissions() {
+    // Logout admin
+    $this->drupalLogout();
+
+    // check that Anon user has no access
+    $this->drupalGet('admin/settings/nodetypetheme');
+    $this->assertRaw('<h2>Access denied</h2>');
+
+
+    // Create user with access to the admin form.
+    $user = $this->drupalCreateUser(array('administer node type theme'));
+    $this->drupalLogin($user);
+
+    // check that Admin user has access
+    $this->drupalGet('admin/settings/nodetypetheme');
+    $this->assertRaw('<h2>Node type theme</h2>');
+    $this->drupalLogout();
+  }
+
+  /**
+   * Test that the node-type-theme admin page displays Enabled-themes only.
+   */
+  protected function TestAdminPageAllowsEnabledThemes() {
+    $this->drupalGet('admin/settings/nodetypetheme');
+
+    // the select dropdown should have 4 options: default, garland, marvin, minnelli
+    $elements = $this->xpath('//select[@id="edit-nodetypetheme-page"]/option');
+    $this->assertEqual(count($elements), 4);
+  }
+
+
+  /**
+   * Test scenario 1:
+   * - Site theme is Garland (default)
+   * - Admin theme is default
+   * - Node-type-theme settings:
+   *   > PAGE  nodes display as System-default
+   *   > STORY nodes display as Marvin
+   *   > PAGE  nodes "Use custom theme for node-edit pages" not set
+   *   > STORY nodes "Use custom theme for node-edit pages" not set
+   *
+   * ASSERTIONS:
+   * > (page)  node/nnn       : uses Garland
+   * > (page)  node/nnn/edit  : uses Garland
+   * >         node/add/page  : uses Garland
+   * > (story) node/nnn       : uses Marvin
+   * > (story) node/nnn/edit  : uses Garland
+   * >         node/add/story : uses Garland
+   *
+   */
+  protected function testScenario1() {
+
+    $this->drupalGet("node/{$this->pageNode->nid}");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/{$this->pageNode->nid}/edit");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/add/page");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/{$this->storyNode->nid}");
+    $this->assertTheme('marvin');
+
+    $this->drupalGet("node/{$this->storyNode->nid}/edit");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/add/story");
+    $this->assertTheme('garland');
+  }
+
+
+  /**
+   * Test scenario 2:
+   * - Site theme is Garland (default)
+   * - Admin theme is Minelli
+   * - "Use administration theme for content editing" is not set
+   * - Node-type-theme settings:
+   *   > PAGE  nodes display as System-default
+   *   > STORY nodes display as Marvin
+   *   > PAGE  nodes "Use custom theme for node-edit pages" not set
+   *   > STORY nodes "Use custom theme for node-edit pages" not set
+   *
+   * ASSERTIONS:
+   * > (page)  node/nnn       : uses Garland
+   * > (page)  node/nnn/edit  : uses Garland
+   * >         node/add/page  : uses Garland
+   * > (story) node/nnn       : uses Marvin
+   * > (story) node/nnn/edit  : uses Garland
+   * >         node/add/story : uses Garland
+   *
+   */
+  protected function testScenario2() {
+
+    // Configure admin theme to use Minnelli
+    $edit = array(
+      'admin_theme'  => 'minnelli',
+      'node_admin_theme' => FALSE,
+    );
+    $this->drupalPost('admin/settings/admin', $edit, t('Save configuration'));
+
+
+    $this->drupalGet("node/{$this->pageNode->nid}");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/{$this->pageNode->nid}/edit");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/add/page");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/{$this->storyNode->nid}");
+    $this->assertTheme('marvin');
+
+    $this->drupalGet("node/{$this->storyNode->nid}/edit");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/add/story");
+    $this->assertTheme('garland');
+  }
+
+  /**
+   * Test scenario 3:
+   * - Site theme is Garland (default)
+   * - Admin theme is Minelli
+   * - "Use administration theme for content editing" is set
+   * - Node-type-theme settings:
+   *   > PAGE  nodes display as System-default
+   *   > STORY nodes display as Marvin
+   *   > PAGE  nodes "Use custom theme for node-edit pages" not set
+   *   > STORY nodes "Use custom theme for node-edit pages" not set
+   *
+   * ASSERTIONS:
+   * > (page)  node/nnn       : uses Garland
+   * > (page)  node/nnn/edit  : uses Minelli
+   * >         node/add/page  : uses Minelli
+   * > (story) node/nnn       : uses Marvin
+   * > (story) node/nnn/edit  : uses Minelli
+   * >         node/add/story : uses Minelli
+   *
+   */
+  protected function testScenario3() {
+
+    // Configure admin theme to use Minnelli
+    $edit = array(
+      'admin_theme'  => 'minnelli',
+      'node_admin_theme' => TRUE,
+    );
+    $this->drupalPost('admin/settings/admin', $edit, t('Save configuration'));
+
+
+    $this->drupalGet("node/{$this->pageNode->nid}");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/{$this->pageNode->nid}/edit");
+    $this->assertTheme('minnelli');
+
+    $this->drupalGet("node/add/page");
+    $this->assertTheme('minnelli');
+
+    $this->drupalGet("node/{$this->storyNode->nid}");
+    $this->assertTheme('marvin');
+
+    $this->drupalGet("node/{$this->storyNode->nid}/edit");
+    $this->assertTheme('minnelli');
+
+    $this->drupalGet("node/add/story");
+    $this->assertTheme('minnelli');
+  }
+
+  /**
+   * Test scenario 4:
+   * - Site theme is Garland (default)
+   * - Admin theme is default
+   * - Node-type-theme settings:
+   *   > PAGE  nodes display as System-default
+   *   > STORY nodes display as Marvin
+   *   > PAGE  nodes "Use custom theme for node-edit pages" not set
+   *   > STORY nodes "Use custom theme for node-edit pages" is set
+   *
+   * ASSERTIONS:
+   * > (page)  node/nnn       : uses Garland
+   * > (page)  node/nnn/edit  : uses Garland
+   * >         node/add/page  : uses Garland
+   * > (story) node/nnn       : uses Marvin
+   * > (story) node/nnn/edit  : uses Marvin
+   * >         node/add/story : uses Marvin
+   *
+   */
+  protected function testScenario4() {
+
+    // configure nodetypetheme behaviour
+    $edit = array(
+      'nodetypetheme_page'  => 'default',
+      'nodetypetheme_story' => 'marvin',
+      'nodetypetheme_use_on_edit_page_page'  => FALSE,
+      'nodetypetheme_use_on_edit_page_story' => TRUE,
+    );
+    $this->drupalPost('admin/settings/nodetypetheme', $edit, t('Save configuration'));
+
+
+    $this->drupalGet("node/{$this->pageNode->nid}");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/{$this->pageNode->nid}/edit");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/add/page");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/{$this->storyNode->nid}");
+    $this->assertTheme('marvin');
+
+    $this->drupalGet("node/{$this->storyNode->nid}/edit");
+    $this->assertTheme('marvin');
+
+    $this->drupalGet("node/add/story");
+    $this->assertTheme('marvin');
+  }
+
+  /**
+   * Test scenario 5:
+   * - Site theme is Garland (default)
+   * - Admin theme is Minelli
+   * - "Use administration theme for content editing" is not set
+   * - Node-type-theme settings:
+   *   > PAGE  nodes display as System-default
+   *   > STORY nodes display as Marvin
+   *   > PAGE  nodes "Use custom theme for node-edit pages" not set
+   *   > STORY nodes "Use custom theme for node-edit pages" is set
+   *
+   * ASSERTIONS:
+   * > (page)  node/nnn       : uses Garland
+   * > (page)  node/nnn/edit  : uses Garland
+   * >         node/add/page  : uses Garland
+   * > (story) node/nnn       : uses Marvin
+   * > (story) node/nnn/edit  : uses Marvin
+   * >         node/add/story : uses Marvin
+   *
+   */
+  protected function testScenario5() {
+
+    // Configure admin theme to use Minnelli
+    $edit = array(
+      'admin_theme'  => 'minnelli',
+      'node_admin_theme' => FALSE,
+    );
+    $this->drupalPost('admin/settings/admin', $edit, t('Save configuration'));
+
+    // configure nodetypetheme behaviour
+    $edit = array(
+      'nodetypetheme_page'  => 'default',
+      'nodetypetheme_story' => 'marvin',
+      'nodetypetheme_use_on_edit_page_page'  => FALSE,
+      'nodetypetheme_use_on_edit_page_story' => TRUE,
+    );
+    $this->drupalPost('admin/settings/nodetypetheme', $edit, t('Save configuration'));
+
+    $this->drupalGet("node/{$this->pageNode->nid}");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/{$this->pageNode->nid}/edit");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/add/page");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/{$this->storyNode->nid}");
+    $this->assertTheme('marvin');
+
+    $this->drupalGet("node/{$this->storyNode->nid}/edit");
+    $this->assertTheme('marvin');
+
+    $this->drupalGet("node/add/story");
+    $this->assertTheme('marvin');
+  }
+
+
+  /**
+   * Test scenario 6:
+   * - Site theme is Garland (default)
+   * - Admin theme is Minelli
+   * - "Use administration theme for content editing" is set
+   * - Node-type-theme settings:
+   *   > PAGE  nodes display as System-default
+   *   > STORY nodes display as Marvin
+   *   > PAGE  nodes "Use custom theme for node-edit pages" not set
+   *   > STORY nodes "Use custom theme for node-edit pages" is set
+   *
+   * ASSERTIONS:
+   * > (page)  node/nnn       : uses Garland
+   * > (page)  node/nnn/edit  : uses Minelli
+   * >         node/add/page  : uses Minelli
+   * > (story) node/nnn       : uses Marvin
+   * > (story) node/nnn/edit  : uses Marvin
+   * >         node/add/story : uses Marvin
+   *
+   */
+  protected function testScenario6() {
+
+    // Configure admin theme to use Minnelli
+    $edit = array(
+      'admin_theme'  => 'minnelli',
+      'node_admin_theme' => TRUE,
+    );
+    $this->drupalPost('admin/settings/admin', $edit, t('Save configuration'));
+
+    // configure nodetypetheme behaviour
+    $edit = array(
+      'nodetypetheme_page'  => 'default',
+      'nodetypetheme_story' => 'marvin',
+      'nodetypetheme_use_on_edit_page_page'  => FALSE,
+      'nodetypetheme_use_on_edit_page_story' => TRUE,
+    );
+    $this->drupalPost('admin/settings/nodetypetheme', $edit, t('Save configuration'));
+
+
+    $this->drupalGet("node/{$this->pageNode->nid}");
+    $this->assertTheme('garland');
+
+    $this->drupalGet("node/{$this->pageNode->nid}/edit");
+    $this->assertTheme('minnelli');
+
+    $this->drupalGet("node/add/page");
+    $this->assertTheme('minnelli');
+
+    $this->drupalGet("node/{$this->storyNode->nid}");
+    $this->assertTheme('marvin');
+
+    $this->drupalGet("node/{$this->storyNode->nid}/edit");
+    $this->assertTheme('marvin');
+
+    $this->drupalGet("node/add/story");
+    $this->assertTheme('marvin');
+  }
+
+
+
+
+  
+  /**
+   * Assert that the theme displayed in the last request matches the theme
+   * parameter.
+   * Core themes supported by this feature:
+   * - Bluemarine
+   * - Chameleon
+   * - Garland
+   * - Marvin
+   * - Minnelli
+   * - Pushbutton
+   *
+   * @param String $themeName
+   */
+  protected function assertTheme($themeName) {
+
+    switch($themeName) {
+      case 'bluemarine':
+        // <td id="logo"><a href="/" title="Home"><img src="/themes/bluemarine/logo.png" alt="Home" />
+        $elements = $this->xpath('//td[@id="logo"]/a/img[@src="/themes/bluemarine/logo.png"]');
+        return $this->assertTrue(count($elements), "Theme is Bluemarine.");
+
+      case 'chameleon':
+        // <div id="header"><a href="/" title="Home"><img src="/themes/chameleon/logo.png" alt="Home" /></a>
+        $elements = $this->xpath('//div[@id="header"]/a/img[@src="/themes/chameleon/logo.png"]');
+        return $this->assertTrue(count($elements), "Theme is Chameleon.");
+
+      case 'garland':
+        // <h1><a href="/" title="testbed"><img src="/themes/garland/logo.png" alt="testbed" id="logo" />
+        $elements = $this->xpath('//h1/a/img[@src="/themes/garland/logo.png"]');
+        return $this->assertTrue(count($elements), "Theme is Garland.");
+
+      case 'marvin':
+        // <div id="header"><a href="/" title="Home"><img src="/themes/chameleon/marvin/logo.png" alt="Home" /></a>
+        $elements = $this->xpath('//div[@id="header"]/a/img[@src="/themes/chameleon/marvin/logo.png"]');
+        return $this->assertTrue(count($elements), "Theme is Marvin.");
+
+      case 'minnelli':
+        // <h1><a href="/" title="testbed"><img src="/themes/garland/minnelli/logo.png" alt="testbed" id="logo" /><span>testbed</span></a></h1>        </div>
+        $elements = $this->xpath('//h1/a/img[@src="/themes/garland/minnelli/logo.png"]');
+        return $this->assertTrue(count($elements), "Theme is Minnelli.");
+
+      case 'pushbutton':
+        // <td id="home" width="10%"><a href="/" title="Home"><img src="/themes/pushbutton/logo.png" alt="Home" border="0" /></a>
+        $elements = $this->xpath('//td[@id="home"]/a/img[@src="/themes/pushbutton/logo.png"]');
+        return $this->assertTrue(count($elements), "Theme is Pushbutton.");
+    }
+  }
+}
