Same filename in other branches
  1. 7.x-1.x advagg_bundler/advagg_bundler.module
  2. 8.x-2.x advagg_bundler/advagg_bundler.module

Advanced aggregation bundler module.

Fichier

advagg_bundler/advagg_bundler.module

View source
<?php


/**
 * @file
 * Advanced aggregation bundler module.
 */

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

/**
 * Default for maximum number of CSS bundles used in a themes region.
 */
define('ADVAGG_BUNDLER_MAX_CSS', 2);

/**
 * Default for maximum number of JS bundles used in a themes region.
 */
define('ADVAGG_BUNDLER_MAX_JS', 5);

/**
 * Default of the last used time before the bundle is considered outdated.
 *
 * Value of 1209600 is 2 weeks in seconds.
 */
define('ADVAGG_BUNDLER_OUTDATED', 1209600);

/**
 * Default value to see if the bundler should be active or passive.
 *
 * If it is passive, the bundler will only do analysis and not split up the
 * aggregate.
 */
define('ADVAGG_BUNDLER_ACTIVE', TRUE);

/**
 * Default value to for bundler, set to file size.
 */
define('ADVAGG_BUNDLER_GROUPING_LOGIC', 1);

/**
 * If 4 the admin section gets unlocked.
 */
define('ADVAGG_BUNDLER_ADMIN_MODE', 4);

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

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

/**
 * Implements hook_menu().
 */
function advagg_bundler_menu() {
    $file_path = drupal_get_path('module', 'advagg_bundler');
    $config_path = advagg_admin_config_root_path();
    $items[$config_path . '/advagg/bundler'] = array(
        'title' => 'Bundler',
        'description' => 'Adjust Bundler settings.',
        'page callback' => 'drupal_get_form',
        'page arguments' => array(
            'advagg_bundler_admin_settings_form',
        ),
        'type' => MENU_LOCAL_TASK,
        'access arguments' => array(
            'administer site configuration',
        ),
        'file path' => $file_path,
        'file' => 'advagg_bundler.admin.inc',
        'weight' => 10,
    );
    return $items;
}

/**
 * Implements hook_advagg_hooks_implemented_alter().
 */
function advagg_bundler_advagg_hooks_implemented_alter(&$hooks, $all) {
    if ($all) {
        $hooks['advagg_bundler_analysis_alter'] = array();
    }
}

/**
 * Implements hook_init().
 */
