You are here

skinr.module in Skinr 8.2

Same filename and directory in other branches
  1. 6.2 skinr.module
  2. 6 skinr.module
  3. 7.2 skinr.module

Handles core Skinr functionality.

File

skinr.module
View source
<?php

/**
 * @file
 * Handles core Skinr functionality.
 */
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Component\Utility\Html;
use Drupal\skinr\Entity\Skin;

/**
 * The Skinr API version.
 */
const SKINR_VERSION = 3;

/**
 * A flag used to let us know if an object is in the database.
 */
const SKINR_STORAGE_IN_DATABASE = 0;
const SKINR_STORAGE_IN_CODE = 1;
const SKINR_STORAGE_IN_CODE_OVERRIDDEN = 2;

/**
 * Implements hook_help().
 */
function skinr_help($path, $arg) {
  switch ($path) {
    case 'help.page.skinr':
      return t('Visit the <a href="http://skinr.org/" target="_blank">Skinr.org</a> for full documentation.');
      break;
  }
}

/**
 * Implements hook_hook_info().
 */
function skinr_hook_info() {
  $hooks = array(
    'skinr_api_' . SKINR_VERSION,
    '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',
  ));
  $hooks['skinr_skin_defaults'] = array(
    'group' => 'skinr_default',
  );
  return $hooks;
}

/**
 * Clears cached Skinr information.
 */
function skinr_cache_reset() {

  // @todo Use tags instead?
  // skinr_context_group_defaults, skinr_skinable_hooks
  \Drupal::cache()
    ->deleteMultiple(array(
    'skinr_implements_api',
    'skinr_skin_defaults',
    'skinr_skin_info',
    'skinr_group_info',
    'skinr_config_info',
  ));
}

/**
 * Implements hook_library_info_build().
 */
function skinr_library_info_build() {
  $skin_infos = skinr_get_skin_info();
  $libraries = [];
  foreach ($skin_infos as $skin_name => $skin_info) {
    $path = base_path() . $skin_info['source']['path'] . '/';
    if (!empty($skin_info['attached'])) {
      $libraries['skinr.' . $skin_name] = _skinr_attached_to_library($skin_info['attached'], $path);
    }
    foreach ($skin_info['options'] as $option => &$data) {
      if (!empty($data['attached'])) {
        $libraries['skinr.' . $skin_name . '.' . $option] = _skinr_attached_to_library($data['attached'], $path);
      }
    }
  }
  return $libraries;
}

/**
 * Helper function to turn skin plugins attached arrays into a library.
 *
 * @see skinr_library_info_build()
 */
function _skinr_attached_to_library($attached, $path) {

  // CSS.
  if (isset($attached['css'])) {
    foreach ($attached['css'] as &$category) {
      _skinr_fix_path($category, $path);
    }
  }

  // Javascript.
  if (isset($attached['js'])) {
    _skinr_fix_path($attached['js'], $path);
  }
  return $attached;
}

/**
 * Helper function to fix library paths for skin plugins.
 *
 * @see skinr_library_info_build()
 */
function _skinr_fix_path(&$data, $path) {
  $is_local_uri = function ($uri) {
    $scheme = \Drupal::service('file_system')
      ->uriScheme($uri);
    return $scheme === FALSE || \Drupal::service('stream_wrapper_manager')
      ->getViaScheme($scheme) instanceof \Drupal\Core\StreamWrapper\LocalStream;
  };
  $new = [];
  foreach ($data as $source => $options) {
    if ($is_local_uri($source)) {
      $new[$path . $source] = $options;
    }
    else {
      $new[$source] = $options;
    }
  }
  $data = $new;
}

