<?php

/**
 * @file
 * Handles core Skinr functionality.
 */

/**
 * The Skinr API version.
 */
define('SKINR_VERSION', 2);

/**
 * Implements hook_help().
 */
function skinr_help($path, $arg) {
  switch ($path) {
    case 'admin/help#skinr':
      if (module_exists('advanced_help')) {
        return t('Visit the <a href="@skinr-help">help page</a> for full documentation.', array('@skinr-help' => url('admin/advanced_help/skinr')));
      }
      else {
        return t('Please download and enable the <a href="http://drupal.org/project/advanced_help">Advanced Help</a> module for full Skinr documentation.');
      }
      break;
  }
}

/**
 * Implements hook_hook_info().
 */
function skinr_hook_info() {
  $hooks = array(
    'skinr_api_2',
    'skinr_elements',
    'skinr_group_info',
    'skinr_group_info_alter',
    'skinr_skin_info',
    'skinr_skin_info_alter',
    'skinr_theme_hooks',
    'skinr_theme_hooks_alter',
  );
  $hooks = array_fill_keys($hooks, array(
    'group' => 'skinr',
  ));
  return $hooks;
}

/**
 * Clears cached Skinr information.
 */
function skinr_cache_reset() {
  cache_clear_all('skinr_', 'cache', TRUE);
}

/**
 * Implements hook_preprocess().
 *
 * @todo Optimize this function by removing dependencies on
 *   skinr_get_skin_info() and similar resource heavy functions.
 * @todo Account for Drupal's caching being enabled and make it work.
 */
function skinr_preprocess(&$variables, $hook) {
  // Fix for update script.
  if ($hook == 'maintenance_page') {
    return;
  }

  $current_theme = skinr_current_theme();
  $skin_info = skinr_get_skin_info();

  $theme_registry = theme_get_registry();
  $original_hook = (isset($theme_registry[$hook]['original hook']) ? $theme_registry[$hook]['original hook'] : $hook);

  // An array of $elements based on $module and $original_hook, derived from $variables.
  $array_elements = skinr_invoke_all('skinr_elements', $variables, $original_hook, 'preprocess');
  foreach ($array_elements as $module => $elements) {
    if (empty($elements)) {
      // We can receive empty arrays; if that happens, there's no point
      // in continuing.
      continue;
    }

    // Get a list of skin configuration IDs to pass to
    // skinr_skin_load_multiple().
    $params = array(
      'theme' => $current_theme,
      'module' => $module,
      'element' => $elements,
      'status' => 1,
    );
    $sids = skinr_skin_get_sids($params);
    if (empty($sids)) {
      // Noting to apply.
      continue;
    }

    $applied_skins = array();
    $skins = skinr_skin_load_multiple($sids);

    // Invoke hook_skinr_preprocess_alter() in all modules.
    $context = array(
      'hook' => $hook,
      'variables' => &$variables,
      'theme' => $current_theme,
      'module' => $module,
      'elements' => $elements,
    );
    drupal_alter('skinr_preprocess', $skins, $context);

    foreach ($skins as $skin) {
      $applied_skins = array($skin->skin => $skin->options) + $applied_skins;
    }

    // Use drupal_process_attached() to add attachements such as JS and CSS.
    if (!empty($applied_skins)) {
      foreach ($applied_skins as $skin_name => $skin_options) {

        // Special case for _additional.
        if ($skin_name == '_additional') {
          continue;
        }

        // Make sure this skin is enabled for the current theme.
        if (isset($skin_info[$skin_name]['attached'])) {
          $elements['#attached'] = $skin_info[$skin_name]['attached'];
          drupal_process_attached($elements);
        }

        if (!is_array($skin_options)) {
          $skin_options = array($skin_options);
        }
        foreach ($skin_options as $skin_option) {
          if (isset($skin_info[$skin_name]['options'][$skin_option]['attached'])) {
            $elements['#attached'] = $skin_info[$skin_name]['options'][$skin_option]['attached'];
            drupal_process_attached($elements);
          }
        }
      }

      $variables['classes_array'] = array_merge($variables['classes_array'], skinr_flatten_skins_array($applied_skins));
    }
  }
}

/**
 * Returns an array of classes.
 *
 * @param $skin_options
 *   An array of skin options keyed by their skin name. The key '_additional'
 *   is reserved for additional classes entered by the user.
 *
 * @todo Optimize this function by removing dependencies on the resource heavy
 *   skinr_get_skin_info() function.
 * @todo Rename function to reflect new functionality.
 */
