You are here

utilities.inc in Icon API 8

Same filename and directory in other branches
  1. 7 includes/utilities.inc

utilities.inc Provides useful functions and common tasks.

File

includes/utilities.inc
View source
<?php

/**
 * @file
 * utilities.inc
 * Provides useful functions and common tasks.
 */
use Drupal\Core\Extension\ExtensionDiscovery;

/**
 * Extracts an uploaded archive file to the specified directory.
 *
 * @param string $file
 *   The filepath or URI of the archive that should be extracted.
 * @param string $directory
 *   The directory or URI the archive should extract into.
 *
 * @return Archiver
 *   The Archiver object used to extract the archive.
 */
function icon_archive_extract($file, $directory) {
  $archiver = archiver_get_archiver($file);
  if (!$archiver) {
    throw new Exception(t('Cannot extract %file, not a valid archive.', array(
      '%file' => $file,
    )));
  }
  $files = $archiver
    ->listContents();

  // Determine if archive lives in a wrapped directory matching filename.
  $info = pathinfo($file);
  $archiver->sub_directory = str_replace('.' . $info['extension'], '', $info['basename']);
  foreach ($files as $_file) {
    $pattern = '/^' . $archiver->sub_directory . '\\//';
    if (!preg_match($pattern, $_file)) {
      $archiver->sub_directory = FALSE;
      break;
    }
  }
  $extract_location = $directory;
  if ($archiver->sub_directory) {
    $extract_location .= '/' . $archiver->sub_directory;
  }
  if (file_exists($extract_location)) {
    file_unmanaged_delete_recursive($extract_location);
  }
  if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
    throw new Exception(t('Unable to extract the archive. Please check that your Drupal <a href="!file">file system</a> is correctly configured.', array(
      '!file' => \Drupal\Core\Url::fromRoute('system.file_system_settings'),
    )));
  }
  try {
    $archiver
      ->extract($directory);
  } catch (Exception $e) {

    // Close opened archive before trying to delete it.
    $archiver
      ->getArchive()
      ->close();
    if (file_exists($extract_location)) {
      file_unmanaged_delete_recursive($extract_location);
    }
    file_delete($file);
    throw new Exception($e
      ->getMessage());
  }
  return $archiver;
}

/**
 * Icon API's custom implementation of array_merge_recursive_distinct.
 *
 * array_merge_recursive does indeed merge arrays, but it converts values with
 * duplicate keys to arrays rather than overwriting the value in the first
 * array with the duplicate value in the second array, as array_merge does.
 * I.e., with array_merge_recursive, this happens (documented behavior):
 *
 * @code
 *   array_merge_recursive(
 *     array('key' => 'org value'),
 *     array('key' => 'new value')
 *   ) === array('key' => array('org value', 'new value'));
 * @endcode
 *
 * array_merge_recursive_distinct does not change the datatypes of the values
 * in the arrays. Matching keys' values in the second array overwrite those in
 * the first array, as is the case with array_merge, i.e.:
 *
 * @code
 *   array_merge_recursive_distinct(
 *     array('key' => 'org value'),
 *     array('key' => 'new value')
 *   ) === array('key' => 'new value');
 * @endcode
 *
 * Parameters are passed by reference, though only for performance reasons.
 * They're not altered by this function.
 *
 * @param array $array1
 *   Base array.
 * @param array $array2
 *   Additional array.
 *
 * @author daniel@danielsmedegaardbuus.dk
 * @return array
 *   The merged array.
 */
function &icon_array_merge_recursive(array &$array1, &$array2 = NULL) {
  $merged = $array1;
  if (is_array($array2)) {
    foreach ($array2 as $key => $val) {
      if (is_array($array2[$key])) {
        $merged[$key] = isset($merged[$key]) && is_array($merged[$key]) ? icon_array_merge_recursive($merged[$key], $array2[$key]) : $array2[$key];
      }
      else {
        $merged[$key] = $val;
      }
    }
  }
  return $merged;
}

/**
 * Determine the recursive differences between two arrays.
 *
 * @param array $array1
 *   The first array.
 * @param array $array2
 *   The second array.
 *
 * @return array
 *   If $array2 differs in any way from $array1, the differences will be
 *   returned in an array. If there are no differences, the array will be empty.
 */
function icon_array_diff_recursive(array $array1, array $array2) {
  $diff = array();
  foreach ($array2 as $key => $value) {
    if (array_key_exists($key, $array1)) {
      if (is_array($value)) {
        $recursive_diff = icon_array_diff_recursive($array1[$key], $value);
        if (count($recursive_diff)) {
          $diff[$key] = $recursive_diff;
        }
      }
      else {
        if ($value !== $array1[$key]) {
          $diff[$key] = $value;
        }
      }
    }
    else {
      $diff[$key] = $value;
    }
  }
  return $diff;
}