/**
 * 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 (defined('MAINTENANCE_MODE')) {
    return;
  }
  $data =& drupal_static(__FUNCTION__);

  // Caching the data returned from the following functions is reported to
  // improve performance.
  if (!isset($data)) {
    $data['current_theme'] = skinr_current_theme();
    $data['skin_info'] = skinr_get_skin_info();
    $data['theme_registry'] = \Drupal::service('theme.registry')
      ->getRuntime();
    $data['skip_cache'] = FALSE;
    $implementations = \Drupal::moduleHandler()
      ->getImplementations('skinr_preprocess_alter');

    // skinr_panels is special case that only runs when skinr_context is enabled.
    if (count($implementations) > 0 && $implementations[0] !== 'skinr_panels') {

      // Skip caching whenever a module implements skinr_preprocess_alter.
      $data['skip_cache'] = TRUE;
    }
  }

  // @todo Use $variables['theme_hook_original'] ?
  $original_hook = $hook;

  // dpm($hook . '|' . $variables['theme_hook_original']);
  // An array of $elements based on $module and $original_hook, derived from $variables.
  $implementations = \Drupal::cache('bootstrap')
    ->get('skinr_elements');
  $array_elements = skinr_invoke_all('skinr_elements', $variables, $original_hook, 'preprocess');
  foreach ($array_elements as $element_type => $elements) {
    $applied_skins = array();
    foreach ($elements as $element) {
      $cid = 'skinr_preprocess:' . $element_type . ':' . $element . ':' . $data['current_theme'];
      if (!$data['skip_cache'] && ($cached = \Drupal::cache()
        ->get($cid))) {

        // This type of caching is incompatible with skinr_context.
        $applied_skins = $cached->data;
      }
      else {
        $properties = array(
          'theme' => $data['current_theme'],
          'element_type' => $element_type,
          'element' => $element,
          'status' => 1,
        );
        $skins = entity_load_multiple_by_properties('skin', $properties);

        // Invoke hook_skinr_preprocess_alter() in all modules.
        $context = array(
          'hook' => $hook,
          'variables' => &$variables,
          'theme' => $data['current_theme'],
          'element_type' => $element_type,
          'elements' => $elements,
        );
        \Drupal::moduleHandler()
          ->alter('skinr_preprocess', $skins, $context);
        $applied_skins = array();
        foreach ($skins as $skin) {
          $applied_skins = array(
            $skin->skin => $skin
              ->getOptions(),
          ) + $applied_skins;
        }

        // Cache data.
        if (!$data['skip_cache']) {
          \Drupal::cache('data')
            ->set($cid, $applied_skins, CacheBackendInterface::CACHE_PERMANENT, array(
            'skinr:' . $element_type . ':' . $element,
          ));
        }
      }
    }

    // Attach JS and CSS, and add classes.
    if (!empty($applied_skins)) {
      foreach ($applied_skins as $skin_name => $skin_options) {

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

        // Make sure this skin is enabled for the current theme.
        $status = skinr_skin_info_status_get($skin_info);
        if (empty($status[$data['current_theme']])) {
          continue;
        }
        if (isset($skin_info['attached'])) {
          $variables['#attached']['library'][] = 'skinr/skinr.' . $skin_name;
        }
        if (!is_array($skin_options)) {
          $skin_options = array(
            $skin_options,
          );
        }
        foreach ($skin_options as $skin_option) {
          if (isset($data['skin_info'][$skin_name]['options'][$skin_option]['attached'])) {
            $variables['#attached']['library'][] = 'skinr/skinr.' . $skin_name . '.' . $skin_option;
          }
        }
      }
      $flattened_skins_array = skinr_flatten_skins_array($applied_skins);
      if (isset($variables['attributes']['class'])) {
        $variables['attributes']['class'] = array_merge($variables['attributes']['class'], $flattened_skins_array);
      }
      else {
        $variables['attributes']['class'] = $flattened_skins_array;
      }
    }
  }
}

/**
 * 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 (!is_array($options)) {
      $options = array(
        $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() {

  // All themes are disabled while running update script so theme skins are not
  // cached. Don't cache to prevent this.
  if (defined('MAINTENANCE_MODE')) {
    return array();
  }
  $cache =& drupal_static(__FUNCTION__);
  if (!isset($cache)) {
    if ($cached = \Drupal::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 (\Drupal::moduleHandler()
      ->getImplementations('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.
      // ModuleHandler::getImplementations() 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;
        }
      }
      else {

        // If there is a $module.skinr.inc in the module's root, it gets
        // auto-loaded for any hooks. But if a skin defined thering contains a
        // custom 'form callback' function, we'll need to load it manually. So
        // store the file's info.
        $file = drupal_get_path('module', $module) . '/' . $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.
    $theme_handler = \Drupal::service('theme_handler');
    $all_themes = $theme_handler
      ->listInfo();
    $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
    // \Drupal::service('theme_handler')->listInfo().
    $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()
        // @see \Drupal::theme()->getActiveTheme()
        $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;
        }
      }
    }
    \Drupal::cache()
      ->set('skinr_implements_api', $cache);
  }
  return $cache;
}

/**
 * Determine whether a module implements a hook.
 *
 * Replacement for ModuleHandler::implementsHook() 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. ModuleHandler::implementsHook()
      // only auto-loads $module.skinr.inc in a module's root folder.
      skinr_load_include($extension['include file']);
      if (function_exists($function)) {
        return TRUE;
      }
    }
    else {

      // Run through ModuleHandler::implementsHook() to auto-load
      // $module.skinr.inc from a non-custom path.
      if (\Drupal::moduleHandler()
        ->implementsHook($module, $hook)) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Determine which modules are implementing a hook.
 *
 * Replacement for ModuleHandler::getImplementations() 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)) {

    // @todo Fix cache bin.
    $implementations = \Drupal::cache('bootstrap')
      ->get('skinr_implements');
    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.
        // ModuleHandler::getImplementations() and
        // ModuleHandler::implementsHook() 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 ModuleHandler::implementsHook() to auto-load
        // $module.skinr.inc from a non-custom path.
        if (\Drupal::moduleHandler()
          ->implementsHook($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::moduleHandler()
        ->alter('skinr_implements', $implementations[$hook], $hook);
    }
  }
  else {
    foreach ($implementations[$hook] as $module => $file) {
      if ($file) {
        skinr_load_include($file);
      }
      else {
        \Drupal::moduleHandler()
          ->implementsHook($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 ModuleHandler::implementsHook() 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()
 * @todo hook_exit() no longer exists.
 */
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']);

    // @todo Fix cache bin.
    \Drupal::cache('bootstrap')
      ->set('skinr_implements', $implementations);
  }
}

