Index: skinr.api.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/skinr/skinr.api.php,v
retrieving revision 1.1
diff -u -p -r1.1 skinr.api.php
--- skinr.api.php	17 Dec 2010 22:15:24 -0000	1.1
+++ skinr.api.php	9 Jan 2011 14:19:10 -0000
@@ -93,16 +93,293 @@ function hook_skinr_config() {
 }
 
 /**
- * Register Skinr API information. This is required for your module to have
- * its include files loaded.
+ * Define the API version of Skinr your code is compatible with.
  *
- * The full documentation for this hook is in the advanced help.
+ * This is required when creating a new Skinr plugin. It checks to make sure
+ * your Skins are compatible with the installed version of Skinr and takes care
+ * of loading the include files.
+ *
+ * @return
+ *   An associative array describing Skinr API integration:
+ *   - directory: (optional) The name of a sub-directory, in which include files
+ *     containing skin or group definitions may be found.
+ *   - path: (optional) The path to the directory containing the directory
+ *     specified in 'directory'. Defaults to the path of the module or theme
+ *     implementing the hook.
+ *   In case no Skinr plugin include files exist for your implementation, simply
+ *   define the function with an empty function body.
+ *
+ * The "hook" prefix is substituted with the name of the module or theme that
+ * implements it, e.g. THEME_skinr_api_VERSION() in template.php, or
+ * MODULE_skinr_api_VERSION() in MODULE.module.
+ *
+ * VERSION is normally identical to Skinr's major version; e.g., "2".
  */