function advagg_bundler_init() {
    if (advagg_bundler_enabled()) {
        $GLOBALS['conf']['advagg_core_groups'] = FALSE;
    }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function advagg_bundler_form_advagg_admin_settings_form_alter(&$form, $form_state) {
    if (advagg_bundler_enabled()) {
        $form['global']['advagg_core_groups']['#disabled'] = TRUE;
        $form['global']['advagg_core_groups']['#description'] = t('The bundler submodule disables core grouping logic.');
        $form['global']['advagg_core_groups']['#states'] = array();
    }
}

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

/**
 * Returns TRUE if the bundler will run.
 */
function advagg_bundler_enabled() {
    if (variable_get('advagg_bundler_active', ADVAGG_BUNDLER_ACTIVE) && (variable_get('advagg_bundler_max_css', ADVAGG_BUNDLER_MAX_CSS) || variable_get('advagg_bundler_max_js', ADVAGG_BUNDLER_MAX_JS))) {
        return TRUE;
    }
}

/**
 * Given a filename return a bundle key.
 *
 * @param string $filename
 *   Filename.
 * @param bool $force
 *   Bypass the cache and get a fresh version of the analysis.
 * @param bool $safesql
 *   Turn off SQL language that might cause errors.
 * @param int $depth
 *   Used to prevent endless loops.
 *
 * @return string
 *   String to be used for the grouping key.
 */
function advagg_bundler_analysis($filename = '', $force = FALSE, $safesql = FALSE, $depth = 0) {
    // Cache query in a static.
    static $analysis = array();
    if (empty($analysis)) {
        // See if we have a cached version of this. Generate cache ID.
        $query = db_select('advagg_aggregates_versions', 'aav')->condition('aav.root', 1)
            ->condition('aav.atime', REQUEST_TIME - max(172800, variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED), '>'), '>');
        $query->addExpression('COUNT(aggregate_filenames_hash)', 'counter');
        $count = $query->execute()
            ->fetchField();
        $ideal_cid = 'advagg:bundler_analysis:' . $count;
        if (!$force) {
            // Generate cache IDs.
            $counts = range(max(0, $count - 3), $count + 3);
            foreach ($counts as $count) {
                $cache_ids[] = 'advagg:bundler_analysis:' . $count;
            }
            // Get a range of cached bundler_analysis data.
            $cache_hits = cache_get_multiple($cache_ids, 'cache_advagg_aggregates');
            if (!empty($cache_hits)) {
                if (isset($cache_hits[$ideal_cid])) {
                    $cache = $cache_hits[$ideal_cid];
                }
                elseif (!$force && module_exists('httprl') && httprl_is_background_callback_capable()) {
                    // Setup callback options array.
                    $callback_options = array(
                        array(
                            'function' => 'advagg_bundler_analysis',
                        ),
                        $filename,
                        TRUE,
                    );
                    // Queue up the request.
                    httprl_queue_background_callback($callback_options);
                    // Execute request.
                    httprl_send_request();
                    // Use most recent bundler_analysis data.
                    $max = 0;
                    foreach ($cache_hits as $data) {
                        if ($data->created > $max) {
                            $max = $data->created;
                            $cache = $data;
                        }
                    }
                }
            }
        }
        if ($force || empty($cache->data)) {
            try {
                $analysis = advagg_bundler_analyisis_query($safesql);
                // Save results to the cache.
                cache_set($ideal_cid, $analysis, 'cache_advagg_aggregates', CACHE_TEMPORARY);
            } catch (PDOException $e) {
                if ($depth > 2) {
                    throw $e;
                }
                $depth++;
                return advagg_bundler_analysis($filename, TRUE, TRUE, $depth);
            }
        }
        else {
            $analysis = $cache->data;
        }
    }
    // If no filename is given pass back then entire query results.
    if (empty($filename)) {
        return $analysis;
    }
    // Return a key to be used in groupings.
    if (!empty($analysis[$filename])) {
        return $analysis[$filename];
    }
    // We need to return a value that can be used as an array key if the query
    // didn't give us anything.
    return 0;
}

/**
 * Run the analysis query and return the analysis array.
 *
 * "Magic Query"; only needs to run once. Results are cached.
 * This is what the raw SQL looks like:
 *
 * @code
 *  SELECT
 *    af.filename AS filename,
 *    af.filesize AS filesize,
 *    af.mtime AS mtime,
 *    af.changes AS changes,
 *    af.linecount AS linecount,
 *    af.filename_hash AS filename_hash,
 *    aa.counter AS counter,
 *    aa.hashlist AS hashlist
 *   FROM advagg_files af
 *   INNER JOIN (
 *    SELECT
 *     aa.filename_hash AS filename_hash,
 *     LPAD(CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), 8, '0') AS counter,
 *     HEX(SHA1(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash ORDER BY aa.aggregate_filenames_hash ASC))) AS hashlist
 *    FROM advagg_aggregates aa
 *    INNER JOIN advagg_aggregates_versions aav
 *     ON aav.aggregate_filenames_hash = aa.aggregate_filenames_hash
 *     AND aav.root = 1
 *     AND aav.atime > (UNIX_TIMESTAMP() - 1209600)
 *    GROUP BY aa.filename_hash
 *   ) aa ON af.filename_hash = aa.filename_hash
 * @endcode
 *
 * @param bool $safesql
 *   Turn off SQL language that might cause errors.
 *
 * @return array
 *   The analysis array.
 */