/**
 * Invoke a hook in all enabled modules and themes that implement it.
 *
 * Replacement for ModuleHandler::invokeAll() 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;
}

// -----------------------------------------------------------------------
// 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 &$skin) {

  // Make sure we're getting valid data.
  if (!skinr_skin_validate($skin)) {
    return FALSE;
  }
  return $skin
    ->save();
}

/**
 * Get skin configuration IDs.
 */
function skinr_skin_uuid_to_sid($uuid) {
  $sids =& drupal_static(__FUNCTION__, array());
  if (!isset($sids[$uuid])) {
    $sids[$uuid] = db_query("SELECT sid FROM {skinr_skins} WHERE uuid = :uuid", array(
      ':uuid' => $uuid,
    ))
      ->fetchField();
  }
  return $sids[$uuid];
}

/**
 * Get skin configuration IDs.
 */
function skinr_skin_sid_to_uuid($sid) {
  $uuids =& drupal_static(__FUNCTION__, array());
  if (!isset($uuids[$sid])) {
    $uuids[$sid] = db_query("SELECT uuid FROM {skinr_skins} WHERE sid = :sid", array(
      ':sid' => $sid,
    ))
      ->fetchField();
  }
  return $uuids[$sid];
}

/**
 * Load a skin configuration object from the database using UUID.
 *
 * @param $uuid
 *   The UUID of the skin configuration to load.
 *
 * @return
 *   A fully-populated skin configuration object.
 */
function skinr_skin_load_by_uuid($uuid) {
  if (!($sid = skinr_skin_uuid_to_sid($uuid))) {
    return FALSE;
  }

  // Run this through skinr_skin_load_multiple() to preserve caching.
  $skin = entity_load_multiple('skin', array(
    $sid,
  ));
  return $skin ? reset($skin) : FALSE;
}

/**
 * Returns all default skin configuration objects.
 */