function skinr_flatten_skins_array($skin_options) {
  $skin_info = skinr_get_skin_info();

  $classes = array();
  foreach ($skin_options as $skin_name => $options) {
    if ($skin_name == '_additional') {
      $classes = array_merge($classes, $options);
    }
    else {
      foreach ($options as $option) {
        if (!empty($skin_info[$skin_name]['options'][$option]['class'])) {
          $classes = array_merge($classes, $skin_info[$skin_name]['options'][$option]['class']);
        }
      }
    }
  }
  return array_unique($classes);
}

/**
 * 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_implements_api() {
  $cache = &drupal_static(__FUNCTION__);

  if (!isset($cache)) {
    if ($cached = cache_get('skinr_implements_api')) {
      $cache = $cached->data;
      return $cache;
    }
    $cache = array();

    // 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).
    $module_info = system_get_info('module');
    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,
        'version' => isset($module_info[$module]['version']) ? $module_info[$module]['version'] : NULL,
      );
      // Check whether the hook returns any information.
      $function = $module . '_skinr_api_' . SKINR_VERSION;
      $info = $function();
      if (isset($info) && is_array($info)) {
        $cache[$module] += $info;
      }
      // If the module specified a custom path, check whether it contains a
      // $module.skinr.inc file and auto-load it. module_implements() only
      // auto-loads $module.skinr.inc in a module's root folder.
      if (isset($cache[$module]['path'])) {
        $file = $cache[$module]['path'] . '/' . $module . '.skinr.inc';
        if (file_exists(DRUPAL_ROOT . '/' . $file)) {
          $cache[$module]['include file'] = $file;
        }
      }
      // 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.
    // Account for all enabled themes and (any recursive) base themes of them,
    // regardless of whether base themes are enabled.
    $all_themes = list_themes();
    $themes = array();
    // Additionally record the base themes and sub themes of each theme, in
    // order to apply inheritance rules elsewhere. Do not assign these variables
    // as properties on the theme objects themselves, since all objects are
    // pointers (much like references) in PHP 5, so our properties would be
    // visible for everyone else who calls list_themes().
    $base_themes = array();
    $sub_themes = array();
    foreach ($all_themes as $name => $theme) {
      // If the theme is enabled, add it to the stack.
      if (!empty($theme->status)) {
        $themes[$name] = $theme;
        // Find and add all base themes of the enabled theme to the stack.
        // @see drupal_theme_initialize()
        $sub_theme_name = $name;
        while ($name && isset($all_themes[$name]->base_theme)) {
          // Record the sub theme for the base theme.
          $sub_themes[$all_themes[$name]->base_theme][$name] = $name;

          // Add the base theme to the stack.
          $name = $all_themes[$name]->base_theme;
          $themes[$name] = $all_themes[$name];

          // Record the base theme for the original sub theme.
          $base_themes[$sub_theme_name][$name] = $name;
        }
      }
    }
    foreach ($themes as $name => $theme) {
      if (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,
          'version' => isset($theme->info['version']) ? $theme->info['version'] : NULL,
          'base themes' => isset($base_themes[$name]) ? $base_themes[$name] : array(),
          'sub themes' => isset($sub_themes[$name]) ? $sub_themes[$name] : array(),
        );
        // 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 = $cache[$name]['path'] . '/' . $name . '.skinr.inc';
        if (file_exists(DRUPAL_ROOT . '/' . $file)) {
          $cache[$name]['include file'] = $file;
        }
      }
    }

    cache_set('skinr_implements_api', $cache);
  }

  return $cache;
}

/**
 * Determine whether a module implements a hook.
 *
 * Replacement for module_hook() that only invokes modules that implement
 * the current version of Skinr API. It also supports $module.skinr.inc files
 * in themes and custom paths.
 *
 * @param $module
 *   The name of the module (without the .module extension).
 * @param $hook
 *   The name of the hook (e.g. "skinr_skin_info" or "skinr_theme_hooks").
 *
 * @return
 *   TRUE if the module is both installed and enabled, and the hook is
 *   implemented in that module.
 */