function advagg_bundler_analyisis_query($safesql) {
    // Return a count of how many root bundles all files are used in. Count is
    // padded with eight zeros so the count can be key sorted as a string
    // without worrying about it getting put in the wrong order.
    // Return the bundle_md5's value; we need something more unique than count
    // when grouping together.
    // Return the filename. Used for lookup.
    // We join the advagg bundles and files together making sure to only use
    // root bundles that have been used in the last 2 weeks. This prevents an
    // old site structure from influencing new bundles.
    // Grouping by the filename gives us the count and makes it so we don't
    // return a lot of rows.
    $db_type = Database::getConnection()->databaseType();
    $schema = Database::getConnection()->schema();
    if ($safesql) {
        $mssql_group_concat = FALSE;
        $mssql_lpad = FALSE;
        $mssql_md5 = FALSE;
        $pg9 = FALSE;
    }
    else {
        $mssql_group_concat = method_exists($schema, 'functionExists') && $schema->functionExists('GROUP_CONCAT');
        $mssql_lpad = method_exists($schema, 'functionExists') && $schema->functionExists('LPAD');
        $mssql_md5 = method_exists($schema, 'functionExists') && $schema->functionExists('MD5');
        if ($db_type === 'pgsql') {
            $database_connection = Database::getConnection();
            $pg9 = FALSE;
            if (version_compare($database_connection->version(), '9') >= 0) {
                $pg9 = TRUE;
            }
        }
    }
    // Create join query for the advagg_aggregates table.
    $subquery_aggregates = db_select('advagg_aggregates', 'aa');
    // Counter column.
    $fields = array(
        'counter',
    );
    if ($db_type === 'sqlsrv' && !$mssql_lpad) {
        // MS SQL does not support LPAD.
        $subquery_aggregates->addExpression("RIGHT(REPLICATE('0',8) + CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)),8)", 'counter');
    }
    elseif ($db_type === 'sqlite') {
        // SQLite does not support LPAD.
        $subquery_aggregates->addExpression("substr('00000000' || CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), -8, 8)", 'counter');
    }
    else {
        $subquery_aggregates->addExpression("LPAD(CAST(COUNT(aav.aggregate_filenames_hash) AS char(8)), 8, '0')", 'counter');
    }
    // Hashlist column.
    if ($db_type === 'mysql') {
        $fields[] = 'hashlist';
        db_query('SET SESSION group_concat_max_len = 65535');
        $subquery_aggregates->addExpression('HEX(SHA1(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash ORDER BY aa.aggregate_filenames_hash ASC)))', 'hashlist');
    }
    elseif ($db_type === 'pgsql') {
        if ($pg9) {
            $fields[] = 'hashlist';
            $subquery_aggregates->addExpression("MD5(STRING_AGG(DISTINCT(aa.aggregate_filenames_hash), ',' ORDER BY aa.aggregate_filenames_hash ASC))", 'hashlist');
        }
    }
    elseif ($db_type === 'sqlite') {
        $fields[] = 'hashlist';
        $subquery_aggregates->addExpression('GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash)', 'hashlist');
        $subquery_aggregates->orderBy("aa.aggregate_filenames_hash", "ASC");
    }
    elseif ($db_type === 'sqlsrv' && $mssql_group_concat) {
        $fields[] = 'hashlist';
        if ($mssql_md5) {
            $subquery_aggregates->addExpression('MD5(GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash))', 'hashlist');
        }
        else {
            $subquery_aggregates->addExpression('GROUP_CONCAT(DISTINCT aa.aggregate_filenames_hash)', 'hashlist');
        }
        // The ORDER BY clause is invalid in views, inline functions,
        // derived tables, subqueries, and common table expressions, unless TOP or
        // FOR XML is also specified. So no point in doing an order-by like in the
        // other cases.
    }
    // Create join for the advagg_aggregates_versions table.
    // 1209600 = 2 weeks.
    $time = REQUEST_TIME - max(172800, variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED), '>');
    $subquery_aggregates->join('advagg_aggregates_versions', 'aav', "aav.aggregate_filenames_hash=aa.aggregate_filenames_hash AND aav.root=1 AND aav.atime > {$time}");
    $subquery_aggregates = $subquery_aggregates->fields('aa', array(
        'filename_hash',
    ))
        ->groupBy('aa.filename_hash');
    // Create main query for the advagg_files table.
    $af_fields = array(
        'filename',
        'filesize',
        'mtime',
        'changes',
        'linecount',
        'filename_hash',
    );
    // Make drupal_get_installed_schema_version() available.
    include_once DRUPAL_ROOT . '/includes/install.inc';
    if (drupal_get_installed_schema_version('advagg') >= 7211) {
        $af_fields[] = 'filesize_processed';
    }
    $query = db_select('advagg_files', 'af');
    $query->join($subquery_aggregates, 'aa', 'af.filename_hash=aa.filename_hash');
    $query = $query->fields('af', $af_fields)
        ->fields('aa', $fields);
    $query->comment('Query called from ' . __FUNCTION__ . '()');
    $results = $query->execute();
    $analysis = array();
    foreach ($results as $row) {
        // Implement slower GROUP_CONCAT functionality for non mysql databases.
        if (empty($row->hashlist)) {
            $subquery_aggregates_versions = db_select('advagg_aggregates_versions', 'aav')->fields('aav')
                ->condition('aav.root', 1)
                ->condition('aav.atime', REQUEST_TIME - max(172800, variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED), '>'), '>');
            $subquery_aggregates = db_select('advagg_aggregates', 'aa');
            $subquery_aggregates->join($subquery_aggregates_versions, 'aav', 'aav.aggregate_filenames_hash=aa.aggregate_filenames_hash');
            $subquery_aggregates = $subquery_aggregates->fields('aa', array(
                'aggregate_filenames_hash',
            ))
                ->condition('aa.filename_hash', $row->filename_hash)
                ->groupBy('aa.aggregate_filenames_hash')
                ->orderBy('aa.aggregate_filenames_hash', 'ASC');
            $subquery_aggregates->comment('Query called from ' . __FUNCTION__ . '()');
            $aa_results = $subquery_aggregates->execute();
            $aa_rows = array();
            foreach ($aa_results as $aa_row) {
                $aa_rows[] = $aa_row->aggregate_filenames_hash;
            }
            $row->hashlist = implode(',', $aa_rows);
        }
        $row->hashlist = drupal_hash_base64($row->hashlist);
        $analysis[$row->filename] = array(
            'group_hash' => $row->counter . ' ' . $row->hashlist,
            'mtime' => $row->mtime,
            'filesize' => $row->filesize,
            'filesize_processed' => empty($row->filesize_processed) ? $row->filesize : $row->filesize_processed,
            'linecount' => $row->linecount,
            'changes' => $row->changes,
        );
    }
    arsort($analysis);
    // Invoke hook_advagg_bundler_analysis_alter() to give installed modules a
    // chance to alter the analysis array.
    drupal_alter('advagg_bundler_analysis', $analysis);
    return $analysis;
}

Functions

Titre Deprecated Résumé
advagg_bundler_advagg_hooks_implemented_alter Implements hook_advagg_hooks_implemented_alter().
advagg_bundler_analyisis_query Run the analysis query and return the analysis array.
advagg_bundler_analysis Given a filename return a bundle key.
advagg_bundler_enabled Returns TRUE if the bundler will run.
advagg_bundler_form_advagg_admin_settings_form_alter Implements hook_form_FORM_ID_alter().
advagg_bundler_init Implements hook_init().
advagg_bundler_menu Implements hook_menu().

Constants

Titre Deprecated Résumé
ADVAGG_BUNDLER_ACTIVE Default value to see if the bundler should be active or passive.
ADVAGG_BUNDLER_ADMIN_MODE If 4 the admin section gets unlocked.
ADVAGG_BUNDLER_GROUPING_LOGIC Default value to for bundler, set to file size.
ADVAGG_BUNDLER_MAX_CSS Default for maximum number of CSS bundles used in a themes region.
ADVAGG_BUNDLER_MAX_JS Default for maximum number of JS bundles used in a themes region.
ADVAGG_BUNDLER_OUTDATED Default of the last used time before the bundle is considered outdated.