/**
 * Determines whether an extension implements a hook.
 *
 * @param string $type
 *   The type of extension (theme or module).
 * @param string $extension
 *   The name of the extension.
 * @param string $hook
 *   The name of the hook (e.g. "help" or "menu").
 *
 * @return bool
 *   TRUE if the extension is both installed and enabled, and the hook is
 *   implemented in that extension. FALSE if the extension does not implement
 *   the hook.
 */
function icon_extension_hook($type, $extension, $hook) {
  $hooks =& drupal_static(__FUNCTION__, array());

  // Ensure the extension is loaded.
  if (!isset($hooks[$type][$extension])) {
    if ('module' === $type) {

      //@TODO: replace replace drupal_load()

      //drupal_load($type, $extension);
    }
    elseif ('theme' === $type && ($include = icon_find_theme_include($extension))) {
      include_once $include;
    }
  }

  // Check to see if the hook is implemented.
  if (!isset($hooks[$type][$extension][$hook])) {
    $hooks[$type][$extension][$hook] = 'module' === $type ? \Drupal::moduleHandler()
      ->implementsHook($extension, $hook) : function_exists($extension . '_' . $hook);
  }
  return $hooks[$type][$extension][$hook];
}

/**
 * Determines which extensions are implementing a hook.
 *
 * @param string $hook
 *   The name of the hook (e.g. "help" or "menu").
 *
 * @return array
 *   An array with the names of the extensions which are implementing this hook.
 */
function icon_extension_implements($hook) {
  $implements =& drupal_static(__FUNCTION__, array());
  if (!isset($implements[$hook])) {
    $implements[$hook] = array();

    // Gather the modules that implement the hook.
    foreach (\Drupal::moduleHandler()
      ->getImplementations($hook) as $module) {
      $implements[$hook][$module] = 'module';
    }

    // Due to how this module caches data, using the global $base_theme_info
    // variable will not work here. That array only contains the active
    // theme. We need to determine whether all enabled themes have a hook
    // definition. To do this, themes must define an 'icon.inc' file somewhere
    // in their directory structure.
    foreach (icon_enabled_themes() as $theme) {
      if ($include = icon_find_theme_include($theme)) {
        @(include_once $include);
        if (function_exists($theme . '_' . $hook)) {
          $implements[$hook][$theme] = 'theme';
        }
      }
    }
  }
  return $implements[$hook];
}

/**
 * Invokes a hook in a particular extension.
 *
 * @param string $type
 *   The type of extension (theme or module).
 * @param string $extension
 *   The name of the extension.
 * @param string $hook
 *   The name of the hook to invoke.
 * @param mixed $data
 *   Arguments to pass to the hook implementation.
 * @param mixed $context1
 *   Arguments to pass to the hook implementation.
 * @param mixed $context2
 *   Arguments to pass to the hook implementation.
 *
 * The arguments listed for this function are passed by reference, however
 * more arguments sent if needed.
 *
 * @return mixed
 *   The return value of the hook implementation.
 */
function icon_extension_invoke($type, $extension, $hook, &$data = NULL, &$context1 = NULL, &$context2 = NULL) {
  $args = func_get_args();

  // Remove $type, $extension and $hook from the arguments.
  unset($args[0], $args[1], $args[2]);
  if (isset($args[3])) {
    $args[3] =& $data;
  }
  if (isset($args[4])) {
    $args[4] =& $context1;
  }
  if (isset($args[5])) {
    $args[5] =& $context2;
  }
  if (icon_extension_hook($type, $extension, $hook)) {
    return call_user_func_array($extension . '_' . $hook, $args);
  }
}

/**
 * Determine which themes are enabled.
 *
 * @return array
 *   An array of theme names.
 */
function icon_enabled_themes() {
  $themes =& drupal_static(__FUNCTION__);
  if (!isset($themes)) {
    $themes = array();
    foreach (\Drupal::service('theme_handler')
      ->listInfo() as $theme) {
      if (!$theme->status || in_array($theme
        ->getName(), $themes)) {
        continue;
      }
      $themes[] = $theme
        ->getName();

      // Merge in the base themes if this is a sub-theme.
      if (isset($theme->sub_themes)) {

        // Only add the theme if it's not already in the list.
        foreach (array_keys($theme->sub_themes) as $name) {
          if (!in_array($name, $themes)) {
            $themes[] = $name;
          }
        }
      }
    }
  }
  return $themes;
}