function skinr_hook($module, $hook) {
  $function = $module . '_' . $hook;
  if (function_exists($function)) {
    return TRUE;
  }
  // If the hook implementation does not exist, check whether it may live in an
  // include file in a custom path.
  $extensions = skinr_implements_api();
  if (isset($extensions[$module])) {
    $extension = $extensions[$module];
    if (isset($extension['include file'])) {
      // The module specified a custom path. module_hook() only auto-loads
      // $module.skinr.inc in a module's root folder.
      skinr_load_include($extension['include file']);
      if (function_exists($module . '_' . $hook)) {
        return TRUE;
      }
    }
    else {
      // Run through module_hook() to auto-load $module.skinr.inc from a
      // non-custom path.
      if (module_hook($module, $hook)) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Determine which modules are implementing a hook.
 *
 * Replacement for module_implements() that only invokes modules that implement
 * the current version of Skinr API. It also supports $module.skinr.inc files
 * in themes and custom paths.
 *
 * @param $hook
 *   The name of the hook (e.g. "skinr_skin_info" or "skinr_theme_hooks").
 *
 * @return
 *   An array with the names of the modules which are implementing this hook.
 *
 * @see skinr_exit()
 */
function skinr_implements($hook) {
  $implementations = &drupal_static(__FUNCTION__, array());

  // Fetch implementations from cache.
  if (empty($implementations)) {
    $implementations = cache_get('skinr_implements', 'cache_bootstrap');
    if ($implementations === FALSE) {
      $implementations = array();
    }
    else {
      $implementations = $implementations->data;
    }
  }

  if (!isset($implementations[$hook])) {
    $implementations['#write_cache'] = TRUE;

    $extensions = skinr_implements_api();
    $implementations[$hook] = array();
    foreach ($extensions as $module => $extension) {
      if (isset($extension['include file'])) {
        // The module specified a custom path. module_implements() and
        // module_hook() only auto-load $module.skinr.inc in a module's
        // root folder.
        $include_file = skinr_load_include($extension['include file']);
        if (function_exists($module . '_' . $hook)) {
          $implementations[$hook][$module] = $include_file ? $extension['include file'] : FALSE;
        }
      }
      else {
        // Run through module_hook() to auto-load $module.skinr.inc from a
        // non-custom path.
        if (module_hook($module, $hook)) {
          $implementations[$hook][$module] = FALSE;
        }
      }
    }
    // Allow modules to change the weight of specific implementations but avoid
    // an infinite loop.
    if ($hook != 'skinr_implements_alter') {
      drupal_alter('skinr_implements', $implementations[$hook], $hook);
    }
  }
  else {
    foreach ($implementations[$hook] as $module => $file) {
      if ($file) {
        skinr_load_include($file);
      }
      else {
        module_hook($module, $hook);
      }
      // It is possible that a module removed a hook implementation without the
      // implementations cache being rebuilt yet, so we check whether the
      // function exists on each request to avoid undefined function errors.
      // Since module_hook() may needlessly try to load the include file again,
      // function_exists() is used directly here.
      if (!function_exists($module . '_' . $hook)) {
        // Clear out the stale implementation from the cache and force a cache
        // refresh to forget about no longer existing hook implementations.
        unset($implementations[$hook][$module]);
        $implementations['#write_cache'] = TRUE;
      }
    }
  }

  return array_keys($implementations[$hook]);
}

/**
 * Implements hook_exit().
 *
 * @see module_implements_write_cache()
 */
function skinr_exit($destination = NULL) {
  $implementations = &drupal_static('skinr_implements');
  // Check whether we need to write the cache. We do not want to cache hooks
  // which are only invoked on HTTP POST requests since these do not need to be
  // optimized as tightly, and not doing so keeps the cache entry smaller.
  if (isset($implementations['#write_cache']) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')) {
    unset($implementations['#write_cache']);
    cache_set('skinr_implements', $implementations, 'cache_bootstrap');
  }
}

/**
 * Invoke a hook in all enabled modules and themes that implement it.
 *
 * Replacement for module_invoke_all() that only invokes modules that implement
 * the current version of Skinr API. It also supports $module.skinr.inc files
 * in themes and custom paths.
 *
 * @param $hook
 *   The name of the hook to invoke.
 * @param ...
 *   Arguments to pass to the hook.
 *
 * @return
 *   An array of return values of the hook implementations. If modules return
 *   arrays from their implementations, those are merged into one array.
 */
function skinr_invoke_all($hook) {
  $args = func_get_args();
  // Remove $hook from the arguments.
  unset($args[0]);
  $return = array();
  foreach (skinr_implements($hook) as $module) {
    $function = $module . '_' . $hook;
    if (function_exists($function)) {
      $result = call_user_func_array($function, $args);
      if (isset($result) && is_array($result)) {
        $return = array_merge_recursive($return, $result);
      }
      elseif (isset($result)) {
        $return[] = $result;
      }
    }
  }

  return $return;
}

/**
 * Loads a $module.skinr.inc include file.
 */
function skinr_load_include($file) {
  if (is_file($file)) {
    include_once $file;
    return $file;
  }
  return FALSE;
}

/**
 * Includes Skinr plugin files for an extension, if any.
 *
 * @param $extension
 *   The API information for an extension, as returned by skinr_implements_api().
 */
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' => TRUE,
      'min_depth' => 1,
      'callback' => 'skinr_include_once',
    ));
  }
  return $loaded[$extension['name']];
}