function _skinr_skin_get_defaults() {
  $default_skins =& drupal_static(__FUNCTION__);
  if (!isset($default_skins)) {
    if ($cached = \Drupal::cache()
      ->get('skinr_skin_defaults')) {
      $default_skins = $cached->data;
      return $default_skins;
    }

    // Don't use ModuleHandler::invokeAll() to prevent oddly merged defaults.
    $default_skins = array();
    foreach (\Drupal::moduleHandler()
      ->getImplementations('skinr_skin_defaults') as $module) {
      $function = $module . '_skinr_skin_defaults';
      if (function_exists($function)) {
        $result = call_user_func_array($function, array());
        if (isset($result) && is_array($result)) {
          $default_skins = array_merge($default_skins, $result);
        }
        elseif (isset($result)) {
          $default_skins[] = $result;
        }
      }
    }

    // Let modules modify the skin configuration.
    \Drupal::moduleHandler()
      ->alter('skinr_skin_defaults', $default_skins);
    \Drupal::cache()
      ->set('skinr_skin_defaults', $default_skins);
  }
  return $default_skins;
}

/**
 * Imports default skin confiuration objects from code into database.
 *
 * @param $module_name
 *   Limit importing of defaults to a single module.
 * @param $force
 *   If FALSE (default) the default skin will only be imported if the skin
 *   configuration doesn't exist, or if storage is default in code. If TRUE, the
 *   skin configuration in code will always be overwritten.
 *
 * @return
 *  If an import failed, returns FALSE. If all imports succeeded, returns TRUE.
 */
function skinr_skin_defaults_import($module_name = NULL, $force = FALSE) {
  if (isset($module_name)) {
    if (!($default_skins = \Drupal::moduleHandler()
      ->invoke($module_name, 'skinr_skin_defaults'))) {
      $default_skins = array();
    }
    \Drupal::moduleHandler()
      ->alter('skinr_skin_defaults', $default_skins);
  }
  else {
    $default_skins = _skinr_skin_get_defaults();
  }
  $status = TRUE;
  foreach ($default_skins as $skin) {
    $status = skinr_skin_import($skin, $force) && $status;
  }
  return $status;
}

/**
 * Revert a skin configuration object that's stored in code to its default state.
 *
 * @param $sid
 *   A skin configuration ID.
 *
 * @return
 *  If revert failed, returns FALSE. If it succeeded, returns TRUE.
 */
function skinr_skin_revert($sid) {
  if (!($uuid = skinr_skin_sid_to_uuid($sid))) {
    return FALSE;
  }
  $default_skins = _skinr_skin_get_defaults();
  if (!isset($default_skins[$uuid])) {
    return FALSE;
  }
  if ($status = skinr_skin_import($default_skins[$uuid], TRUE)) {
    drupal_static_reset('skinr_skin_uuid_to_sid');
    drupal_static_reset('skinr_skin_sid_to_uuid');
  }
  return $status;
}

/**
 * Import a skin configuration object as defined in skinr_skin_defaults().
 *
 * @param $default_skin
 *   A skin configuration object.
 * @param $force
 *   If FALSE (default) the default skin will only be imported if the skin
 *   configuration doesn't exist, or if storage is default in code. If TRUE, the
 *   skin configuration in code will always be overwritten.
 *
 * @return
 *  If import failed, returns FALSE. If it succeeded, returns TRUE.
 */
function skinr_skin_import($default_skin, $force = FALSE) {

  // Make sure we don't modify the cached default skin array.
  $skin = clone $default_skin;

  // Functionality is abstracted for use in skinr_skin_storage().
  _skinr_skin_import($skin);
  if (!$force && !empty($skin->sid)) {

    // Load existing skin configuration from DB.
    $db_skin = entity_load('skin', $skin->sid, TRUE);

    // Sync status.
    $skin->status = $db_skin->status;
    if ($skin != $db_skin) {

      // Skin exists and is overridden, so cancel import.
      \Drupal::logger('skinr')
        ->warning('Canceled import of skin with UUID %uuid and SID %sid. It is overridden.', array(
        '%uuid' => $skin->uuid,
        '%sid' => $skin->sid,
      ));
      return FALSE;
    }
  }
  if ($status = skinr_skin_save($skin)) {
    drupal_static_reset('skinr_skin_uuid_to_sid');
    drupal_static_reset('skinr_skin_sid_to_uuid');
    \Drupal::logger('skinr')
      ->notice('Imported skin with UUID %uuid and SID %sid.', array(
      '%uuid' => $skin->uuid,
      '%sid' => $skin->sid,
    ));
  }
  else {
    \Drupal::logger('skinr')
      ->warning('Failed to import skin with UUID %uuid and SID %sid.', array(
      '%uuid' => $skin->uuid,
      '%sid' => isset($skin->sid) ? $skin->sid : t('unknown'),
    ));
  }
  return $status;
}
function _skinr_skin_import(&$skin) {
  if ($sid = skinr_skin_uuid_to_sid($skin->uuid)) {
    $skin->sid = $sid;
  }

  // Let modules modify the skin configuration.
  \Drupal::moduleHandler()
    ->alter('skinr_skin_import', $skin);
}

