<?php

/**
 * @file
 * Advanced CSS/JS aggregation js compression module.
 */

/**
 * @addtogroup default_variables
 * @{
 */

/**
 * Default value to see packer is enabled.
 */
define('ADVAGG_JS_COMPRESS_PACKER', FALSE);

/**
 * Default value to see what compressor to use. 0 is Disabled.
 */
define('ADVAGG_JS_COMPRESSOR', 0);

/**
 * Default value to see what compressor to use. 0 is Disabled.
 */
define('ADVAGG_JS_COMPRESS_INLINE', 0);

/**
 * Default value to if inline compression is used if page is not cacheable.
 */
define('ADVAGG_JS_COMPRESS_INLINE_IF_NOT_CACHEABLE', FALSE);

/**
 * Default value for the compression ratio test.
 */
define('ADVAGG_JS_COMPRESS_RATIO', 0.1);

/**
 * Default value for the compression ratio test.
 */
define('ADVAGG_JS_COMPRESS_MAX_RATIO', 0.9);

/**
 * Default value for per file compression settings.
 */
define('ADVAGG_JS_COMPRESSOR_FILE_SETTINGS', -1);

/**
 * Default value to if inline compression is used if page is not cacheable.
 */
define('ADVAGG_JS_COMPRESS_ADD_LICENSE', 3);

/**
 * Default value to refresh the data in the cache 1 week before the TTL expires.
 */
define('ADVAGG_JS_COMPRESS_REFRESH_BEFORE_CACHE_TTL', 604800);


/**
 * @} End of "addtogroup default_variables".
 */

/**
 * @addtogroup hooks
 * @{
 */

/**
 * Implements hook_cron().
 */
function advagg_js_compress_cron() {
  // Get the redo list.
  list(, $redo_list) = advagg_js_compress_all_js_files_list();

  // Return if nothing to do.
  if (empty($redo_list)) {
    return;
  }

  // Compress js files and cache.
  advagg_js_compress_redo_files($redo_list);
}

/**
 * Implements hook_menu().
 */
function advagg_js_compress_menu() {
  $file_path = drupal_get_path('module', 'advagg_js_compress');
  $config_path = advagg_admin_config_root_path();

  $items[$config_path . '/advagg/js-compress'] = array(
    'title' => 'JS Compression',
    'description' => 'Adjust JS Compression settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('advagg_js_compress_admin_settings_form'),
    'type' => MENU_LOCAL_TASK,
    'access arguments' => array('administer site configuration'),
    'file path' => $file_path,
    'file' => 'advagg_js_compress.admin.inc',
    'weight' => 10,
  );
  $items[$config_path . '/advagg/js-compress/batch'] = array(
    'title' => 'Batch Generate',
    'page callback' => 'advagg_js_compress_batch_callback',
    'access arguments' => array('administer site configuration'),
  );

  return $items;
}

/**
 * Implements hook_module_implements_alter().
 */
function advagg_js_compress_module_implements_alter(&$implementations, $hook) {
  // Move advagg_js_compress below advagg.
  if ($hook === 'advagg_save_aggregate_alter' && array_key_exists('advagg_js_compress', $implementations)) {
    $advagg_key = '';
    $advagg_js_compress_key = '';
    $counter = 0;
    foreach ($implementations as $key => $value) {
      if ($key == 'advagg') {
        $advagg_key = $counter;
      }
      if ($key == 'advagg_js_compress') {
        $advagg_js_compress_key = $counter;
      }
      $counter++;
    }

    if ($advagg_js_compress_key > $advagg_key) {
      // Move advagg_js_compress to the top.
      $item = array('advagg_js_compress' => $implementations['advagg_js_compress']);
      unset($implementations['advagg_js_compress']);
      $implementations = array_merge($item, $implementations);

      // Move advagg to the very top.
      $item = array('advagg' => $implementations['advagg']);
      unset($implementations['advagg']);
      $implementations = array_merge($item, $implementations);
    }
  }
}

/**
 * @} End of "addtogroup hooks".
 */

/**
 * @addtogroup advagg_hooks
 * @{
 */

/**
 * Implements hook_advagg_current_hooks_hash_array_alter().
 */