/**
 * file_scan_directory() callback wrapper around include_once.
 *
 * include_once is a PHP construct, not a function, so it cannot be invoked
 * directly as 'callback' in file_scan_directory().
 */
function skinr_include_once($file) {
  include_once $file;
}

// -----------------------------------------------------------------------
// Skinr data handling functions.

/**
 * Validate a skinr object.
 *
 * @param $skin
 *   A skin object.
 *
 * @return
 *   TRUE on success, FALSE on failure.
 */
function skinr_skin_validate(&$skin) {
  if (empty($skin->theme) || empty($skin->module) || empty($skin->element) || empty($skin->skin) || empty($skin->options)) {
    return FALSE;
  }
  if (!is_array($skin->options)) {
    return FALSE;
  }

  // Strip empty skins.
  $skin->options = _skinr_array_strip_empty($skin->options);
  if (empty($skin->options)) {
    return FALSE;
  }

  return TRUE;
}

/**
 * Save a skin object.
 *
 * @param $skin
 *   A skin object.
 *
 * @return
 *   TRUE on success, FALSE on failure.
 */
function skinr_skin_save(&$skin) {
  // Make sure we're getting valid data.
  if (!skinr_skin_validate($skin)) {
    return FALSE;
  }

  // Load the stored skin configuration object, if any.
  if (!empty($skin->sid)) {
    if (!isset($skin->original)) {
      // Load an uncached version of the skin configuration object.
      $skin->original = skinr_skin_load_unchanged($skin->sid);
    }
  }

  // Let modules modify the node before it is saved to the database.
  module_invoke_all('skinr_skin_presave', $skin);

  if (!empty($skin->sid)) {
    // Record exists, so let's update.
    $status = drupal_write_record('skinr_skins', $skin, 'sid');
    module_invoke_all('skinr_skin_update', $skin);
  }
  else {
    // Insert a new record.
    $status = drupal_write_record('skinr_skins', $skin);
    module_invoke_all('skinr_skin_insert', $skin);
  }

  // Clear internal properties.
  unset($skin->original);
  // Clear the static loading cache.
  // @todo Once we have a more granular reset for skinr_skin_load_multiple(), we
  //   need to use it here.
  drupal_static_reset('skinr_skin_load_multiple');

  return $status;
}

/**
 * Delete a skin object.
 *
 * @param $sid
 *   The skin configuration ID.
 */
function skinr_skin_delete($sid) {
  skinr_skin_delete_multiple(array($sid));
}

/**
 * Delete multiple skin configuration objects.
 *
 * @param $sids
 *   An array of skin configuration IDs.
 */
function skinr_skin_delete_multiple($sids) {
  $transaction = db_transaction();
  if (!empty($sids)) {
    $skins = skinr_skin_load_multiple($sids);

    try {
      foreach ($skins as $sid => $skin) {
        module_invoke_all('skinr_skin_delete', $skin);
      }

      // Delete after calling hooks so that they can query node tables as needed.
      db_delete('skinr_skins')
        ->condition('sid', $sids, 'IN')
        ->execute();
    }
    catch (Exception $e) {
      $transaction->rollback();
      watchdog_exception('skinr', $e);
      throw $e;
    }

    // Clear the skinr_skin_load_multiple cache.
    drupal_static_reset('skinr_skin_load_multiple');
  }
}