/**
 * Moves a file to a new location without database changes or hook invocation.
 *
 * @param string $source
 *   The filepath or URI where the original source file(s) reside.
 * @param string $destination
 *   The URI where $source should be moved to. This must be a stream wrapper
 *   URI. If this value is omitted, Drupal's default files scheme will be used,
 *   usually "public://".
 * @param bool|string $rename
 *   When moving a directory, the base name of $source will be used. If desired,
 *   you can effectively rename the top level directory on $destination by
 *   providing a new base name value to the $rename parameter.
 * @param int $replace
 *   Replace behavior when the destination file already exists:
 *     FILE_EXISTS_REPLACE - Replace the existing file (default behavior).
 *     FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
 *       unique.
 *     FILE_EXISTS_ERROR - Do nothing and return FALSE.
 * @param int $depth
 *   Internal use, do not use.
 *
 * @return string|false
 *   The URI of the moved file or directory, or FALSE in the event of an error.
 */
function icon_file_move_recursive($source, $destination = NULL, $rename = FALSE, $replace = FILE_EXISTS_REPLACE, $depth = 0) {
  if (is_dir(\Drupal::service("file_system")
    ->realpath($source))) {
    if (!$depth) {
      $destination .= '/' . ($rename ? $rename : \Drupal::service("file_system")
        ->basename($source));
      file_unmanaged_delete_recursive(\Drupal::service("file_system")
        ->realpath($destination));
    }
    if (!file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
      return FALSE;
    }
    $dir = dir(\Drupal::service("file_system")
      ->realpath($source));
    while (($file = $dir
      ->read()) !== FALSE) {
      if ($file == '.' || $file == '..') {
        continue;
      }
      $source_file = $source . '/' . $file;
      $destination_file = $destination . '/' . $file;
      if (!icon_file_move_recursive($source_file, $destination_file, FALSE, $replace, $depth + 1)) {
        $dir
          ->close();
        return FALSE;
      }
    }
    $dir
      ->close();
    if (!file_unmanaged_delete_recursive(\Drupal::service("file_system")
      ->realpath($source))) {
      return FALSE;
    }
    return $destination;
  }
  elseif (is_file(\Drupal::service("file_system")
    ->realpath($source))) {
    return file_unmanaged_copy($source, $destination, $replace);
  }
  return FALSE;
}

/**
 * Find a specific theme icon include file.
 *
 * @param string $theme
 *   The name of the theme to find an include file for.
 *
 * @return string|false
 *   The include path or FALSE if not found.
 */
function icon_find_theme_include($theme) {
  static $includes;
  $themes =& drupal_static(__FUNCTION__, array());
  if (!isset($themes[$theme])) {
    $themes[$theme] = FALSE;
    if (!isset($includes)) {
      $includes = array();
      $listing = new ExtensionDiscovery(\Drupal::root());
      $available_themes = $listing
        ->scan('themes');
      foreach ($listing
        ->scan('themes') as $theme) {
        if (file_exists($filepath = $theme
          ->getPath() . '/icons.inc')) {
          $includes[$theme
            ->getName()] = $filepath;
        }
        else {
          if (file_exists($filepath = $theme
            ->getPath() . '/icon.inc')) {
            $includes[$theme
              ->getName()] = $filepath;
          }
        }
      }

      //$includes = array_keys(drupal_system_listing('/icon.inc|icons.inc$/', 'themes', 'uri', 0));
    }
    foreach ($includes as $uri) {
      $theme_path = drupal_get_path('theme', $theme);
      if (!empty($theme_path) && strpos($uri, $theme_path) !== FALSE) {
        $themes[$theme] = $uri;
        break;
      }
    }
  }
  return $themes[$theme];
}

/**
 * Wrapper function for drupal_process_attached().
 *
 * It only is invoked once per bundle, regardless of however many icons are
 * rendered from it.
 *
 * @param array $bundle
 *   The icon bundle array.
 *
 * @return bool
 *   FALSE if there were any missing library dependencies; TRUE if all library
 *   dependencies were met.
 */
function icon_process_attached(array $bundle = array()) {
  if (empty($bundle['name'])) {
    return FALSE;
  }
  $bundles =& drupal_static(__FUNCTION__, array());
  if (!isset($bundles[$bundle['name']])) {
    $bundles[$bundle['name']] = drupal_process_attached($bundle);
  }
  return $bundles[$bundle['name']];
}

Functions

Namesort descending Description
icon_archive_extract Extracts an uploaded archive file to the specified directory.
icon_array_diff_recursive Determine the recursive differences between two arrays.
icon_array_merge_recursive Icon API's custom implementation of array_merge_recursive_distinct.
icon_enabled_themes Determine which themes are enabled.
icon_extension_hook Determines whether an extension implements a hook.
icon_extension_implements Determines which extensions are implementing a hook.
icon_extension_invoke Invokes a hook in a particular extension.
icon_file_move_recursive Moves a file to a new location without database changes or hook invocation.
icon_find_theme_include Find a specific theme icon include file.
icon_process_attached Wrapper function for drupal_process_attached().