function advagg_js_compress_advagg_current_hooks_hash_array_alter(&$aggregate_settings) {
  $aggregate_settings['variables']['advagg_js_compressor'] = variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR);
  $aggregate_settings['variables']['advagg_js_compress_packer'] = variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER);
  $aggregate_settings['variables']['advagg_js_compress_ratio'] = variable_get('advagg_js_compress_ratio', ADVAGG_JS_COMPRESS_RATIO);
  $aggregate_settings['variables']['advagg_js_compress_max_ratio'] = variable_get('advagg_js_compress_max_ratio', ADVAGG_JS_COMPRESS_MAX_RATIO);
  $aggregate_settings['variables']['advagg_js_compressor_file_settings'] = variable_get('advagg_js_compressor_file_settings', array());
  $aggregate_settings['variables']['advagg_js_compress_add_license'] = variable_get('advagg_js_compress_add_license', ADVAGG_JS_COMPRESS_ADD_LICENSE);
}

/**
 * Implements hook_advagg_modify_js_pre_render_alter().
 *
 * Used compress inline js.
 */
function advagg_js_compress_advagg_modify_js_pre_render_alter(&$children, &$elements) {
  // Get variables.
  $aggregate_settings['variables']['advagg_js_compressor'] = variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE);

  // Do nothing if the compressor is disabled.
  if (empty($aggregate_settings['variables']['advagg_js_compressor'])) {
    return;
  }

  // Do nothing if the page is not cacheable and inline compress if not
  // cacheable is not checked.
  if (!variable_get('advagg_js_compress_inline_if_not_cacheable', ADVAGG_JS_COMPRESS_INLINE_IF_NOT_CACHEABLE) && !drupal_page_is_cacheable()) {
    return;
  }

  // Compress any inline JS.
  module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg');
  foreach ($children as &$values) {
    // Compress onload.
    if (!empty($values['#attributes']['onload'])) {
      $contents = $values['#attributes']['onload'];
      $filename = drupal_hash_base64($contents);
      advagg_js_compress_prep($contents, $filename, $aggregate_settings, FALSE);
      $values['#attributes']['onload'] = $contents;
    }

    // Compress onerror.
    if (!empty($values['#attributes']['onerror'])) {
      $contents = $values['#attributes']['onerror'];
      $filename = drupal_hash_base64($contents);
      advagg_js_compress_prep($contents, $filename, $aggregate_settings, FALSE);
      $values['#attributes']['onerror'] = $contents;
    }

    // Compress inline.
    if (!empty($values['#value'])) {
      $contents = $values['#value'];
      $filename = drupal_hash_base64($contents);
      advagg_js_compress_prep($contents, $filename, $aggregate_settings, FALSE);
      $values['#value'] = $contents;
    }
  }
  unset($values);
}

/**
 * @} End of "addtogroup advagg_hooks".
 */

/**
 * @addtogroup 3rd_party_hooks
 * @{
 */

/**
 * Implements hook_libraries_info().
 */
function advagg_js_compress_libraries_info() {
  $libraries['JShrink'] = array(
    'name' => 'JShrink',
    'vendor url' => 'https://github.com/tedious/JShrink',
    'download url' => 'https://github.com/tedious/JShrink/archive/master.zip',
    'local version' => '1.2.0',
    'version' => '1.2.0',
    'files' => array(
      'php' => array(
        'src/JShrink/Minifier.php',
      ),
    ),
  );
  $libraries['jsqueeze'] = array(
    'name' => 'JSqueeze',
    'vendor url' => 'https://github.com/tchwork/jsqueeze',
    'download url' => 'https://github.com/tchwork/jsqueeze/archive/master.zip',
    'local version' => '2.0.5',
    'version' => '2.0.5',
    'files' => array(
      'php' => array(
        'src/JSqueeze.php',
      ),
    ),
  );
  $libraries['jsminplus'] = array(
    'vendor url' => 'https://github.com/JSMinPlus/JSMinPlus',
    'download url' => 'https://github.com/JSMinPlus/JSMinPlus/archive/master.zip',
    'name' => 'JSMinPlus',
    'local version' => '1.4',
    'version arguments' => array(
      'file' => 'jsminplus.php',
      'pattern' => '/JSMinPlus\s+version\s+([0-9a-zA-Z\.-]+)/',
      'lines' => 10,
      'cols' => 40,
    ),
    'files' => array(
      'php' => array(
        'jsminplus.php',
      ),
    ),
  );
  $libraries['jspacker'] = array(
    'vendor url' => 'https://github.com/tholu/php-packer',
    'download url' => 'https://github.com/tholu/php-packer/archive/master.zip',
    'name' => 'JSPacker',
    'local version' => '1.1',
    'version arguments' => array(
      'file' => 'src/Packer.php',
      'pattern' => '/\.\s+version\s+([0-9a-zA-Z\.-]+)/',
      'lines' => 4,
      'cols' => 40,
    ),
    'files' => array(
      'php' => array(
        'src/Packer.php',
      ),
    ),
  );

  return $libraries;
}