/**
 * Load a skin configuration object from the database.
 *
 * @param $sid
 *   The skin configuration ID.
 *
 * @return
 *   A fully-populated skin configuration object.
 */
function skinr_skin_load($sid = NULL) {
  $sids = (isset($sid) ? array($sid) : array());
  $skin = skinr_skin_load_multiple($sids);
  return $skin ? reset($skin) : FALSE;
}

/**
 * Load skin configuration objects from the database.
 *
 * This function should be used whenever you need to load more than one skin
 * configuration from the database. Skin configurations are loaded into memory
 * and will not require database access if loaded again during the same page
 * request.
 *
 * @see skinr_skin_get_sids()
 *
 * @param $sids
 *   An array of skin configuration IDs.
 *
 * @return
 *   An array of skin configuration objects indexed by sid.
 */
function skinr_skin_load_multiple($sids = array()) {
  // @todo Do we want to write a more granular cache reset?
  $skins = &drupal_static(__FUNCTION__, array());

  // Create a new variable which is either a prepared version of the $sids
  // array for later comparison with cached skin configuration objects, or FALSE
  // if no $sids were passed. The $sids array is reduced as items are loaded
  // from cache, and we need to know if it's empty for this reason to avoid
  // querying the database when all requested skin configuration objects are
  // loaded from cache.
  $passed_sids = !empty($sids) ? array_flip($sids) : FALSE;
  if ($passed_sids) {
    $sids = array_keys(array_diff_key($passed_sids, $skins));
  }

  // Load any remaining skin configurations from the database. This is the
  // case if $sids is set to FALSE (so we load all skins), or if there are any
  // sids left to load.
  if ($sids === FALSE || $sids) {
    // Build the query.
    $query = db_select('skinr_skins', 's')
      ->fields('s');
    if ($sids !== FALSE) {
      $query->condition('sid', $sids);
    }
    $queried_skins = $query->execute()
      ->fetchAllAssoc('sid');

    foreach ($queried_skins as $sid => $skin) {
      // Unserialize options array.
      $queried_skins[$sid]->options = unserialize($skin->options);

      // Let modules modify the skin configurations.
      module_invoke_all('skinr_skin_load', $queried_skins[$sid]);
    }
    $skins += $queried_skins;
  }

  // Ensure that the returned array is ordered the same as the original
  // $sids array if this was passed in and remove any invalid sids.
  if ($passed_sids) {
    // Remove any invalid sids from the array.
    $passed_sids = array_intersect_key($passed_sids, $skins);
    $return = array();
    foreach ($passed_sids as $sid => $ignore) {
      $return[$sid] = $skins[$sid];
    }
  }
  else {
    $return = $skins;
  }

  return $return;
}

/**
 * Load an uncached version of a skin configuration object.
 *
 * @param $sid
 *   The skin configuration ID.
 *
 * @return
 *   A fully-populated skin configuration object.
 */
function skinr_skin_load_unchanged($sid) {
  // Load an uncached version of the skin configuration object.
  $skin = db_query("SELECT * FROM {skinr_skins} WHERE sid = :sid", array(
    ':sid' => $sid,
  ))
  ->fetchObject();

  // Unserialize options array.
  $skin->options = unserialize($skin->options);

  // Let modules modify the skin configuration.
  module_invoke_all('skinr_skin_load', $skin);

  return $skin;
}

/**
 * Get skin configuration IDs.
 *
 * @param $filter_by
 *   An associative array whose keys are:
 *   - theme: (optional) The theme.
 *   - module: (optional) The module.
 *   - element: (optional) The element ID.
 *   - skin: (optional) The skin name.
 *   - status: (optional) Boolean indicating whether or not this skin
 *     configuration is enabled.
 *
 * @return
 *   An array of skin configuration IDs.
 */
function skinr_skin_get_sids($filter_by = array()) {
  $query = db_select('skinr_skins', 's')
      ->fields('s', array('sid'));
  if (isset($filter_by['theme'])) {
    $query->condition('theme', $filter_by['theme']);
  }
  if (isset($filter_by['module'])) {
    $query->condition('module', $filter_by['module']);
  }
  if (isset($filter_by['element'])) {
    $query->condition('element', $filter_by['element']);
  }
  if (isset($filter_by['skin'])) {
    $query->condition('skin', $filter_by['skin']);
  }
  if (isset($filter_by['status'])) {
    $query->condition('status', $filter_by['status']);
  }
  return $query->execute()
    ->fetchCol();
}