/**
 * Export var function.
 *
 * @see features_var_export()
 */
function skinr_var_export($var, $prefix = '', $init = TRUE) {
  if (is_object($var)) {
    $output = method_exists($var, 'export') ? $var
      ->export() : skinr_var_export((array) $var, '', FALSE);
  }
  else {
    if (is_array($var)) {
      if (empty($var)) {
        $output = 'array()';
      }
      else {
        $output = "array(\n";
        foreach ($var as $key => $value) {

          // Using normal var_export on the key to ensure correct quoting.
          $output .= "  " . var_export($key, TRUE) . " => " . skinr_var_export($value, '  ', FALSE) . ",\n";
        }
        $output .= ')';
      }
    }
    else {
      if (is_bool($var)) {
        $output = $var ? 'TRUE' : 'FALSE';
      }
      else {
        if (is_int($var)) {
          $output = intval($var);
        }
        else {
          if (is_numeric($var)) {
            $output = floatval($var);
          }
          else {
            if (is_string($var) && strpos($var, "\n") !== FALSE) {

              // Replace line breaks in strings with a token for replacement
              // at the very end. This protects whitespace in strings from
              // unintentional indentation.
              $var = str_replace("\n", "***BREAK***", $var);
              $output = var_export($var, TRUE);
            }
            else {
              $output = var_export($var, TRUE);
            }
          }
        }
      }
    }
  }
  if ($prefix) {
    $output = str_replace("\n", "\n{$prefix}", $output);
  }
  if ($init) {
    $output = str_replace("***BREAK***", "\n", $output);
  }
  return $output;
}

/**
 * Export object function.
 *
 * @see ctools_object_export()
 */
function skinr_object_export($object, $identifier, $prefix = '') {
  $output = $prefix . '$' . $identifier . ' = new ' . get_class($object) . "();\n";
  $output .= $prefix . '$' . $identifier . '->status = ';
  if ($object->status) {
    $output .= 'TRUE; /* Edit this to false to make a default ' . $identifier . ' disabled initially */' . "\n";
  }
  else {
    $output .= 'FALSE; /* Edit this to true to make a default ' . $identifier . ' enabled initially */' . "\n";
  }
  $output .= $prefix . '$' . $identifier . '->api_version = ' . SKINR_VERSION . ";\n";
  foreach ($object as $field => $value) {
    if ($field == 'status') {
      continue;
    }
    $output .= $prefix . '$' . $identifier . '->' . $field . ' = ' . skinr_var_export($value, $prefix) . ";\n";
  }
  return $output;
}

/**
 * Output a skin configuration object as code suitable for skinr_skin_defaults().
 *
 * @param $skin
 *   A skin configuration object.
 * @param $prefix
 *   A string to prefix the code with, used to indent the resulting code.
 *
 * @return
 *   A string.
 */
function skinr_skin_export($skin, $prefix = '') {

  // Make sure we don't modify the cached skin object.
  $skin = clone $skin;

  // Let modules modify the skin configuration.
  \Drupal::moduleHandler()
    ->alter('skinr_skin_export', $skin, $prefix);

  // Remove site specific $sid.
  unset($skin->sid);
  $output = skinr_object_export($skin, 'skin', $prefix);
  $output .= $prefix . "\$skins['{$skin->uuid}'] = \$skin;\n";
  return $output;
}

/**
 * Returns a skin configuration object's storage method.
 *
 * @param $skin
 *   A skin configuration object.
 *
 * @return
 *   SKINR_STORAGE_IN_DATABASE if stored in the database,
 *   SKINR_STORAGE_IN_CODE if stored in code,
 *   SKINR_STORAGE_IN_CODE_OVERRIDDEN if stored in code and overridden in db.
 */