/**
 * @} End of "addtogroup 3rd_party_hooks".
 */

/**
 * Test a file, making sure it is compressible.
 *
 * @param string $filename
 *   Path and filename of the js file to test.
 * @param array $compressors
 *   List of compressors to test.
 * @param string $cache_id
 *   The cache ID for this file.
 *
 * @return array
 *   Array showing the results of the compression tests.
 */
function advagg_js_compress_test_file($filename, array $compressors, $cache_id) {
  $contents = (string) @advagg_file_get_contents($filename);
  // Get the JS string length before the compression operation.
  $contents_before = $contents;
  $before = strlen($contents);

  module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg');

  $results = array();
  foreach ($compressors as $key => $name) {
    $contents = $contents_before;
    $aggregate_settings['variables']['advagg_js_compressor'] = $key;

    // Compress it.
    $no_errors = advagg_js_compress_prep($contents, $filename, $aggregate_settings, FALSE, FALSE, FALSE, TRUE);
    $after = strlen($contents);

    $ratio = 0;
    if ($before != 0) {
      $ratio = ($before - $after) / $before;
    }
    // Set to "-1" if the compressor threw an error.
    if ($no_errors === FALSE) {
      $results[$key] = array(
        'code' => -1,
        'ratio' => round($ratio, 5),
        'name' => $name,
      );
    }
    // Set to "-2" if compression ratio sucks (it's already compressed).
    elseif ($ratio < 0.001) {
      $results[$key] = array(
        'code' => -2,
        'ratio' => round($ratio, 5),
        'name' => $name,
      );
    }
    // Set to "-3" if the compression ratio is way too good (bad js output).
    elseif ($ratio > 0.999) {
      $results[$key] = array(
        'code' => -3,
        'ratio' => round($ratio, 5),
        'name' => $name,
      );
    }
    // Set to "1". Everything worked, mark this file as compressible.
    else {
      $results[$key] = array(
        'code' => 1,
        'ratio' => round($ratio, 5),
        'name' => $name,
      );
    }

  }

  $cache = cache_get($cache_id, 'cache_advagg_info');
  // Merge in old cached data.
  if (!empty($cache->data)) {
    // Do not merge in -1 code.
    foreach ($cache->data as $key => $value) {
      if ($value['code'] == -1) {
        unset($cache->data[$key]);
      }
    }
    $results += $cache->data;
  }

  // CACHE_PERMANENT isn't good here. Use 2 weeks from now + 0-45 days.
  // The random 0 to 45 day addition is to prevent a cache stampeed.
  cache_set($cache_id, $results, 'cache_advagg_info', round(REQUEST_TIME + 1209600 + mt_rand(0, 3888000), -3));

  return $results;
}

/**
 * Generate the js compress configuration.
 *
 * @return array
 *   Array($options, $description, $compressors, $functions).
 */