/**
 * Helper function to remove empty skins from an array.
 *
 * @param $array
 *   A single or multi-dimensional array to strip of empty values.
 *
 * @return
 *   An array stripped of empty values.
 */
function _skinr_array_strip_empty($array) {
  $new_array = array();
  foreach ($array as $key => $value) {
    if (is_array($value)) {
      $value = _skinr_array_strip_empty($value);
    }
    if (!empty($value)) {
      $new_array[$key] = $value;
    }
  }
  return $new_array;
}

/**
 * Helper function to retrieve the current theme.
 *
 * The global variable $theme_key doesn't work for our purposes when an admin
 * theme is enabled.
 *
 * @param $exclude_admin_theme
 *   Optional. Set to TRUE to exclude the admin theme from possible themes to
 *   return.
 *
 * @return
 *   The current theme name.
 */
function skinr_current_theme($exclude_admin_theme = FALSE) {
  global $user, $custom_theme;

  if (!empty($user->theme) && drupal_theme_access($user->theme)) {
    $current_theme = $user->theme;
  }
  elseif (!empty($custom_theme) && drupal_theme_access($custom_theme) && !($exclude_admin_theme && $custom_theme == variable_get('admin_theme', '0'))) {
    // Don't return the admin theme if we're editing skinr settings.
    $current_theme = $custom_theme;
  }
  else {
    $current_theme = variable_get('theme_default', 'bartik');
  }
  return $current_theme;
}

/**
 * Prepare the default status for a skin.
 *
 * @param $skin_info
 *   Information about a registered skin.
 *
 * @return
 *   An array of default statuses for each enabled theme.
 */
function skinr_skin_info_status_default($skin_info) {
  $status = array();
  // Retrieve the explicit default status of the registering theme for itself.
  $base_theme_status = NULL;
  if (isset($skin_info['status'][$skin_info['source']['name']])) {
    $base_theme_status = $skin_info['status'][$skin_info['source']['name']];
  }
  // Retrieve the sub themes of the base theme that registered the skin.
  $sub_themes = array();
  if (isset($skin_info['source']['sub themes'])) {
    $sub_themes = $skin_info['source']['sub themes'];
  }
  $themes = list_themes();
  foreach ($themes as $name => $theme) {
    if (!$theme->status) {
      continue;
    }
    // If this theme is a sub theme of the theme that registered the skin, check
    // whether we need to inherit the status of the base theme to the sub theme.
    // This is the case when a skin of a base theme enables itself for the base
    // theme (not knowing about potential sub themes).
    if (isset($base_theme_status) && isset($sub_themes[$name])) {
      $status[$name] = $base_theme_status;
    }
    // Apply global default.
    $status += array($name => $skin_info['default status']);
  }
  // Lastly, apply all explicit defaults.
  $status = array_merge($status, $skin_info['status']);

  return $status;
}

/**
 * Retrieve the overridden status of a skin.
 *
 * @param $skin_info
 *   Information about a registered skin.
 *
 * @return
 *   An array of statuses for each enabled theme. If no overrides are found,
 *   the status defaults will be returned.
 */
function skinr_skin_info_status_get($skin_info) {
  return variable_get('skinr_skin_' . $skin_info['name'] . '_status', $skin_info['status']);
}

/**
 * Set the status of a skin. Overrides the skin plugin settings.
 *
 * @param $skin_info
 *   Information about a registered skin.
 * @param $status
 *   An array of statuses for each theme.
 */
function skinr_skin_info_status_set($skin_info, $status) {
  variable_set('skinr_skin_' . $skin_info['name'] . '_status', $status);
}

/**
 * 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://')
 * or relative to the site's root (e.g. the url starts with '/') the path does
 * not get prepended.
 *
 * @param $files
 *   A an array of filenames that need the path prepended.
 *   @todo Adjust docs to account for arrays instead of filenames.
 * @param $path
 *   The path to prepend.
 */