function skinr_skin_storage($skin) {
  $default_skins = _skinr_skin_get_defaults();
  $storage = SKINR_STORAGE_IN_DATABASE;
  if (isset($default_skins[$skin->uuid])) {
    $default_skin = clone $default_skins[$skin->uuid];

    // Make sure skin has same processing as import.
    _skinr_skin_import($default_skin);

    // API version is only used for export.
    unset($default_skin->api_version);

    // Status shouldn't influence overridden.
    $default_skin->status = $skin->status;
    $storage = SKINR_STORAGE_IN_CODE;
    if ($default_skin != $skin) {

      // Default was overridden.
      $storage = SKINR_STORAGE_IN_CODE_OVERRIDDEN;
    }
  }
  return $storage;
}

/**
 * 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 = \Drupal::entityQuery('skin');
  if (isset($filter_by['theme'])) {
    $query
      ->condition('theme', $filter_by['theme']);
  }
  if (isset($filter_by['element_type'])) {
    $query
      ->condition('element_type', $filter_by['element_type']);
  }
  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();
}

/**
 * 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.
 *
 * @param $exclude_admin_theme
 *   Optional. Set to TRUE to exclude the admin theme from possible themes to
 *   return.
 *
 * @return
 *   The current theme name.
 *
 * @see drupal_theme_initialize()
 */
function skinr_current_theme($exclude_admin_theme = FALSE) {
  global $user;

  // Drupal core, and modules such as themkey and og_theme, set the theme
  // through hook_custom_theme() or hook_menu() using 'theme callback', which
  // are all picked up by menu_get_custom_theme().
  $theme = \Drupal::theme()
    ->getActiveTheme()
    ->getName();
  if (!$theme) {
    dpm('Empty theme!');
  }
  return $theme;
  if ($exclude_admin_theme) {

    // @todo See https://api.drupal.org/api/drupal/core%21includes%21theme.inc/function/drupal_theme_initialize/8
    $theme_handler = \Drupal::service('theme_handler');
    $themes = $theme_handler
      ->listInfo();

    // Determine the active theme for the theme negotiator service. This includes
    // the default theme as well as really specific ones like the ajax base theme.
    $route_match = \Drupal::routeMatch();
    $theme = \Drupal::service('theme.negotiator')
      ->determineActiveTheme($route_match);

    // If no theme could be negotiated, or if the negotiated theme is not within
    // the list of enabled themes, fall back to the default theme output of core
    // and modules (similar to Stark, but without a theme extension at all). This
    // is possible, because _drupal_theme_initialize() always loads the Twig theme
    // engine.
    if (!$theme || !isset($themes[$theme])) {
      $theme = 'core';
    }
  }
  return $theme;
}

/**
 * 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) {
  $status = \Drupal::config('skinr.status')
    ->get($skin_info['name']);

  // We don't get a default returned for dynamically created config.
  if (is_null($status)) {
    $status = $skin_info['status'];
  }
  return $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) {

  // @todo Clear if default?
  \Drupal::configFactory()
    ->getEditable('skinr.status')
    ->set($skin_info['name'], $status)
    ->save();
}

/**
 * Retrieves all skins registered by modules and themes.
 *
 * @return
 *   An array of skins.
 */