function advagg_js_compress_configuration() {
  // Set the defaults.
  $description = '';
  $options = array(
    0 => t('Disabled'),
    1 => t('JSMin+ ~1300ms'),
    // 2 => t('Packer ~500ms'),
    // 3 is JSMin c extension.
    // 4 is JShrink.
    // 5 is JSqueeze.
  );
  if (function_exists('jsmin')) {
    $options[3] = t('JSMin ~2ms');
    $description .= t('JSMin is the very fast C complied version. Recommend using it.');
  }
  else {
    if (!defined('PHP_VERSION_ID') || constant('PHP_VERSION_ID') < 50310) {
      $link = 'http://www.ypass.net/software/php_jsmin/';
    }
    else {
      $link = 'https://github.com/sqmk/pecl-jsmin/';
    }
    $description .= t('You can use the much faster C version of JSMin (~2ms) by installing the <a href="@php_jsmin">JSMin PHP Extension</a> on this server.', array('@php_jsmin' => $link));
  }
  // Add in JShrink and JSqueeze if using php 5.3 or higher.
  if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) {
    $options += array(
      4 => t('JShrink ~1000ms'),
      5 => t('JSqueeze ~600ms'),
    );
  }

  $compressors = array(
    1 => 'jsminplus',
    2 => 'packer',
  );
  if (function_exists('jsmin')) {
    $compressors[3] = 'jsmin';
  }
  if (defined('PHP_VERSION_ID') && constant('PHP_VERSION_ID') >= 50300) {
    $compressors += array(
      4 => 'jshrink',
      5 => 'jsqueeze',
    );
  }

  $functions = array(
    1 => 'advagg_js_compress_jsminplus',
    2 => 'advagg_js_compress_jspacker',
    3 => 'advagg_js_compress_jsmin',
    4 => 'advagg_js_compress_jshrink',
    5 => 'advagg_js_compress_jsqueeze',
  );

  // Allow for other modules to alter this list.
  $options_desc = array($options, $description);
  // Call hook_advagg_js_compress_configuration_alter().
  drupal_alter('advagg_js_compress_configuration', $options_desc, $compressors, $functions);
  list($options, $description) = $options_desc;

  return array($options, $description, $compressors, $functions);
}

/**
 * Get all js files and js files that are not compressed.
 *
 * @return array
 *   Array($list, $redo_list).
 */
function advagg_js_compress_all_js_files_list() {
  // Get all files stored in the database.
  $result = db_select('advagg_files', 'af')
    ->fields('af')
    ->condition('filetype', 'js')
    ->orderBy('filename', 'ASC')
    ->execute();
  if (empty($result)) {
    return array();
  }

  module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg');
  $compressor_list = advagg_js_compress_get_enabled_compressors(array(), -1);
  $compressor_list_count = count($compressor_list);

  // Check if files have been compressed.
  module_load_include('inc', 'advagg', 'advagg');
  $redo_list = array();
  $failed_redo_list = array();
  $list = array();
  $cache_ids = array();
  foreach ($result as $row) {
    $row = (array) $row;
    // Check cache for jsmin info.
    $info = advagg_get_info_on_file($row['filename']);
    if ($info['filesize'] == 0) {
      continue;
    }
    $list[$row['filename']] = $info;

    // Get the cache id as well.
    $cache_id = 'advagg:js_compress:info:' . $info['filename_hash'];
    $cache_id .= !empty($info['content_hash']) ? ':' . $info['content_hash'] : '';
    $cache_ids[$cache_id] = $row['filename'];
  }

  // Check for soon to expire cache ids.
  $values = array_keys($cache_ids);
  $cache_hits = cache_get_multiple($values, 'cache_advagg_info');
  $ttl = variable_get('advagg_js_compress_refresh_before_cache_ttl', ADVAGG_JS_COMPRESS_REFRESH_BEFORE_CACHE_TTL);
  foreach ($cache_hits as $cache) {
    if (!empty($cache->expire) && $cache->expire - $ttl < REQUEST_TIME) {
      $info = $list[$cache_ids[$cache->cid]];
      $redo_list[$info['data']] = $info;
    }
  }

  foreach ($list as $info) {
    // No jsmin info or incomplete data => rerun compression tests.
    if (empty($info['advagg_js_compress']) || count($info['advagg_js_compress']) !== $compressor_list_count) {
      $redo_list[$info['data']] = $info;
      continue;
    }
    $empty_ratio_count = 0;
    $bad_compression_count = 0;
    foreach ($info['advagg_js_compress'] as $values) {
      if (empty($values['ratio'])) {
        if ($values['code'] != -1) {
          $empty_ratio_count++;
        }
        else {
          $bad_compression_count++;
        }
      }
    }
    // More than one compressor has an empty ratio.
    if ($empty_ratio_count > 1) {
      $failed_redo_list[$info['data']] = $info;
    }
    // All failed; try again.
    if ($bad_compression_count == count($info['advagg_js_compress'])) {
      $failed_redo_list[$info['data']] = $info;
    }
  }

  $redo_list = array_merge($redo_list, $failed_redo_list);
  $reversed_needle = strrev('.min.js');
  foreach ($redo_list as $key => $info) {
    // Filter out .min.js if they have already been ran.
    if (stripos(strrev($info['data']), $reversed_needle) === 0
      && !empty($info['advagg_js_compress'][2]['ratio'])
    ) {
      unset($redo_list[$key]);
      continue;
    }

    // Filter out file if it is empty.
    $data = file_get_contents($info['data']);
    if (empty($data)) {
      unset($redo_list[$key]);
      continue;
    }

    // Filter out file if it only contains a small amount of whitespace.
    $count_ws = strlen($data);
    $count = strlen(preg_replace('/\s/', '', $data));
    if ($count / $count_ws > 0.97) {
      unset($redo_list[$key]);
      continue;
    }
  }

  shuffle($redo_list);
  return array($list, $redo_list);
}