function _skinr_add_path_to_files(&$files, $path) {
  $newfiles = array();
  foreach ($files as $data => $options) {
    if (!is_array($options)) {
      // $options is not an array, it's a filename and passed as first
      // (and only) argument.
      if (_skinr_is_local_file($path . '/' . $options)) {
        $options = $path . '/' . $options;
      }
      $newfiles[] = $options;
    }
    elseif (is_numeric($data)) {
      // If $options is an array, but $data is not a filename, find $data in the
      // $options array.
      if (_skinr_is_local_file($path . '/' . $options['data'])) {
        $options['data'] = $path . '/' . $options['data'];
      }
      $newfiles[] = $options;
    }
    else {
      if (_skinr_is_local_file($path . '/' . $data)) {
        $data = $path . '/' . $data;
      }
      $newfiles[$data] = $options;
    }
  }
  $files = $newfiles;
}

/**
 * Helper function to determine whether or not a given file is local or not.
 */
function _skinr_is_local_file($file) {
  if (strpos($file, 'http://') === 0 || strpos($file, 'https://') === 0 || strpos($file, '/') === 0) {
    return FALSE;
  }
  if (!file_exists($file)) {
    return FALSE;
  }
  return TRUE;
}

/**
 * Parse a skin_infos array as returned from a skins plugin.
 *
 * This function inserts any missing defaults and updates the stylesheet and
 * script paths to be relative to Drupal's root.
 *
 * @param $skin_infos
 *   An array of skins as returned from skin plugins.
 * @param $source
 *   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) {
    // Populate default properties.
    $skin_infos[$skin_name] += array(
      'name' => '',
      'title' => '',
      'type' => 'checkboxes',
      'description' => '',
      'group' => 'general',
      'theme hooks' => array('*'),
      'attached' => array(),
      'options' => array(),
      'weight' => NULL,
      'default status' => 0,
      'status' => array(),
    );

    // Merge in name.
    $skin_infos[$skin_name]['name'] = $skin_name;

    // Merge in source information.
    $skin_infos[$skin_name]['source'] = $source;

    // Merge in default status for all themes.
    $skin_infos[$skin_name]['status'] = skinr_skin_info_status_default($skin_infos[$skin_name]);

    // 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']);
    }
    // Add path to scripts.
    if (isset($skin_infos[$skin_name]['attached']['js'])) {
      _skinr_add_path_to_files($skin_infos[$skin_name]['attached']['js'], $source['path']);
    }

    foreach ($skin_infos[$skin_name]['options'] as $option_name => $option) {
      // Add path to stylesheets.
      if (isset($option['attached']['css'])) {
        _skinr_add_path_to_files($skin_infos[$skin_name]['options'][$option_name]['attached']['css'], $source['path']);
      }
      // Add path to scripts.
      if (isset($option['attached']['js'])) {
        _skinr_add_path_to_files($skin_infos[$skin_name]['options'][$option_name]['attached']['js'], $source['path']);
      }

      // Validate class by running it through drupal_html_class().
      if (!is_array($skin_infos[$skin_name]['options'][$option_name]['class'])) {
        $skin_infos[$skin_name]['options'][$option_name]['class'] = array($skin_infos[$skin_name]['options'][$option_name]['class']);
      }
      foreach ($skin_infos[$skin_name]['options'][$option_name]['class'] as $key => $class) {
        $skin_infos[$skin_name]['options'][$option_name]['class'][$key] = drupal_html_class($class);
      }
    }
  }
}

/**
 * Retrieves all skins registered by modules and themes.
 *
 * @return
 *   An array of skins.
 */
function skinr_get_skin_info() {
  $skin_info = &drupal_static(__FUNCTION__);

  if (!isset($skin_info)) {
    if ($cached = cache_get('skinr_skin_info')) {
      $skin_info = $cached->data;
      return $skin_info;
    }
    $skin_info = array();

    foreach (skinr_implements_api() as $name => $extension) {
      $hooks = array();
      // Run through skinr_hook to ensure the required include gets loaded.
      if (skinr_hook($name, 'skinr_skin_info')) {
        $hooks["{$name}_skinr_skin_info"] = $extension;
      }

      // Load the extension's plugins, if any.
      if ($files = skinr_load_plugins($extension)) {
        // The base path for plugins is the directory defined by the extension.
        $dir = $extension['path'] . '/' . $extension['directory'];
        foreach ($files as $plugin => $file) {
          $hooks["{$name}_skinr_skin_{$plugin}_info"] = array(
            // The source path for a plugin is the plugin directory.
            'path' => $dir . '/' . basename(dirname($file->uri)),
            'filename' => $file->filename,
          ) + $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);

            $skin_info += $extension_info;
          }
        }
      }
    }

    // Allow modules to alter registered skin information.
    drupal_alter('skinr_skin_info', $skin_info);

    cache_set('skinr_skin_info', $skin_info);
  }

  return $skin_info;
}