function skinr_get_skin_info() {
  $skin_info =& drupal_static(__FUNCTION__);
  if ($skin_info === NULL) {
    if ($cached = \Drupal::cache()
      ->get('skinr_skin_info')) {
      $skin_info = $cached->data;
      return $skin_info;
    }
    $skin_info = [];
    $plugins = \Drupal::service('plugin.manager.skin')
      ->getDefinitions();
    foreach ($plugins as $plugin) {
      if (empty($plugin['skins'])) {
        continue;
      }
      $skin_info = $plugin['skins'];
    }

    // Allow modules to alter registered skin information.
    \Drupal::moduleHandler()
      ->alter('skinr_skin_info', $skin_info);
    \Drupal::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 ($group_info === NULL) {
    if ($cached = \Drupal::cache()
      ->get('skinr_group_info')) {
      $group_info = $cached->data;
      return $group_info;
    }
    $group_info = [];
    $plugins = \Drupal::service('plugin.manager.skin')
      ->getDefinitions();
    foreach ($plugins as $plugin) {
      if (empty($plugin['groups'])) {
        continue;
      }
      foreach ($plugin['groups'] as $group_name => $group_data) {
        $group_info[$group_name] = $group_data + [
          'title' => '',
          'description' => '',
          'weight' => 0,
        ];
      }
    }

    // Allow modules to alter groups through hook_skinr_group_info_alter().
    \Drupal::moduleHandler()
      ->alter('skinr_group_info', $group_info);
    \Drupal::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 = \Drupal::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::moduleHandler()
      ->alter('skinr_config_info', $config_info);
    \Drupal::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::moduleHandler()
      ->alter('skinr_theme_hooks', $theme_hooks[$module][$element], $module, $element);
  }
  return $theme_hooks[$module][$element];
}

/**
 * Implements hook_modules_enabled().
 */
function skinr_modules_enabled($modules) {
  skinr_cache_reset();
  foreach ($modules as $module) {
    skinr_skin_defaults_import($module);
  }
}

/**
 * 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_3() {
  return skinr_skinr_api_modules();
}
function comment_skinr_api_3() {
  return skinr_skinr_api_modules();
}
function node_skinr_api_3() {
  return skinr_skinr_api_modules();
}
function system_skinr_api_3() {
  return skinr_skinr_api_modules();
}
function views_skinr_api_3() {
  return skinr_skinr_api_modules();
}

Functions

Namesort descending Description
block_skinr_api_3
comment_skinr_api_3
node_skinr_api_3
skinr_cache_reset Clears cached Skinr information.
skinr_current_theme Helper function to retrieve the current theme.
skinr_exit Implements hook_exit().
skinr_flatten_skins_array Returns an array of classes.
skinr_get_config_info Fetch Skinr configuration data from functionality plugins.
skinr_get_group_info Retrieves all skin groups registered by modules and themes.
skinr_get_skin_info Retrieves all skins registered by modules and themes.
skinr_help Implements hook_help().
skinr_hook Determine whether a module implements a hook.
skinr_hook_info Implements hook_hook_info().
skinr_implements Determine which modules are implementing a hook.
skinr_implements_api Returns a list of extensions that implement this API version of Skinr.
skinr_invoke_all Invoke a hook in all enabled modules and themes that implement it.
skinr_library_info_build Implements hook_library_info_build().
skinr_load_include Loads a $module.skinr.inc include file.
skinr_modules_disabled Implements hook_modules_disabled().
skinr_modules_enabled Implements hook_modules_enabled().
skinr_object_export Export object function.
skinr_preprocess Implements hook_preprocess().
skinr_skinr_api_modules Helper function for built-in integration code.
skinr_skin_defaults_import Imports default skin confiuration objects from code into database.
skinr_skin_export Output a skin configuration object as code suitable for skinr_skin_defaults().
skinr_skin_get_sids Get skin configuration IDs.
skinr_skin_import Import a skin configuration object as defined in skinr_skin_defaults().
skinr_skin_info_status_get Retrieve the overridden status of a skin.
skinr_skin_info_status_set Set the status of a skin. Overrides the skin plugin settings.
skinr_skin_load_by_uuid Load a skin configuration object from the database using UUID.
skinr_skin_revert Revert a skin configuration object that's stored in code to its default state.
skinr_skin_save Save a skin object.
skinr_skin_sid_to_uuid Get skin configuration IDs.
skinr_skin_storage Returns a skin configuration object's storage method.
skinr_skin_uuid_to_sid Get skin configuration IDs.
skinr_skin_validate Validate a skinr object.
skinr_themes_disabled Implements hook_themes_disabled().
skinr_themes_enabled Implements hook_themes_enabled().
skinr_theme_hooks Provide a list of all available theme hooks for a given element.
skinr_var_export Export var function.
system_skinr_api_3
views_skinr_api_3
_skinr_array_strip_empty Helper function to remove empty skins from an array.
_skinr_attached_to_library Helper function to turn skin plugins attached arrays into a library.
_skinr_fix_path Helper function to fix library paths for skin plugins.
_skinr_skin_get_defaults Returns all default skin configuration objects.
_skinr_skin_import

Constants

Namesort descending Description
SKINR_STORAGE_IN_CODE
SKINR_STORAGE_IN_CODE_OVERRIDDEN
SKINR_STORAGE_IN_DATABASE A flag used to let us know if an object is in the database.
SKINR_VERSION The Skinr API version.