/**
 * Get all js files and js files that are not compressed.
 *
 * @param array $redo_list
 *   JS files that need to be compressed.
 * @param int $max_time
 *   Max amount of time to spend on compressing.
 * @param bool $drush
 *   Set to TRUE to output drush info when running.
 *
 * @return array
 *   Array($list, $redo_list).
 */
function advagg_js_compress_redo_files(array $redo_list, $max_time = 30, $drush = FALSE) {
  // Get the compressor list and start the clock.
  module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg');
  $compressor_list = advagg_js_compress_get_enabled_compressors(array(), -1);
  shuffle($redo_list);
  $time_start = microtime(TRUE);
  if ($drush && (!is_callable('drush_log') || !is_callable('dt'))) {
    $drush = FALSE;
  }

  // Change settings for testing.
  if (isset($GLOBALS['conf']['advagg_js_compress_force_run'])) {
    $advagg_js_compress_force_run = $GLOBALS['conf']['advagg_js_compress_force_run'];
  }
  if (isset($GLOBALS['conf']['advagg_js_compress_add_license'])) {
    $advagg_js_compress_add_license = $GLOBALS['conf']['advagg_js_compress_add_license'];
  }
  if (isset($GLOBALS['conf']['httprl_background_callback'])) {
    $httprl_background_callback = $GLOBALS['conf']['httprl_background_callback'];
  }
  $GLOBALS['conf']['advagg_js_compress_force_run'] = TRUE;
  $GLOBALS['conf']['advagg_js_compress_add_license'] = 0;
  $GLOBALS['conf']['httprl_background_callback'] = FALSE;

  $counter = array();
  foreach ($redo_list as $key => $values) {
    // Test the files for up to 30 seconds.
    $filenames_info = array();
    $filenames_info[$values['data']] = $values;
    $compressors = $compressor_list;
    if (isset($values['compressors'])) {
      $compressors = $values['compressors'];
    }

    if ($drush) {
      drush_log(dt('Compressing @data.', array(
        '@data' => $values['data'],
      )), 'ok');
    }

    // Remove jsqueeze if compression failed.
    if (!empty($values['advagg_js_compress'])) {
      $neg_one_counter = 0;
      foreach ($values['advagg_js_compress'] as $compressor_data) {
        if ($compressor_data['code'] == -1) {
          $neg_one_counter++;
        }
      }
      if (count($values['advagg_js_compress']) === $neg_one_counter) {
        $compressor_key = array_search('jsqueeze', $compressors);
        if ($compressor_key !== FALSE) {
          unset($compressors[$compressor_key]);
        }
      }
    }

    // Prime cache.
    advagg_js_compress_run_mutiple_tests($filenames_info, $compressors);
    // Add to cache.
    advagg_get_info_on_file($values['data'], TRUE, TRUE);

    $counter[$key] = $values;

    // Stop after 30 seconds of processing.
    $time_end = microtime(TRUE);
    $time = $time_end - $time_start;
    if (!empty($max_time) && $time > $max_time) {
      break;
    }
  }

  // Put them back to normal.
  if (isset($advagg_js_compress_force_run)) {
    $GLOBALS['conf']['advagg_js_compress_force_run'] = $advagg_js_compress_force_run;
  }
  if (isset($advagg_js_compress_add_license)) {
    $GLOBALS['conf']['advagg_js_compress_add_license'] = $advagg_js_compress_add_license;
  }
  if (isset($httprl_background_callback)) {
    $GLOBALS['conf']['httprl_background_callback'] = $httprl_background_callback;
  }

  return $counter;
}