/**
 * Retrieves all skin groups registered by modules and themes.
 *
 * @return
 *   An array of groups.
 */
function skinr_get_group_info() {
  $group_info = &drupal_static(__FUNCTION__);

  if (!isset($group_info)) {
    if ($cached = cache_get('skinr_group_info')) {
      $group_info = $cached->data;
      return $group_info;
    }
    $group_info = array();

    foreach (skinr_implements_api() as $name => $extension) {
      $hooks = array();
      // Run through skinr_hook to ensure the required include gets loaded.
      if (skinr_hook($name, 'skinr_group_info')) {
        $hooks["{$name}_skinr_group_info"] = $extension;
      }

      // Load the extension's plugins, if any.
      if ($files = skinr_load_plugins($extension)) {
        // The base path for plugins is the directory defined by the extension.
        $dir = $extension['path'] . '/' . $extension['directory'];
        foreach ($files as $plugin => $file) {
          $hooks["{$name}_skinr_group_{$plugin}_info"] = array(
            // The source path for a plugin is the plugin directory.
            'path' => $dir . '/' . basename(dirname($file->uri)),
            'filename' => $file->filename,
          ) + $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.
            foreach ($extension_info as &$group) {
              $group += array(
                'title' => '',
                'description' => '',
                'weight' => 0,
              );
            }
            $group_info += $extension_info;
          }
        }
      }
    }

    // Allow modules to alter groups through hook_skinr_group_info_alter().
    drupal_alter('skinr_group_info', $group_info);

    cache_set('skinr_group_info', $group_info);
  }

  return $group_info;
}

/**
 * Fetch Skinr configuration data from functionality plugins.
 *
 * @return
 *   An array of all configuration data.
 */
function skinr_get_config_info() {
  $config_info = &drupal_static(__FUNCTION__);

  if (!isset($config_info)) {
    if ($cached = cache_get('skinr_config_info')) {
      $config_info = $cached->data;
      return $config_info;
    }
    $config_info = skinr_invoke_all('skinr_config_info');

    // Allow modules to alter config info via hook_skinr_config_info_alter().
    drupal_alter('skinr_config_info', $config_info);

    cache_set('skinr_config_info', $config_info);
  }

  return $config_info;
}

/**
 * Provide a list of all available theme hooks for a given element.
 *
 * @param $module
 *   The module implementing given element.
 * @param $element
 *   An element.
 *
 * @return
 *   An array of theme hooks.
 */
function skinr_theme_hooks($module, $element) {
  $theme_hooks = &drupal_static(__FUNCTION__, array());

  if (!isset($theme_hooks[$module][$element])) {
    // Invoke hook_skinr_theme_hooks() and hook_skinr_theme_hooks_alter().
    $theme_hooks[$module][$element] = skinr_invoke_all('skinr_theme_hooks', $module, $element);
    drupal_alter('skinr_theme_hooks', $theme_hooks[$module][$element], $module, $element);
  }

  return $theme_hooks[$module][$element];
}

/**
 * Implements hook_modules_enabled().
 */
function skinr_modules_enabled() {
  skinr_cache_reset();
}

/**
 * Implements hook_modules_disabled().
 */
function skinr_modules_disabled() {
  skinr_cache_reset();
}

/**
 * Implements hook_themes_enabled().
 */
function skinr_themes_enabled() {
  skinr_cache_reset();
}

/**
 * Implements hook_themes_disabled().
 */
function skinr_themes_disabled() {
  skinr_cache_reset();
}

/**
 * Helper function for built-in integration code.
 */
function skinr_skinr_api_modules() {
  return array(
    'path' => drupal_get_path('module', 'skinr') . '/modules',
  );
}

function block_skinr_api_2() {
  return skinr_skinr_api_modules();
}

function comment_skinr_api_2() {
  return skinr_skinr_api_modules();
}

function node_skinr_api_2() {
  return skinr_skinr_api_modules();
}

function system_skinr_api_2() {
  return skinr_skinr_api_modules();
}

function views_skinr_api_2() {
  return skinr_skinr_api_modules();
}
