diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 51e75d9c1c9bd5ff03fd41ff42f9432de225463b..377c9711e05118c622c73a7ff70b157344671e4e 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -975,6 +975,7 @@ function drupal_maintenance_theme() {
   foreach ($themes as $hook => $info) {
     if (!isset($info['file']) && !isset($info['function'])) {
       $themes[$hook]['function'] = 'theme_'. $hook;
+      $themes[$hook]['theme path'] = 'misc';
     }
   }
   _theme_set_registry($themes);
diff --git a/includes/form.inc b/includes/form.inc
index 5de5ded31e1908bb4ed02a13d98724ad6dd526db..dce3e22d47829ceb5e9211f631f1da2aadec40c5 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -1821,7 +1821,7 @@ function batch_set($batch_definition) {
 
 /**
  * Process the batch.
- * 
+ *
  * Unless the batch has been markes with 'progressive' = FALSE, the function
  * isses a drupal_goto and thus ends page execution.
  *
diff --git a/includes/menu.inc b/includes/menu.inc
index c2ec2eb8132f2d4a29a41521f97f23a135b30d13..1f1d1decedeec78d736b7429f6f82899150d3db5 100644
--- a/includes/menu.inc
+++ b/includes/menu.inc
@@ -1055,3 +1055,22 @@ function menu_get_item_by_mid($mid) {
   }
   return FALSE;
 }
+
+/**
+ * Returns the rendered local tasks. The default implementation renders
+ * them as tabs.
+ *
+ * @ingroup themeable
+ */
+function theme_menu_local_tasks() {
+  $output = '';
+
+  if ($primary = menu_primary_local_tasks()) {
+    $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
+  }
+  if ($secondary = menu_secondary_local_tasks()) {
+    $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
+  }
+
+  return $output;
+}
diff --git a/includes/theme.inc b/includes/theme.inc
index 593c2819ef1c3dee714bf1f00affe49872fa58c8..f8f762185fdf7246f68ae4b81dd34e5f7af02f97 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -27,7 +27,6 @@
 
 /**
  * Initialize the theme system by loading the theme.
- *
  */
 function init_theme() {
   global $theme, $user, $custom_theme, $theme_engine, $theme_key;
@@ -51,38 +50,100 @@ function init_theme() {
   // Store the identifier for retrieving theme settings with.
   $theme_key = $theme;
 
-  // If we're using a style, load its appropriate theme,
-  // which is stored in the style's description field.
-  // Also add the stylesheet using drupal_add_css().
-  // Otherwise, load the theme.
-  if (strpos($themes[$theme]->filename, '.css')) {
-    // File is a style; loads its CSS.
-    // Set theme to its template/theme
-    drupal_add_css($themes[$theme]->filename, 'theme');
-    $theme = basename(dirname($themes[$theme]->owner));
+  // Find all our ancestor themes and put them in an array.
+  $base_theme = array();
+  $ancestor = $theme;
+  while ($ancestor && isset($themes[$ancestor]->base_theme)) {
+    if (isset($themes[$themes[$ancestor]->base_theme])) {
+      $base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme];
+      // stop if this is final ancestor.
+      if (!isset($new_base_theme->base_theme)) {
+        break;
+      }
+      $ancestor = $new_base_theme->base_theme;
+    }
+    // stop if ancestor didn't exist.
+    else {
+      break;
+    }
+  }
+  _init_theme($themes[$theme], array_reverse($base_theme));
+}
+
+/**
+ * Initialize the theme system given already loaded information. This
+ * function is useful to initialize a theme when no database is present.
+ *
+ * @param $theme
+ *   An object with the following information:
+ *     filename
+ *       The .info file for this theme. The 'path' to
+ *       the theme will be in this file's directory. (Required)
+ *     owner
+ *       The path to the .theme file or the .engine file to load for
+ *       the theme. (Required)
+ *     stylesheet
+ *       The primary stylesheet for the theme. (Optional)
+ *     engine
+ *       The name of theme engine to use. (Optional)
+ * @param $base_theme
+ *    An optional array of objects that represent the 'base theme' if the
+ *    theme is meant to be derivative of another theme. It requires
+ *    the same information as the $theme object. It should be in
+ *    'oldest first' order, meaning the top level of the chain will
+ *    be first.
+ */
+function _init_theme($theme, $base_theme = array()) {
+  global $theme_info, $base_theme_info, $theme_engine, $theme_path;
+  $theme_info = $theme;
+  $base_theme_info = $base_theme;
+
+  $theme_path = dirname($theme->filename);
+
+  // Add the default stylesheet
+  if (!empty($theme->stylesheet)) {
+    drupal_add_css($theme->stylesheet, 'theme');
   }
   else {
-    // File is a template/theme
-    // Load its CSS, if it exists
-    if (file_exists($stylesheet = dirname($themes[$theme]->filename) .'/style.css')) {
-      drupal_add_css($stylesheet, 'theme');
+    // If we don't have a stylesheet of our own, look for the first
+    // base to have one and use its.
+    foreach ($base_theme as $base) {
+      if (!empty($base->stylesheet)) {
+        drupal_add_css($base->stylesheet, 'theme');
+        break;
+      }
     }
   }
 
-  if (strpos($themes[$theme]->filename, '.theme')) {
-   // file is a theme; include it
-   include_once './'. $themes[$theme]->filename;
-   _theme_load_registry($theme);
-  }
-  elseif (strpos($themes[$theme]->owner, '.engine')) {
-    // file is a template; include its engine
-    include_once './'. $themes[$theme]->owner;
-    $theme_engine = basename($themes[$theme]->owner, '.engine');
+  $theme_engine = NULL;
+
+  // Initialize the theme.
+  if (isset($theme->engine)) {
+    // Include the engine.
+    include_once './'. $theme->owner;
+
+    $theme_engine = $theme->engine;
     if (function_exists($theme_engine .'_init')) {
-      call_user_func($theme_engine .'_init', $themes[$theme]);
+      foreach ($base_theme as $base) {
+        call_user_func($theme_engine .'_init', $base);
+      }
+      call_user_func($theme_engine .'_init', $theme);
+    }
+  }
+  else {
+    // include non-engine theme files
+    foreach ($base_theme as $base) {
+      // Include the theme file or the engine.
+      if (!empty($base->owner)) {
+        include_once './'. $base->owner;
+      }
+    }
+    // and our theme gets one too.
+    if (!empty($theme->owner)) {
+      include_once './'. $theme->owner;
     }
-   _theme_load_registry($theme, $theme_engine);
   }
+  _theme_load_registry($theme, $base_theme, $theme_engine);
 }
 
 /**
@@ -111,14 +172,24 @@ function _theme_set_registry($registry) {
 /**
  * Get the theme_registry cache from the database; if it doesn't exist, build
  * it.
+ *
+ * @param $theme
+ *   The loaded $theme object.
+ * @param $base_theme
+ *   An array of loaded $theme objects representing the ancestor themes in
+ *   oldest first order.
+ * @param theme_engine
+ *   The name of the theme engine.
  */
-function _theme_load_registry($theme, $theme_engine = NULL) {
-  $cache = cache_get("theme_registry:$theme", 'cache');
+function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL) {
+  // Check the theme registry cache; if it exists, use it.
+  $cache = cache_get("theme_registry:$theme->name", 'cache');
   if (isset($cache->data)) {
     $registry = $cache->data;
   }
   else {
-    $registry = _theme_build_registry($theme, $theme_engine);
+    // If not, build one and cache it.
+    $registry = _theme_build_registry($theme, $base_theme, $theme_engine);
     _theme_save_registry($theme, $registry);
   }
   _theme_set_registry($registry);
@@ -128,7 +199,7 @@ function _theme_load_registry($theme, $theme_engine = NULL) {
  * Write the theme_registry cache into the database.
  */
 function _theme_save_registry($theme, $registry) {
-  cache_set("theme_registry:$theme", $registry);
+  cache_set("theme_registry:$theme->name", $registry);
 }
 
 /**
@@ -141,22 +212,31 @@ function drupal_rebuild_theme_registry() {
 }
 
 /**
- * Process a single invocation of the theme hook.
+ * Process a single invocation of the theme hook. $type will be one
+ * of 'module', 'theme_engine' or 'theme' and it tells us some
+ * important information.
+ *
+ * Because $cache is a reference, the cache will be continually
+ * expanded upon; new entries will replace old entries in the
+ * array_merge, but we are careful to ensure some data is carried
+ * forward, such as the arguments a theme hook needs.
  */
-function _theme_process_registry(&$cache, $name, $type) {
+function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
   $function = $name .'_theme';
   if (function_exists($function)) {
-    $result = $function($cache);
+    $result = $function($cache, $type, $theme, $path);
 
-    // Automatically find paths
-    $path = drupal_get_path($type, $name);
     foreach ($result as $hook => $info) {
       $result[$hook]['type'] = $type;
+      $result[$hook]['theme path'] = $path;
       // if function and file are left out, default to standard naming
       // conventions.
       if (!isset($info['file']) && !isset($info['function'])) {
         $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name .'_') . $hook;
       }
+      // If a path is set in the info, use what was set. Otherwise use the
+      // default path. This is mostly so system.module can declare theme
+      // functions on behalf of core .include files.
       if (isset($info['file']) && !isset($info['path'])) {
         $result[$hook]['file'] = $path .'/'. $info['file'];
       }
@@ -170,15 +250,7 @@ function _theme_process_registry(&$cache, $name, $type) {
       if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
         $info['preprocess functions'] = array();
         $prefix = ($type == 'module' ? 'template' : $name);
-        // It would be too much of a performance hit to let every module have
-        // a generic preprocess function; themes and theme engines can do that.
-        if ($type != 'module' && function_exists($prefix .'_preprocess')) {
-          $info['preprocess functions'][] = $prefix .'_preprocess';
-        }
-        if (function_exists($prefix .'_preprocess_'. $hook)) {
-          $info['preprocess functions'][] = $prefix .'_preprocess_'. $hook;
-        }
-        // theme engines get an extra set.
+        // theme engines get an extra set that come before the normally named preprocess.
         if ($type == 'theme_engine') {
           if (function_exists($prefix .'_engine_preprocess')) {
             $info['preprocess functions'][] = $prefix .'_engine_preprocess';
@@ -187,6 +259,15 @@ function _theme_process_registry(&$cache, $name, $type) {
             $info['preprocess functions'][] = $prefix .'_engine_preprocess_'. $hook;
           }
         }
+
+        // It would be too much of a performance hit to let every module have
+        // a generic preprocess function; themes and theme engines can do that.
+        if ($type != 'module' && function_exists($prefix .'_preprocess')) {
+          $info['preprocess functions'][] = $prefix .'_preprocess';
+        }
+        if (function_exists($prefix .'_preprocess_'. $hook)) {
+          $info['preprocess functions'][] = $prefix .'_preprocess_'. $hook;
+        }
       }
       if (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions']) && empty($cache[$hook]['override preprocess functions'])) {
         $info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']);
@@ -194,24 +275,47 @@ function _theme_process_registry(&$cache, $name, $type) {
       $result[$hook]['preprocess functions'] = $info['preprocess functions'];
     }
 
+    // Merge the newly created theme hooks into the existing cache.
     $cache = array_merge($cache, $result);
   }
 }
 
 /**
  * Rebuild the hook theme_registry cache.
+ *
+ * @param $theme
+ *   The loaded $theme object.
+ * @param $base_theme
+ *   An array of loaded $theme objects representing the ancestor themes in
+ *   oldest first order.
+ * @param theme_engine
+ *   The name of the theme engine.
  */
-function _theme_build_registry($theme, $theme_engine) {
+function _theme_build_registry($theme, $base_theme, $theme_engine) {
   $cache = array();
+  // First, process the theme hooks advertised by modules. This will
+  // serve as the basic registry.
   foreach (module_implements('theme') as $module) {
-    _theme_process_registry($cache, $module, 'module');
+    _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));
   }
 
+  // Process each base theme.
+  foreach ($base_theme as $base) {
+    // If the theme uses a theme engine, process its hooks.
+    $base_path = dirname($base->filename);
+    if ($theme_engine) {
+      _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path);
+    }
+    _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path);
+  }
+
+  // And then the same thing, but for the theme.
   if ($theme_engine) {
-    _theme_process_registry($cache, $theme_engine, 'theme_engine');
+    _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename));
   }
 
-  _theme_process_registry($cache, $theme, 'theme');
+  // Finally, hooks provided by the theme itself.
+  _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename));
 
   return $cache;
 }
@@ -237,6 +341,15 @@ function list_themes($refresh = FALSE) {
     while ($theme = db_fetch_object($result)) {
       if (file_exists($theme->filename)) {
         $theme->info = unserialize($theme->info);
+        if (isset($theme->info['stylesheet'])) {
+          $theme->stylesheet = $theme->info['stylesheet'];
+        }
+        if (isset($theme->info['engine'])) {
+          $theme->engine = $theme->info['engine'];
+        }
+        if (isset($theme->info['base theme'])) {
+          $theme->base_theme = $theme->info['base theme'];
+        }
         $list[$theme->name] = $theme;
       }
     }
@@ -344,6 +457,10 @@ function theme() {
   }
 
   $info = $hooks[$hook];
+  global $theme_path;
+  $temp = $theme_path;
+  // point path_to_theme() to the currently used theme path:
+  $theme_path = $hooks[$hook]['theme path'];
 
   if (isset($info['function'])) {
     // The theme call is a function.
@@ -355,7 +472,7 @@ function theme() {
       }
       include_once($function_file);
     }
-    return call_user_func_array($info['function'], $args);
+    $output = call_user_func_array($info['function'], $args);
   }
   else {
     // The theme call is a template.
@@ -424,8 +541,11 @@ function theme() {
         $template_file = $hooks[$hook]['path'] .'/'. $template_file;
       }
     }
-    return $render_function($template_file, $variables);
+    $output = $render_function($template_file, $variables);
   }
+  // restore path_to_theme()
+  $theme_path = $temp;
+  return $output;
 }
 
 /**
@@ -448,30 +568,13 @@ function drupal_discover_template($suggestions, $extension = '.tpl.php') {
  * Return the path to the currently selected theme.
  */
 function path_to_theme() {
-  global $theme;
+  global $theme_path;
 
-  if (!isset($theme)) {
+  if (!isset($theme_path)) {
     init_theme();
   }
 
-  $themes = list_themes();
-
-  return dirname($themes[$theme]->filename);
-}
-
-/**
- * Return the path to the currently selected engine.
- */
-function path_to_engine() {
-  global $theme, $theme_engine;
-
-  if (!isset($theme)) {
-    init_theme();
-  }
-
-  $engines = list_theme_engines();
-
-  return dirname($engines[$theme_engine]->filename);
+  return $theme_path;
 }
 
 /**
diff --git a/modules/color/color.module b/modules/color/color.module
index 9aabcea4badaad0dc4726880343073a504900cdf..04bbb8ff6e42a2562035162e101e8c81c4d91d5e 100644
--- a/modules/color/color.module
+++ b/modules/color/color.module
@@ -279,7 +279,8 @@ function color_scheme_form_submit($form_id, $values) {
  */
 function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette) {
   // Load stylesheet
-  $style = file_get_contents($paths['source'] .'style.css');
+  $themes = list_themes();
+  $style = file_get_contents($themes[$theme]->stylesheet);
 
   // Prepare color conversion table
   $conversion = $palette;
diff --git a/modules/system/system.install b/modules/system/system.install
index 7cf9b19797df57e82dc04780331b20e958a7e519..c6b55878ea51c93de9189492a8f11e43558e1c3d 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -1109,8 +1109,8 @@ function system_install() {
       break;
   }
 
-  db_query("INSERT INTO {system} (filename, name, type, owner, status, throttle, bootstrap, schema_version) VALUES ('themes/engines/phptemplate/phptemplate.engine', 'phptemplate', 'theme_engine', '', 1, 0, 0, 0)");
-  db_query("INSERT INTO {system} (filename, name, type, owner, status, throttle, bootstrap, schema_version, info) VALUES ('themes/garland/page.tpl.php', 'garland', 'theme', 'themes/engines/phptemplate/phptemplate.engine', 1, 0, 0, 0, '%s')", serialize(drupal_parse_info_file('themes/garland/garland.info') + system_theme_default()));
+  // Load system theme data appropriately.
+  system_theme_data();
 
   db_query("INSERT INTO {users} (uid,name,mail) VALUES(0,'','')");
 
@@ -3864,6 +3864,16 @@ function system_update_6012() {
   return $ret;
 }
 
+/**
+ * Rebuild cache data for theme system changes
+ */
+function system_update_6013() {
+  // Rebuild system table contents.
+  module_rebuild_cache();
+  system_theme_data();
+}
+
+
 /**
  * @} End of "defgroup updates-5.x-to-6.x"
  * The next series of updates should start at 7000.
diff --git a/modules/system/system.module b/modules/system/system.module
index a18439e07a2230e8add71e53530aa40885e24668..51478a383dfc38d95fecfd46d90a3953a47df7a9 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -505,7 +505,9 @@ function system_admin_theme_submit($form_id, $form_values) {
 function system_theme_select_form($description = '', $default_value = '', $weight = 0) {
   if (user_access('select different theme')) {
     $enabled = array();
-    foreach (list_themes() as $theme) {
+    $themes = list_themes();
+
+    foreach ($themes as $theme) {
       if ($theme->status) {
         $enabled[] = $theme;
       }
@@ -526,8 +528,17 @@ function system_theme_select_form($description = '', $default_value = '', $weigh
         // For the default theme, revert to an empty string so the user's theme updates when the site theme is changed.
         $info->key = $info->name == variable_get('theme_default', 'garland') ? '' : $info->name;
 
-        $info->screenshot = dirname($info->filename) .'/screenshot.png';
-        $screenshot = file_exists($info->screenshot) ? theme('image', $info->screenshot, t('Screenshot for %theme theme', array('%theme' => $info->name)), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');
+        $screenshot = NULL;
+        $theme_key = $info->name;
+        while ($theme_key) {
+          if (file_exists($themes[$theme_key]->info['screenshot'])) {
+            $screenshot = $themes[$theme_key]->info['screenshot'];
+            break;
+          }
+          $theme_key = isset($themes[$theme_key]->info['base theme']) ? $themes[$theme_key]->info['base theme'] : NULL;
+        }
+
+        $screenshot = $screenshot ? theme('image', $screenshot, t('Screenshot for %theme theme', array('%theme' => $info->name)), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');
 
         $form['themes'][$info->key]['screenshot'] = array('#value' => $screenshot);
         $form['themes'][$info->key]['description'] = array('#type' => 'item', '#title' => $info->name,  '#value' => dirname($info->filename) . ($info->name == variable_get('theme_default', 'garland') ? '<br /> <em>'. t('(site default theme)') .'</em>' : ''));
@@ -964,6 +975,8 @@ function system_theme_default() {
       'search',
       'slogan'
     ),
+    'stylesheet' => 'style.css',
+    'screenshot' => 'screenshot.png',
   );
 }
 
@@ -971,22 +984,12 @@ function system_theme_default() {
  * Collect data about all currently available themes
  */
 function system_theme_data() {
-  include_once './includes/install.inc';
-
   // Find themes
-  $themes = drupal_system_listing('\.theme$', 'themes');
+  $themes = drupal_system_listing('\.info$', 'themes');
 
   // Find theme engines
   $engines = drupal_system_listing('\.engine$', 'themes/engines');
 
-  // can't iterate over array itself as it uses a copy of the array items
-  foreach (array_keys($themes) as $key) {
-    drupal_get_filename('theme', $themes[$key]->name, $themes[$key]->filename);
-    drupal_load('theme', $themes[$key]->name);
-    $themes[$key]->owner = $themes[$key]->filename;
-    $themes[$key]->prefix = $key;
-  }
-
   // Remove all theme engines from the system table
   db_query("DELETE FROM {system} WHERE type = 'theme_engine'");
 
@@ -995,61 +998,106 @@ function system_theme_data() {
     drupal_get_filename('theme_engine', $engine->name, $engine->filename);
     drupal_load('theme_engine', $engine->name);
     db_query("INSERT INTO {system} (name, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', %d, %d, %d)", $engine->name, 'theme_engine', $engine->filename, 1, 0, 0);
+  }
 
-    // Add templates to the site listing
-    foreach (call_user_func($engine->name .'_templates') as $template) {
-      // Do not double-insert templates with theme files in their directory,
-      // but do register their engine data.
-      if (array_key_exists($template->name, $themes)) {
-        $themes[$template->name]->template = TRUE;
-        $themes[$template->name]->owner = $engine->filename;
-        $themes[$template->name]->prefix = $engine->name;
-      }
-      else {
-        $template->template = TRUE;
-        $template->name = basename(dirname($template->filename));
-        $template->owner = $engine->filename;
-        $template->prefix = $engine->name;
+  $defaults = system_theme_default();
 
-        $themes[$template->name] = $template;
+  $sub_themes = array();
+  // Read info files for each theme
+  foreach ($themes as $key => $theme) {
+    $themes[$key]->info = drupal_parse_info_file($theme->filename) + $defaults;
+    if (!empty($themes[$key]->info['base theme'])) {
+      $sub_themes[] = $key;
+    }
+    if (empty($themes[$key]->info['engine'])) {
+      $filename = dirname($themes[$key]->filename) .'/'. $themes[$key]->name .'.theme';
+      if (file_exists($filename)) {
+        $themes[$key]->owner = $filename;
+        $themes[$key]->prefix = $key;
       }
     }
+    else {
+      $engine = $themes[$key]->info['engine'];
+      if (isset($engines[$engine])) {
+        $themes[$key]->owner = $engines[$engine]->filename;
+        $themes[$key]->prefix = $engines[$engine]->name;
+        $themes[$key]->template = TRUE;
+      }
+    }
+
+    // Give the stylesheet proper path information.
+    if (!empty($themes[$key]->info['stylesheet'])) {
+      $themes[$key]->info['stylesheet'] = dirname($themes[$key]->filename) .'/'. $themes[$key]->info['stylesheet'];
+    }
+    // Give the screenshot proper path information.
+    if (!empty($themes[$key]->info['screenshot'])) {
+      $themes[$key]->info['screenshot'] = dirname($themes[$key]->filename) .'/'. $themes[$key]->info['screenshot'];
+    }
   }
 
-  // Find styles in each theme's directory.
-  foreach ($themes as $theme) {
-    foreach (file_scan_directory(dirname($theme->filename), 'style.css$') as $style) {
-      $style->style = TRUE;
-      $style->template = isset($theme->template) ? $theme->template : FALSE;
-      $style->name = basename(dirname($style->filename));
-      $style->owner = $theme->filename;
-      $style->prefix = !empty($theme->template) ? $theme->prefix : $theme->name;
-      // do not double-insert styles with theme files in their directory
-      if (array_key_exists($style->name, $themes)) {
-        continue;
+  // Now that we've established all our master themes, go back and fill in
+  // data for subthemes.
+  foreach ($sub_themes as $key) {
+    $base_key = system_find_base_theme($themes, $key);
+    if (!$base_key) {
+      continue;
+    }
+    // Copy the 'owner' and 'engine' over if the top level theme uses a
+    // theme engine.
+    if (isset($themes[$base_key]->owner)) {
+      if (isset($themes[$base_key]->info['engine'])) {
+        $themes[$key]->info['engine'] = $themes[$base_key]->info['engine'];
+        $themes[$key]->owner = $themes[$base_key]->owner;
+        $themes[$key]->prefix = $themes[$base_key]->prefix;
+      }
+      else {
+        $themes[$key]->prefix = $key;
       }
-      $themes[$style->name] = $style;
     }
   }
 
   // Extract current files from database.
   system_get_files_database($themes, 'theme');
 
-  $defaults = system_theme_default();
-  // Read info files for the owner
-  foreach (array_keys($themes) as $key) {
-    $themes[$key]->info = drupal_parse_info_file(dirname($themes[$key]->filename) .'/'. $themes[$key]->name .'.info') + $defaults;
-  }
-
   db_query("DELETE FROM {system} WHERE type = 'theme'");
 
   foreach ($themes as $theme) {
+    if (!isset($theme->owner)) {
+      $theme->owner = '';
+    }
+
     db_query("INSERT INTO {system} (name, owner, info, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d)", $theme->name, $theme->owner, serialize($theme->info), 'theme', $theme->filename, isset($theme->status) ? $theme->status : 0, 0, 0);
   }
 
   return $themes;
 }
 
+/**
+ * Recursive function to find the top level base theme. Themes can inherit
+ * templates and function implementations from earlier themes; this function
+ * finds the top level parent that has no ancestor, or returns NULL if there
+ * isn't a valid parent.
+ */
+function system_find_base_theme($themes, $key, $used_keys = array()) {
+  $base_key = $themes[$key]->info['base theme'];
+  // Does the base theme exist?
+  if (!isset($themes[$base_key])) {
+    return NULL;
+  }
+
+  // Is the base theme itself a child of another theme?
+  if (isset($themes[$base_key]->info['base theme'])) {
+    // Prevent loops.
+    if ($used_keys[$base_key]) {
+      return NULL;
+    }
+    $used_keys[$base_key] = TRUE;
+    return system_find_base_theme($themes, $base_key, $used_keys);
+  }
+  // If we get here, then this is our parent theme.
+  return $base_key;
+}
+
 /**
  * Get a list of available regions from a specified theme.
  *
@@ -1190,8 +1238,16 @@ function system_themes_form() {
   $status = array();
 
   foreach ($themes as $theme) {
-    $theme->screenshot = dirname($theme->filename) .'/screenshot.png';
-    $screenshot = file_exists($theme->screenshot) ? theme('image', $theme->screenshot, t('Screenshot for %theme theme', array('%theme' => $theme->info['name'])), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');
+    $screenshot = NULL;
+    $theme_key = $theme->name;
+    while ($theme_key) {
+      if (file_exists($themes[$theme_key]->info['screenshot'])) {
+        $screenshot = $themes[$theme_key]->info['screenshot'];
+        break;
+      }
+      $theme_key = isset($themes[$theme_key]->info['base theme']) ? $themes[$theme_key]->info['base theme'] : NULL;
+    }
+    $screenshot = $screenshot ? theme('image', $screenshot, t('Screenshot for %theme theme', array('%theme' => $theme->info['name'])), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');
 
     $form[$theme->name]['screenshot'] = array('#value' => $screenshot);
     $form[$theme->name]['info'] = array('#type' => 'value', '#value' => $theme->info);
diff --git a/themes/bluemarine/bluemarine.info b/themes/bluemarine/bluemarine.info
index 366a5d3bdbdfe4883558f14838d1a70a2d3c8035..15646d9dfea547497e9b1254a86712111226d6d0 100644
--- a/themes/bluemarine/bluemarine.info
+++ b/themes/bluemarine/bluemarine.info
@@ -2,3 +2,4 @@
 name = Bluemarine
 description = Table-based multi-column theme with a marine and ash color scheme.
 version = VERSION
+engine = phptemplate
diff --git a/themes/chameleon/chameleon.theme b/themes/chameleon/chameleon.theme
index 1c87653d3380f55741ea89dad84b4951c20f03d1..9d8c24f98459f36c5d5a39bc00efd46e1c37a99e 100644
--- a/themes/chameleon/chameleon.theme
+++ b/themes/chameleon/chameleon.theme
@@ -9,10 +9,9 @@
 /**
  * Implementation of hook_theme. Auto-discover theme functions.
  */
-function chameleon_theme($existing) {
+function chameleon_theme($existing, $type, $theme, $path) {
   $templates = array();
   // Check for function overrides.
-  global $theme;
   foreach ($existing as $hook => $info) {
     if (function_exists($theme .'_'. $hook)) {
       $templates[$hook] = array(
diff --git a/themes/chameleon/marvin/marvin.info b/themes/chameleon/marvin/marvin.info
index d70394ee5a5be4843fff73dc75b27f8764b4c01d..c77d66055b40be68e563ebe1b1436428991dedc4 100644
--- a/themes/chameleon/marvin/marvin.info
+++ b/themes/chameleon/marvin/marvin.info
@@ -4,3 +4,4 @@ description = Boxy tabled theme in all grays.
 regions[left] = Left sidebar
 regions[right] = Right sidebar
 version = VERSION
+base theme = chameleon
diff --git a/themes/engines/phptemplate/phptemplate.engine b/themes/engines/phptemplate/phptemplate.engine
index 86f1a002f6bf146c55f85142d0086b621d538b1e..4e9c065afbaddae80385ed12fee1707e331c461f 100644
--- a/themes/engines/phptemplate/phptemplate.engine
+++ b/themes/engines/phptemplate/phptemplate.engine
@@ -19,11 +19,11 @@ function phptemplate_init($template) {
  * pre-defined by Drupal so that we can use that information if
  * we need to.
  */
-function phptemplate_theme($existing) {
+function phptemplate_theme($existing, $type, $theme, $path) {
   $templates = array();
 
   // Check for template overrides.
-  $files = drupal_system_listing('\.tpl\.php$', path_to_theme(), 'name', 0);
+  $files = drupal_system_listing('\.tpl\.php$', $path, 'name', 0);
 
   foreach ($files as $template => $file) {
     // chop off the .tpl
@@ -37,7 +37,6 @@ function phptemplate_theme($existing) {
   }
 
   // Check for function overrides.
-  global $theme;
   foreach ($existing as $hook => $info) {
     if (function_exists($theme .'_'. $hook)) {
       $templates[$hook] = array(
@@ -54,10 +53,6 @@ function phptemplate_theme($existing) {
   return $templates;
 }
 
-function phptemplate_templates($directory = 'themes') {
-  return drupal_system_listing('^page\.tpl\.php$', $directory, 'filename');
-}
-
 /**
  * Adds additional helper variables to all templates.
  *
diff --git a/themes/garland/garland.info b/themes/garland/garland.info
index 68d9c57c171ad55d61fe57bad7a3af00085a2da2..740e0342573a46d7d7e4055528625a2f1895638f 100644
--- a/themes/garland/garland.info
+++ b/themes/garland/garland.info
@@ -2,3 +2,4 @@
 name = Garland
 description = Tableless, recolorable, multi-column, fluid width theme (default).
 version = VERSION
+engine = phptemplate
diff --git a/themes/garland/minnelli/minnelli.info b/themes/garland/minnelli/minnelli.info
index 66e0d8fa929a9e458d2c7b978b7992e05c9bb901..3fb7d16a60e4e8976a915b5ba71048f352fa3f27 100644
--- a/themes/garland/minnelli/minnelli.info
+++ b/themes/garland/minnelli/minnelli.info
@@ -2,3 +2,4 @@
 name = Minnelli
 description = Tableless, recolorable, multi-column, fixed width theme.
 version = VERSION
+base theme = garland
diff --git a/themes/pushbutton/pushbutton.info b/themes/pushbutton/pushbutton.info
index 895373d8677e621d6441a50a366ddcb378eaeb4c..cbe4aca15254aafaaf5338ee17f56b6e32fd46d5 100644
--- a/themes/pushbutton/pushbutton.info
+++ b/themes/pushbutton/pushbutton.info
@@ -2,3 +2,4 @@
 name = Pushbutton
 description = Tabled, multi-column theme in blue and orange tones.
 version = VERSION
+engine = phptemplate
diff --git a/update.php b/update.php
index c406531619c63a0eb77a8d0f598da739ed381a9a..ccda20707e6951b9749a31eb3d28e06701b69dd8 100644
--- a/update.php
+++ b/update.php
@@ -431,7 +431,7 @@ function update_results_page() {
   if ($GLOBALS['access_check'] == FALSE) {
     $output .= "<p><strong>Reminder: don't forget to set the <code>\$access_check</code> value at the top of <code>update.php</code> back to <code>TRUE</code>.</strong></p>";
   }
-  
+
   $output .= theme('item_list', $links);
 
   // Output a list of queries executed