/**
 * The batch callback.
 */
function advagg_js_compress_batch_callback() {
  $batch = array(
    'operations' => array(),
    'finished' => 'advagg_js_compress_batch_done',
    'title' => t('Batch JS Minification'),
    'init_message' => t('Starting'),
    'progress_message' => t('Processed @current out of @total.'),
    'error_message' => t('JS minification has encountered an error.'),
  );

  list($list, $redo_list) = advagg_js_compress_all_js_files_list();
  $config_path = advagg_admin_config_root_path();
  if (empty($redo_list)) {
    $redo_list = $list;
  }

  foreach ($redo_list as $redo) {
    $batch['operations'][] = array('advagg_js_compress_batch_process', array($redo));
  }
  batch_set($batch);
  // The path to redirect to when done.
  batch_process($config_path . '/advagg/js-compress');
}

/**
 * The batch processor.
 */
function advagg_js_compress_batch_process($redo, &$context) {
  // Give it up to 3 minutes.
  $max_time = ini_get('max_execution_time');
  if ($max_time != 0 && $max_time < 180) {
    set_time_limit(180);
  }
  ignore_user_abort(TRUE);

  // Display a progress message...
  $context['message'] = t("Now processing @filename...", array('@filename' => $redo['data']));
  // Do heavy lifting here...
  advagg_js_compress_redo_files(array($redo));
}

/**
 * The batch finish handler.
 */
function advagg_js_compress_batch_done($success, $results, $operations) {
  if ($success) {
    drupal_set_message(t('Done!'));
  }
  else {
    $error_operation = reset($operations);
    $message = t('An error occurred while processing %error_operation with arguments: @arguments', array(
      '%error_operation' => $error_operation[0],
      '@arguments' => print_r($error_operation[1], TRUE),
    ));
    drupal_set_message($message, 'error');
  }
}

/**
 * Test the cache_advagg_info cache bin; make sure it works.
 *
 * @return array
 *   Single section of the requirements array.
 */
function advagg_js_compress_check_cache_bin() {
  $t = get_t();

  $requirements = array();
  // Make sure cache table is working.
  $cid = 'advagg_js_compres:install:cache_test';
  $bin = 'cache_advagg_info';
  cache_set($cid, TRUE, $bin);
  $cache = cache_get($cid, $bin);
  $working = FALSE;
  if (!empty($cache->data)) {
    cache_clear_all($cid, $bin);
    $cache = cache_get($cid, $bin);
    if (empty($cache->data)) {
      $working = TRUE;
    }
  }

  if (empty($working)) {
    $broken_name = get_class(_cache_get_object($bin));

    if ($broken_name === 'DrupalFakeCache') {
      $working_name = get_class(_cache_get_object('cache_form'));
      if ($working_name === 'DrupalFakeCache') {
        $working_name = 'DrupalDatabaseCache';
      }
      $extra_description = t('Please add this to the bottom of your settings.php file. <p><code>@string</code></p>', array('@string' => "\$conf['cache_class_cache_advagg_info'] = '{$working_name}';"));
    }
    $requirements['advagg_js_compres_cache_bin'] = array(
      'title' => $t('AdvAgg JS Compressor - The %bin cache table does not work', array('%bin' => $bin)),
      'severity' => REQUIREMENT_WARNING,
      'value' => $t('The %class_name cache bin appears to be broken.', array('%class_name' => $broken_name)),
      'description' => $t('You need to adjust your settings.php file so that the %bin bin is working correctly.', array(
        '%bin' => $bin,
      )),
    );
    $requirements['advagg_js_compres_cache_bin']['description'] .= " {$extra_description}";
  }

  return $requirements;
}
