You are here

magic.assets.inc in Magic 7.2

A file to contain functions for the magic module to abuse.

File

includes/magic.assets.inc
View source
<?php

/**
 * @file
 * A file to contain functions for the magic module to abuse.
 */

/**
 * Helper function for generating a regex from a list of paths.
 *
 * Generates a single regex from a list of file paths that can be used to match
 * JS or CSS files using preg_grep() for example in hook_css_alter() or
 * hook_js_alter(). The '*' (asterisk) character can be used as a wild-card.
 *
 * @param $paths
 *   An array of file paths.
 *
 * @return string
 *   The generated regex.
 *
 * @see hook_js_alter()
 * @see hook_css_alter()
 */
function magic_assets_prepare_regex($paths) {
  $profile = drupal_get_profile();
  $site = preg_quote(conf_path(), '/');
  $themes = array();
  foreach ($GLOBALS['base_theme_info'] as $info) {
    $themes[] = preg_quote(dirname($info->filename), '/');
  }
  $tokens = array(
    ':all' => '.+',
    ':core' => '(?:misc|modules|themes)\\/.+',
    ':contrib' => "(?:sites\\/all\\/modules|{$site}\\/modules|profiles\\/{$profile}\\/modules)\\/.+",
    ':current-theme' => preg_quote(drupal_get_path('theme', $GLOBALS['theme_key']), '/') . "\\/.+",
    ':base-theme' => $themes ? '(?:' . implode('|', $themes) . ')\\/.+' : FALSE,
  );
  foreach ($paths as &$item) {

    // The first segment (everything before the first slash) is the namespace.
    // This rule only applies to local files... So if the namespace can not be
    // mapped to a token, module, profile or theme engine we assume that we are
    // trying to target an external file.
    list($namespace) = explode('/', $item, 2);

    // Process token namespaces.
    if (isset($tokens[$namespace]) && empty($tokens[$namespace])) {
      unset($item);
      continue;
    }
    elseif ($namespace != '*' && !isset($tokens[$namespace])) {

      // Check if it refers to a theme, module, profile or theme engine.
      foreach (array(
        'theme',
        'module',
        'profile',
        'theme_engine',
      ) as $type) {

        // We can't use drupal_get_path() directly because that uses dirname()
        // internally which returns '.' if no filename was found.
        if ($filename = drupal_get_filename($type, $namespace, NULL, FALSE)) {
          $directory = preg_quote(dirname($filename), '/');

          // Now that we know about this namespace we can add it to the tokens
          // array for performance reasons.
          $tokens[$namespace] = $directory;
          break;
        }
      }
    }

    // Escape any regex characters and replace tokens and wildcards.
    $item = isset($tokens[$namespace]) ? substr($item, strlen($namespace)) : $item;
    $item = preg_quote($item, '/');
    $item = str_replace('\\*', '.*', $item);
    $item = isset($tokens[$namespace]) ? $tokens[$namespace] . $item : $item;
  }
  return empty($paths) ? FALSE : '/^' . implode('|', $paths) . '$/';
}

/**
 * Groups include/exclude patterns together.
 *
 * This is required to allow for complex chained includes/excludes. We can't do
 * this in a single, complicated regular expression as regex does not really
 * support this (negative lookbehinds are not sufficient).
 *
 * @param $items
 *   An array of include/exclude patterns.
 *
 * @return array
 *   The include/exclude patterns as groups.
 */
function magic_assets_regex_steps($items) {
  $switch = FALSE;
  $key = 0;
  $groups = array();

  // Iterate over all exclude/include paths and form groups. We start a new
  // group every time we switch from inclusion to exclusion.
  foreach ($items as $item) {

    // If an item starts with a "~" it is an inclusion pattern.
    if ($exclude = strpos($item, '~') === 0) {
      $item = substr($item, 1);
    }

    // Start a new group if we are switching between exclusion/inclusion.
    if ($switch !== $exclude && ($switch = !$switch) === FALSE) {
      $key++;
    }

    // Initialize the group with two empty arrays if it does not exist yet.
    if (!isset($groups[$key])) {
      $groups[$key] = array_fill(0, 2, array());
    }
    $groups[$key][!$switch ? 0 : 1][] = $item;
  }

  // Each group now needs to have its items converted to a regex string.
  foreach ($groups as &$group) {
    foreach (array(
      0,
      1,
    ) as $key) {
      $group[$key] = !empty($group[$key]) ? magic_assets_prepare_regex($group[$key]) : FALSE;
    }
  }
  return $groups;
}

/**
 * Eliminates elements from an array using a simplified regex pattern.
 *
 * @param $elements
 *   The array of elements that should have some of its items removed.
 * @param $steps
 *   A set of regex as generated by magic_prepare_regex().
 */
function magic_assets_exclude(&$elements, $steps) {
  $mapping = magic_assets_generate_mapping($elements);
  $remove = array();
  foreach ($steps as $step) {
    list($exclude, $include) = $step;
    $remove += preg_grep($exclude, !empty($remove) ? array_diff_key($mapping, $remove) : $mapping);
    if (!empty($include)) {
      $remove = array_diff_key($remove, preg_grep($include, $remove));
    }
  }
  $elements = array_diff_key($elements, $remove);
}

/**
 * Helper function for generating a map of assets based on the data attribute.
 *
 * We can not rely on the array keys of the JS and CSS file arrays in Drupal
 * because in case of inline JS or CSS (which uses numerical array keys) and due
 * to potential overrides of the 'data' attribute which holds the actual,
 * reliable path of the file. This function returns a single-level array of
 * reliable JS/CSS file paths using the original array keys as keys. Elements of
 * type 'inline' or 'setting' are ignored.
 *
 * @param $elements
 *   An array of JS or CSS files as given in hook_css_alter() or
 *   hook_js_alter().
 *
 * @return array
 *   A map of file paths generated from $elements.
 *
 * @see hook_js_alter()
 * @see hook_css_alter()
 */
function magic_assets_generate_mapping($elements) {
  $mapping = array();
  foreach ($elements as $key => $item) {
    if ($item['type'] == 'inline' || $item['type'] == 'setting') {

      // Naturally, in-line CSS is not supported.
      continue;
    }

    // We need to build an array containing just the 'data' attribute because
    // that's the actual path of the file. The array key of the elements can
    // be something else if someone is sneaky enough to use drupal_add_js() or
    // drupal_add_css() with a bogus first argument (normally, that is the
    // path to the file) and then specify the actual path through the 'data'
    // attribute in the $options array.
    $mapping[$key] = $item['data'];
  }
  return $mapping;
}

Functions

Namesort descending Description
magic_assets_exclude Eliminates elements from an array using a simplified regex pattern.
magic_assets_generate_mapping Helper function for generating a map of assets based on the data attribute.
magic_assets_prepare_regex Helper function for generating a regex from a list of paths.
magic_assets_regex_steps Groups include/exclude patterns together.