-function hook_skinr_api() {
+function hook_skinr_api_VERSION() {
   return array(
-    'api' => 1,
-    'path' => drupal_get_path('module', 'modulename'),
+    'path' => drupal_get_path('module', 'mymodule'),
+    'directory' => 'skins',
+  );
+}
+
+/**
+ * Define the skin(s) for this Skinr plugin.
+ *
+ * Each skin definition consists of properties that define its form element and
+ * settings that are needed to make it work, such as the class(es) Skinr should
+ * apply, which files it should load, whether or not it should be disabled by
+ * default and which theme hook(s) it was designed to work with.
+ *
+ * Each skin name must be unique. It is recommended to prefix the name of each
+ * skin with the name of the theme or module name implementing it, followed by
+ * the name of the skin. Note that you cannot define 2 skins with the same
+ * the same name, even if they are in different plugins.
+ *
+ * Skin settings:
+ * - title (required): Title of the skin form element.
+ * - description (optional): Description of the skin form element.
+ * - group (optional): The group the skin is attached to; defaults to a Skinr
+ *   provided group labeled "General."
+ * - type (optional): The type of form element. Allowed values:
+ *   - checkboxes (default): Useful when single or multiple options can be
+ *     chosen.
+ *     Does not need to be set manually, as Skinr will apply this by default.
+ *   - select: Useful when a single option can be chosen, but many exist.
+ *   - radios: Useful when a single option can be chosen and only a few options
+ *     exist.
+ * - weight (discouraged): Sets the weight of the skin inside the group; NULL
+ *   by default. weight should not be set on each individual skin. Instead, it
+ *   should be used sparingly where positioning a skin at the very top or
+ *   bottom is desired.
+ * - attached (optional): A array containing information about CSS and
+ *   JavaScript files the skin requires. Each entry is an array keyed by type:
+ *   - css (optional): Maps to the functionality of drupal_add_css() with one
+ *     exception: paths are automatically assumed relative to the plugin
+ *     directory, unless external. Examples:
+ *     - Simple:
+ *       'css' => array('css/skin-name.css')
+ *     - Advanced:
+ *       'css' => array(
+ *         array('css/skin-name-ie.css', array(
+ *           'media' => 'screen',
+ *           'browsers' => array('IE' => 'lte IE 8'),
+ *         ),
+ *       )
+ *   - js (optional): Maps to the functionality of drupal_add_js() with one
+ *     exception: paths are automatically assumed as relative to the plugin
+ *     directory, unless external. Examples:
+ *     - Simple:
+ *       'js' => array('js/skin-name.js')
+ *     - Advanced:
+ *       'js' => array(
+ *         array(
+ *           'js/skin-name-advanced.js',
+ *           array(
+ *             'scope' => 'footer',
+ *             'group' => JS_THEME,
+ *         ),
+ *       )
+ * - options (required): An array containing one or more options (also arrays)
+ *   for the user to choose from when applying skins. Each option key should be
+ *   a machine name describing the option. An option should including the
+ *   following keys:
+ *   - label (required): The option label.
+ *   - class (required): An array containing one or more classes the skin
+ *     should apply. All classes should be entered as an array: For example:
+ *     'class' => array('class-b', 'class-b')
+ *   - attached (optional): Same syntax as above, but applies to a specific
+ *     option only.
+ * - theme hooks (optional): An array containing certain allowed hooks, which
+ *   allow you to limit where the skin can be used. Allowed options include:
+ *   block, block__MODULE, comment, comment__NODETYPE, comment_wrapper,
+ *   comment__wrapper_NODETYPE, node, node__NODETYPE, region,
+ *   region__REGIONNAME, panels_display, panels_region, panels_pane, views_view,
+ *   views_view__STYLENAME, views_view__DISPLAY_NAME, views_view__VIEWNAME, and
+ *   views_view__VIEWNAME_DISPLAYNAME.
+ * - default_status (optional): Skins are disabled by default to keep UI
+ *   clutter to a minimum. In some cases, like contrib themes, it makes sense to
+ *   enable skins which are required to make the theme work properly by default.
+ *   Setting this property to 1 will cause it to be enabled by default for all
+ *   installed themes.
+ *
+ * The "hook" prefix is substituted with the name of the module or theme
+ * implementing it.
+ */
+function hook_skinr_skin_info() {
+  $skins['skinr_menus'] = array(
+    'title' => t('Menu styles'),
+    'description' => t('Select a style to use for the main navigation.'),
+    'type' => 'select',
+    'group' => 'skinr_menus',
+    'theme hooks' => array('block__menu', 'block__menu_block'),
+    'attached' => array(
+      'css' => array('css/nav.css'),
+    ),
+    'options' => array(
+      'one_level' => array(
+        'title' => t('Standard (1 level) - No colors'),
+        'class' => array('nav'),
+      ),
+      'menu_a' => array(
+        'title' => t('Standard (1 level) - Green'),
+        'class' => array('nav', 'nav-a'),
+        'attached' => array('css' => array('css/nav-colors.css')),
+      ),
+      'menu_b' => array(
+        'title' => t('Standard (1 level) - Blue'),
+        'class' => array('nav', 'nav-b'),
+        'attached' => array('css' => array('css/nav-colors.css')),
+      ),
+      'menu_c' => array(
+        'title' => t('Dropdowns - No colors'),
+        'class' => array('nav', 'nav-dd'),
+        'attached' => array(
+          'css' => array('css/nav-dd.css'),
+          'js' => array('js/dropdown.js'),
+        ),
+      ),
+      'menu_d' => array(
+        'title' => t('Dropdowns - Green'),
+        'class' => array('nav', 'nav-dd', 'nav-a'),
+        'attached' => array(
+          'css' => array('css/nav-dd.css'),
+          'js' => array('js/dropdown.js'),
+        ),
+      ),
+      'menu_e' => array(
+        'title' => t('Dropdowns - Blue'),
+        'class' => array('nav', 'nav-dd', 'nav-b'),
+        'attached' => array(
+          'css' => array('css/nav-dd.css'),
+          'js' => array('js/dropdown.js'),
+        ),
+      ),
+    ),
   );
+  return $skins;
+}
+
+/**
+ * Define one or more skins in an atomic Skinr plugin file.
+ *
+ * This hook works identically to hook_skinr_skin_info(), but allows to place
+ * one or more related skin definitions into a separate plugin file.
+ *
+ * For example, considering a module or theme with the name "extension" that
+ * contains an include file:
+ * @code
+ * extension/skins/example.inc
+ * @encode
+ * The "hook" prefix is substituted with the name of the module or theme
+ * implementing it ("extension"), and PLUGIN is substituted with the name of the
+ * include file ("example"), e.g., THEME_skinr_skin_PLUGIN_info() or
+ * MODULE_skinr_skin_PLUGIN_info(). For above example, the function name would
+ * be: extension_skinr_skin_example_info().
+ */
+function hook_skinr_skin_PLUGIN_info() {
+  $skins['extension_example_menus'] = array(
+    'title' => t('Example menu styles'),
+    'type' => 'select',
+    'group' => 'skinr_menus',
+    'theme hooks' => array('block__menu', 'block__menu_block'),
+    'attached' => array(
+      'css' => array('css/nav.css'),
+    ),
+    'options' => array(
+      'menu_a' => array(
+        'title' => t('Standard (1 level) - Green'),
+        'class' => array('nav', 'nav-a'),
+        'attached' => array('css' => array('css/nav-colors.css')),
+      ),
+      'menu_b' => array(
+        'title' => t('Standard (1 level) - Blue'),
+        'class' => array('nav', 'nav-b'),
+        'attached' => array('css' => array('css/nav-colors.css')),
+      ),
+    ),
+  );
+  return $skins;
+}
+
+/**
+ * Perform alterations on Skinr skins.
+ *
+ * @param $skins
+ *   An array of skin information exposed by hook_skinr_skin_info()
+ *   implementations.
+ */
+function hook_skinr_skin_info_alter(&$skins) {
+  // Remove restrictions on theme hooks the skin works with.
+  unset($skins['skinr_navigation']['theme hooks']);
+}
+
+/**
+ * Defines group(s) that will contain skins.
+ *
+ * Groups are form element containers used to organize skins categorically. If
+ * you do not define a group, your skins will appear in Skinr's default group,
+ * which is labeled "General." Skinr implements 4 default groups, which may be
+ * used in any skin implementation. For more information, see skins/default.inc.
+ *
+ * Each group name must be unique. It is recommended to prefix the name of each
+ * group with the name of the theme or module name implementing it, followed by
+ * the name of the group. Note that you cannot define 2 groups with the same
+ * name, even if they are in different plugins.
+ *
+ * Group properties:
+ * - title (required): Brief title of the tab.
+ * - description (optional): Description of the group for administration page.
+ * - weight (discouraged): Weight of the tab group; 0 by default.
+ *
+ * The "hook" prefix is substituted with the name of the module or theme that
+ * implements it.
+ *
+ * @see skinr_default_skinr_group_info()
+ */
+function hook_skinr_group_info() {
+  $group['skinr_menus'] = array(
+    'title' => t('Menus'),
+    'description' => t('Menu and navigation styles.'),
+  );
+
+  return $groups;
+}
+
+/**
+ * Define one or more skin groups in an atomic Skinr plugin file.
+ *
+ * This hook works identically to hook_skinr_group_info(), but allows to place
+ * one or more related skin group definitions into a separate plugin file.
+ *
+ * For example, considering a module or theme with the name "extension" that
+ * contains an include file:
+ * @code
+ * extension/skins/example.inc
+ * @encode
+ * The "hook" prefix is substituted with the name of the module or theme
+ * implementing it ("extension"), and PLUGIN is substituted with the name of the
+ * include file ("example"), e.g., THEME_skinr_group_PLUGIN_info() or
+ * MODULE_skinr_group_PLUGIN_info(). For above example, the function name would
+ * be: extension_skinr_group_example_info().
+ */
+function hook_skinr_group_PLUGIN_info() {
+  $group['extension_example_menus'] = array(
+    'title' => t('Menus'),
+    'description' => t('Menu and navigation styles.'),
+  );
+  return $groups;
+}
+
+/**
+ * Perform alterations on Skinr groups.
+ *
+ * @param $groups
+ *   An array of group information exposed by hook_skinr_group_info()
+ *   implementations.
+ */
+function hook_skinr_group_info_alter(&$groups) {
+  // Show this tab group at the top of the Skinr settings form.
+  $groups['skinr_menus']['weight'] = -1;
 }
 
 /**
Index: skinr.handlers.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/skinr/skinr.handlers.inc,v
retrieving revision 1.9
diff -u -p -r1.9 skinr.handlers.inc
--- skinr.handlers.inc	20 Dec 2010 21:42:29 -0000	1.9
+++ skinr.handlers.inc	9 Jan 2011 14:24:50 -0000
@@ -140,26 +140,28 @@ function skinr_submit_handler(&$form, $f
   }
 }
 
-// Declare API compatibility on behalf of core modules:
-
 /**
- * Implements hook_skinr_api().
- *
- * This one is used as the base to reduce errors when updating.
+ * Implements hook_skinr_api_VERSION().
  */
-function skinr_skinr_api() {
-  return array(
-    'api' => 1,
-    'path' => drupal_get_path('module', 'skinr') . '/modules',
-  );
+function skinr_skinr_api_2() {
 }
 
-function block_skinr_api() { return skinr_skinr_api(); }
+function block_skinr_api_2() {
+  return skinr_skinr_api_2();
+}
 
-function comment_skinr_api() { return skinr_skinr_api(); }
+function comment_skinr_api_2() {
+  return skinr_skinr_api_2();
+}
 
-function node_skinr_api() { return skinr_skinr_api(); }
+function node_skinr_api_2() {
+  return skinr_skinr_api_2();
+}
 
-function panels_skinr_api() { return skinr_skinr_api(); }
+function panels_skinr_api_2() {
+  return skinr_skinr_api_2();
+}
 
-function views_skinr_api() { return skinr_skinr_api(); }
+function views_skinr_api_2() {
+  return skinr_skinr_api_2();
+}
Index: skinr.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/skinr/skinr.module,v
retrieving revision 1.37
diff -u -p -r1.37 skinr.module
--- skinr.module	20 Dec 2010 21:42:29 -0000	1.37
+++ skinr.module	9 Jan 2011 16:49:32 -0000
@@ -7,6 +7,11 @@
  */
 
 /**
+ * The Skinr API version.
+ */
+define('SKINR_VERSION', 2);
+
+/**
  * Implements hook_help().
  */
 function skinr_help($path, $arg) {
@@ -23,6 +28,19 @@ function skinr_help($path, $arg) {
 }
 
 /**
+ * Implements hook_hook_info().
+ */
+function skinr_hook_info() {
+  $hooks = array(
+    'skinr_api_2',
+  );
+  $hooks = array_fill_keys($hooks, array(
+    'group' => 'skinr',
+  ));
+  return $hooks;
+}
+
+/**
  * Implements hook_module_implements_alter().
  */
 function skinr_module_implements_alter(&$implementations, $hook) {
@@ -36,10 +54,12 @@ function skinr_module_implements_alter(&
 
 /**
  * Implements hook_init().
+ *
+ * @todo Kill me. Entirely.
  */
 function skinr_init() {
   module_load_include('inc', 'skinr', 'skinr.handlers');
-  skinr_module_load_all_includes();
+  skinr_load_includes();
 }
 
 /**
@@ -64,8 +84,10 @@ function skinr_preprocess(&$variables, $
       $preprocess_settings = $module_settings['preprocess'][$original_hook];
       $sids = skinr_handler('preprocess_index_handler', 'preprocess', $preprocess_settings['index_handler'], $variables);
 
+      // @todo Kill skinr_skin_extract(), and only store what has been
+      //   configured, enabled, and should be actively applied, in order to use
+      //   drupal_process_attached().
       if ($extracted = skinr_skin_extract($module, $sids, $current_theme)) {
-        // @todo How can this be done better with drupal_process_attached()?
         foreach ($extracted['css'] as $file) {
           if ($file['enabled']) {
             drupal_add_css($file['path'], $file['file_options']);
@@ -417,37 +439,75 @@ function skinr_rule_visible($rid) {
   return FALSE;
 }
 
-// ------------------------------------------------------------------
-// Include file helpers.
-
 /**
- * Includes $module.skinr.inc files.
- */
-function skinr_module_load_all_includes() {
-  foreach (skinr_get_module_apis() as $module => $info) {
-    $file = DRUPAL_ROOT . "/$info[path]/$module.skinr.inc";
-    if (is_file($file)) {
-      require_once $file;
-    }
-  }
-}
-
-/**
- * Get a list of modules that support skinr.
+ * Returns a list of extensions that implement this API version of Skinr.
+ *
+ * @return
+ *   An associative array whose keys are system names of extensions and whose
+ *   values are again associative arrays containing:
+ *   - type: Either 'module' or 'theme'.
+ *   - name: The system name of the extension.
+ *   - path: The path to the extension.
+ *   - directory: (optional) The sub-directory holding Skinr plugin files.
+ *   - ...: Any other properties defined by the module or theme.
  */
-function skinr_get_module_apis() {
+function skinr_implements() {
   $cache = &drupal_static(__FUNCTION__);
 
   if (!isset($cache)) {
     $cache = array();
-    foreach (module_implements('skinr_api') as $module) {
-      $function = $module . '_skinr_api';
+    // Collect hook_skinr_api_VERSION() module implementations. This will also
+    // auto-load $module.skinr.inc files, which may contain skin/group hook
+    // implementations (when not using the plugin system).
+    foreach (module_implements('skinr_api_' . SKINR_VERSION) as $module) {
+      // Ensure that $module and the extension type is registered.
+      $cache[$module] = array(
+        'type' => 'module',
+        'name' => $module,
+      );
+      // Check whether the hook returns any information.
+      $function = $module . '_skinr_api_' . SKINR_VERSION;
       $info = $function();
-      if (isset($info['api']) && $info['api'] == 1.000) {
-        if (!isset($info['path'])) {
-          $info['path'] = drupal_get_path('module', $module);
+      if (isset($info) && is_array($info)) {
+        $cache[$module] += $info;
+      }
+      // Populate defaults.
+      $cache[$module] += array(
+        'path' => drupal_get_path('module', $module),
+        'directory' => NULL,
+      );
+    }
+    // Collect the equivalent of hook_skinr_api_VERSION() implementations in
+    // themes. The theme system only initializes one theme (and optionally its
+    // base themes) for the current request, and the phptemplate engine only
+    // loads template.php during theme initialization. Furthermore, template.php
+    // is a custom concept of the phptemplate engine and does not exist for
+    // other theme engines. Since we are interested in all existing
+    // implementations of all enabled themes, the equivalent of the module hook
+    // is a theme .info file property 'skinr' that has the sub-keys 'api' and
+    // optionally 'directory' defined.
+    foreach (list_themes() as $name => $theme) {
+      if (!empty($theme->status) && isset($theme->info['skinr']['api']) && $theme->info['skinr']['api'] == SKINR_VERSION) {
+        // Ensure that the theme name and the extension type is registered.
+        $cache[$name] = array(
+          'type' => 'theme',
+          'name' => $name,
+        );
+        // Add any additional information that has been registered.
+        $cache[$name] += $theme->info['skinr'];
+        // Populate defaults.
+        $cache[$name] += array(
+          'path' => drupal_get_path('theme', $name),
+          // Since themes cannot do anything else than registering skins and
+          // groups, we default to the sub-directory 'skins'.
+          'directory' => 'skins',
+        );
+        // Lastly, for API consistency with modules, check whether the theme
+        // contains a $theme.skinr.inc file and auto-load it, if any.
+        $file = DRUPAL_ROOT . '/' . $cache[$name]['path'] . '/' . $name . '.skinr.inc';
+        if (file_exists($file)) {
+          require_once $file;
         }
-        $cache[$module] = $info;
       }
     }
   }
@@ -455,6 +515,48 @@ function skinr_get_module_apis() {
   return $cache;
 }
 
+/**
+ * Includes $extension.skinr.inc files of extensions compatible with this version of Skinr.
+ *
+ * @todo Shoot me. Twice.
+ */
+function skinr_load_includes() {
+  foreach (skinr_implements() as $extension) {
+    $file = DRUPAL_ROOT . '/' . $extension['path'] . '/' . $extension['name'] . '.skinr.inc';
+    if (file_exists($file)) {
+      require_once $file;
+    }
+  }
+}
+
+/**
+ * Includes Skinr plugin files for an extension, if any.
+ *
+ * @param $extension
+ *   The API information for an extension, as returned by skinr_implements().
+ */
+function skinr_load_plugins($extension) {
+  static $loaded = array();
+
+  // If plugins have already been loaded for this extension, return them.
+  if (isset($loaded[$extension['name']])) {
+    return $loaded[$extension['name']];
+  }
+  $loaded[$extension['name']] = array();
+
+  // If the extension defines a plugin directory, scan its plugins.
+  if (isset($extension['directory'])) {
+    $dir = DRUPAL_ROOT . '/' . $extension['path'] . '/' . $extension['directory'];
+    $mask = '@^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.inc$@';
+    $loaded[$extension['name']] = file_scan_directory($dir, $mask, array(
+      'key' => 'name',
+      'recurse' => FALSE,
+      'callback' => 'include_once',
+    ));
+  }
+  return $loaded[$extension['name']];
+}
+
 // -----------------------------------------------------------------------
 // Skinr data handling functions.
 
@@ -702,24 +804,12 @@ function skinr_current_theme($exclude_ad
 }
 
 /**
- * Prepare defaults for groups.
- *
- * @return
- *   An array of default group settings.
- */
-function skinr_group_info_default() {
-  return array(
-    'title' => '',
-    'description' => '',
-    'weight' => NULL,
-  );
-}
-
-/**
  * Prepare defaults for skins.
  *
  * @return
  *   An array of default skin settings.
+ *
+ * @todo Merge into skinr_get_skin_info() and remove this function.
  */
 function skinr_skin_info_default() {
   return array(
@@ -758,24 +848,6 @@ function skinr_skin_info_status_default(
 }
 
 /**
- * Get a list of plugin hooks.
- *
- * @return
- *   An array of hook details keyed by hook name. The hook details contain the
- *   following keys:
- *   - 'type': Available options are 'module' or 'theme'.
- *   - 'name': The name of the owner module or theme of this plugin.
- *   - 'plugin': The name of the skins plugin.
- *   - 'path': This plugin's path.
- */
-function skinr_plugin_hooks() {
-  // @todo Load the include files and get an array of hooks; we need to
-  //   know which module and which plugin comprises the hook.
-  $hooks = array();
-  return $hooks;
-}
-
-/**
  * Helper function to prepend a path to an array of stylesheet or script filenames.
  *
  * If the url is absolute (e.g. the url start with 'http://' or 'https://')
@@ -813,11 +885,10 @@ function _skinr_add_path_to_files(&$file
  * @param $skin_infos
  *   A skins array as returned from a skins plugin.
  * @param $source
- *   An array with the following keys:
- *   - 'type': Available options are 'module' or 'theme'.
- *   - 'name': The name of the owner module or theme of this plugin.
- *   - 'plugin': The name of the skins plugin.
- *   - 'path': This plugin's path.
+ *   An associative array containing information about the source of the skin.
+ *   See skinr_implements() for details.
+ *
+ * @todo Merge into skinr_get_skin_info() and remove this function.
  */
 function skinr_skin_info_process(&$skin_infos, $source) {
   foreach ($skin_infos as $skin_name => $skin_info) {
@@ -833,9 +904,6 @@ function skinr_skin_info_process(&$skin_
     // Merge in default statuses for all themes.
     $skin_infos[$skin_name]['status'] = array_merge(skinr_skin_info_status_default($skin_infos[$skin_name]['default_status']), $skin_infos[$skin_name]['status']);
 
-    // @todo Apply overridden statuses. This will need to be loaded from
-    //   database.
-
     // Add path to stylesheets.
     if (isset($skin_infos[$skin_name]['attached']['css'])) {
       _skinr_add_path_to_files($skin_infos[$skin_name]['attached']['css'], $source['path']);
@@ -867,82 +935,111 @@ function skinr_skin_info_process(&$skin_
 }
 
 /**
- * Retrieves a list of all available skins.
+ * Retrieves all skins registered by modules and themes.
  *
  * @return
- *    An array of skins.
+ *   An array of skins.
  */
 function skinr_get_skin_info() {
-  $skin_infos = &drupal_static(__FUNCTION__);
+  $skin_info = &drupal_static(__FUNCTION__);
 
-  if (!isset($skin_infos)) {
-    // Set skins to an array to avoid errors down the line if no skins
-    // are found.
-    $skin_infos = array();
-
-    // Load skins from hook_skinr_skins().
-    $hooks = skinr_plugin_hooks();
-    foreach ($hooks as $hook => $source) {
-      $function = $hook . '_skinr_skin_info';
-      $result = $function();
-      if (isset($result)) {
-        if (!is_array($result)) {
-          $result = array($result);
+  if (!isset($skin_info)) {
+    if ($cached = cache_get('skinr_skin_info')) {
+      $skin_info = $cached->data;
+      return $skin_info;
+    }
+    $skin_info = array();
+
+    foreach (skinr_implements() as $name => $extension) {
+      $hooks = array(
+        "$name_skinr_skin_info" => $extension,
+      );
+      // Load the extension's plugins, if any.
+      if ($files = skinr_load_plugins($extension)) {
+        // The source path for a plugin is the directory it is contained in.
+        $dir = $extension['path'] . '/' . $extension['directory'];
+        foreach ($files as $plugin => $file) {
+          $hooks["$name_skinr_skin_$plugin_info"] = array(
+            'path' => $dir,
+          ) + $extension;
         }
+      }
+      foreach ($hooks as $function => $source) {
+        if (function_exists($function)) {
+          $extension_info = $function();
+          if (isset($extension_info) && is_array($extension_info)) {
+            // Prepare the skin information.
+            skinr_skin_info_process($extension_info, $source);
 
-        // Make any programatic changes.
-        skinr_skin_info_process($result, $source);
-
-        $skin_infos = array_merge_recursive($skin_infos, $result);
+            $skin_info += $extension_info;
+          }
+        }
       }
     }
 
-    // Allow modules to alter skin infos through hook_skinr_skin_info_alter().
-    drupal_alter('skinr_skin_info', $skin_infos);
+    // Allow modules to alter registered skin information.
+    drupal_alter('skinr_skin_info', $skin_info);
+
+    cache_set('skinr_skin_info', $skin_info);
   }
 
-  return $skin_infos;
+  return $skin_info;
 }
 
 /**
- * Retrieves a list of all available groups.
+ * Retrieves all skin groups registered by modules and themes.
  *
  * @return
- *    An array of groups.
+ *   An array of groups.
  */
 function skinr_get_group_info() {
-  $group_infos = &drupal_static(__FUNCTION__);
+  $group_info = &drupal_static(__FUNCTION__);
 
-  if (!isset($group_infos)) {
-    // Set groups to an array to avoid errors down the line if no groups
-    // are found.
-    $group_infos = array();
-
-    // Load skins from hook_skinr_groups().
-    $hooks = skinr_plugin_hooks();
-    foreach ($hooks as $hook => $source) {
-      $function = $hook . '_skinr_group_info';
-      $result = $function();
-      if (isset($result)) {
-        if (!is_array($result)) {
-          $result = array($result);
+  if (!isset($group_info)) {
+    if ($cached = cache_get('skinr_group_info')) {
+      $skin_info = $cached->data;
+      return $group_info;
+    }
+    $group_info = array();
+
+    foreach (skinr_implements() as $name => $extension) {
+      $hooks = array(
+        "$name_skinr_group_info" => $extension,
+      );
+      // Load the extension's plugins, if any.
+      if ($files = skinr_load_plugins($extension)) {
+        // The source path for a plugin is the directory it is contained in.
+        $dir = $extension['path'] . '/' . $extension['directory'];
+        foreach ($files as $plugin => $file) {
+          $hooks["$name_skinr_group_$plugin_info"] = array(
+            'path' => $dir,
+          ) + $extension;
         }
+      }
+      foreach ($hooks as $function => $source) {
+        if (function_exists($function)) {
+          $extension_info = $function();
+          if (isset($extension_info) && is_array($extension_info)) {
+            // Prepare the skin group information.
+            $extension_info += array(
+              'title' => '',
+              'description' => '',
+              'weight' => 0,
+            );
 
-        // Make any programatic changes.
-        // Merge in defaults.
-        foreach ($result as $group_name => $group_info) {
-          $result[$group_name] = array_merge(skinr_group_info_default(), $group_info);
+            $group_info += $extension_info;
+          }
         }
-
-        $group_infos = array_merge_recursive($group_infos, $result);
       }
     }
 
     // Allow modules to alter groups through hook_skinr_group_info_alter().
-    drupal_alter('skinr_group_info', $group_infos);
+    drupal_alter('skinr_group_info', $group_info);
+
+    cache_set('skinr_group_info', $group_info);
   }
 
-  return $group_infos;
+  return $group_info;
 }
 
 /**
Index: skins/default.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/skinr/skins/default.inc,v
retrieving revision 1.2
diff -u -p -r1.2 default.inc
--- skins/default.inc	29 Dec 2010 01:02:31 -0000	1.2
+++ skins/default.inc	9 Jan 2011 11:57:11 -0000
@@ -7,17 +7,17 @@
  */
 
 /**
- * Implements hook_skinr_group_info().
+ * Implements hook_skinr_group_PLUGIN_info().
  */
-function skinr_default_skinr_group_info() {
-  $groups['box'] = array(
-    'title' => t('Box styles'),
-    'description' => t('Presentational styles for the container; default group for skins.'),
-  );
+function skinr_skinr_group_default_info() {
   $groups['general'] = array(
     'title' => t('General'),
     'description' => t('Styles for content such as lists, buttons, margins, padding, etc.'),
   );
+  $groups['box'] = array(
+    'title' => t('Box styles'),
+    'description' => t('Presentational styles for the container.'),
+  );
   $groups['typography'] = array(
     'title' => t('Typography'),
     'description' => t('Fonts, styles, sizes and other typography related skins.'),
@@ -26,6 +26,5 @@ function skinr_default_skinr_group_info(
     'title' => t('Layout'),
     'description' => t('Grid, layout and other structural related skins.'),
   );
-
   return $groups;
-}
\ No newline at end of file
+}
