You are here

modernizr.module in Modernizr 8

Main module file for Modernizr

File

modernizr.module
View source
<?php

/**
 * @file
 * Main module file for Modernizr
 */

// Regular expression to determine which version of Modernizr is installed.
define('MODERNIZR_VERSION_REGEX', '/Modernizr [v]?([\\d\\.]*)/');

// Regular expression to detect valid Modernizr filenames.
define('MODERNIZR_FILENAME_REGEX', '/^modernizr[A-Za-z0-9\\.-]*\\.js$/');

// Our drupal_add_js() and libraries_load() calls use this value to maintain
// consistency between the position of the library and its inline settings.
define('MODERNIZR_SCRIPT_GROUP', JS_LIBRARY - 10);
define('MODERNIZR_SCRIPT_WEIGHT', -100);

// Default drupal_add_js() settings. Used in multiple places
global $_modernizr_js_settings;
$_modernizr_js_settings = array(
  'type' => 'file',
  'scope' => 'header',
  'group' => MODERNIZR_SCRIPT_GROUP,
  'weight' => MODERNIZR_SCRIPT_WEIGHT,
  'every_page' => TRUE,
  'preprocess' => 0,
  // This setting is not in Drupal core API. Both Omega 4.x and Aurora 2.x
  // use this new flag in order to preserve scripts in the header in Drupal 7.
  // If you maintain a Drupal theme you should follow suit :)
  'force header' => TRUE,
);

/**
 * Implements hook_menu().
 */
function modernizr_menu() {
  $items = array();
  $items['modernizr_settings'] = array(
    'title' => 'Modernizr settings',
    'description' => 'Queries Drupal for Modernizr dependencies and generates a custom link to the Modernizr builder.',
    'route_name' => 'modernizr_settings',
    // 'page callback' => 'modernizr_generate_url',
    'file' => 'modernizr.admin.inc',
    'type' => MENU_NORMAL_ITEM,
    'access arguments' => array(
      'administer modernizr',
    ),
  );
  return $items;
}

/**
 * Implements hook_page_build().
 *
 * We used to use hook_init(), but that loads the JA files needlessly
 * on AJAX requests, private file requests, etc.
 */
function modernizr_page_build(&$page) {
  global $_modernizr_js_settings;

  // Load Modernizr on the page by invoking our implementation of hook_libraries_info().
  //
  // We can only use this method when Libraries API 2.0 is installed. Since Libraries 1.0
  // did not contain a function called libraries_load(), we must explicitly check for a
  // valid function to avoid fatal errors.
  //
  // @see http://drupal.org/node/1919796
  if (module_exists('libraries') && function_exists('libraries_load')) {
    libraries_load('modernizr');
  }
  else {

    // No Libraries API? Load the regular way.
    drupal_add_js(modernizr_get_path(), $_modernizr_js_settings);
  }

  // We want Modernizr.load() commands to be issued immediately after the call
  // to Modernizr so that they download while the page renders. The overrides
  // to $inline_js_settings will format the output as inline JS.
  if ($output = _modernizr_load_generate()) {
    $inline_js_settings = $_modernizr_js_settings;
    $inline_js_settings['type'] = 'inline';
    $inline_js_settings['weight'] = MODERNIZR_SCRIPT_WEIGHT + 1;

    // Load JS
    drupal_add_js($output, $inline_js_settings);
  }
}

/**
 * Implements hook_permission().
 */
function modernizr_permission() {
  return array(
    'administer modernizr' => array(
      'title' => t('Administer Modernizr'),
      'description' => t('Perform administration tasks for Modernizr.'),
    ),
  );
}

/**
 * Implements hook_libraries_info().
 *
 * @return array
 */
function modernizr_libraries_info() {
  global $_modernizr_js_settings;
  $libraries = array();
  $file_name = modernizr_get_filename();

  // Define Modernizr within Libraries API
  $libraries['modernizr'] = array(
    'name' => t('Modernizr'),
    'vendor url' => 'http://modernizr.com',
    'download url' => 'http://modernizr.com/download/',
    'version arguments' => array(
      'file' => $file_name,
      'pattern' => MODERNIZR_VERSION_REGEX,
    ),
    'files' => array(
      'js' => array(
        $file_name => $_modernizr_js_settings,
      ),
    ),
  );
  return $libraries;
}

/**
 * Returns the full path of modernizr, along with the filename.
 *
 * @return string
 */
function modernizr_get_path() {
  $path =& drupal_static(__FUNCTION__);
  if ($path === NULL) {
    $paths = array();

    // Check for directory specified in hook_libraries_info()
    if (module_exists('libraries')) {
      $library_path = libraries_get_path('modernizr');
      if (file_exists($library_path)) {
        $paths[] = $library_path;
      }
    }

    // The best location for downloaded libraries is sites/all/libraries.
    if (is_dir('libraries/modernizr')) {
      $paths[] = 'libraries/modernizr';
    }

    // Check inside the module folder itself
    // NOTE: this will be removed in 7.x-3.2
    $paths[] = drupal_get_path('module', 'modernizr');

    // Scan directories for files
    $path = _modernizr_scan_for_library($paths);
  }
  return $path;
}

/**
 * Helper function to scan for acceptably named libraries
 */
function _modernizr_scan_for_library($paths) {
  $path = '';
  foreach ($paths as $p) {
    if ($files = file_scan_directory($p, MODERNIZR_FILENAME_REGEX)) {
      $path = reset($files)->uri;
      break;
    }
  }
  return $path;
}

/**
 * Helper function to generate the current filename of
 * the active Modernizr library
 */
function modernizr_get_filename() {

  // Get the full path to the library,
  $full_path = modernizr_get_path();

  // Break it up into its directories and file
  $file_parts = explode('/', $full_path);

  // Isolate the filename
  $file_name = $file_parts[count($file_parts) - 1];
  return $file_name;
}

/**
 * Guesses the modernizr library version.
 *
 * This function is using a regex, which assumes that the format of the version
 * string won't change. If it changes, feel free to submit a bug report.
 *
 * @return mixed The version number if exists, or a boolean FALSE if it can't be
 * determined.
 */
function modernizr_get_version($reset = FALSE) {
  $version =& drupal_static(__FUNCTION__);

  // if ($version === NULL || $reset == TRUE) {
  //   if ($cached = cache_get('modernizr_version') && isset($cached->data) && $reset != TRUE) {
  //     $version = $cached->data;
  //   }
  //   else {
  //     $version = FALSE;
  //     $modernizr_path = modernizr_get_path();
  //     if (file_exists($modernizr_path)) {
  //       $modernizr = file_get_contents($modernizr_path);
  //       $matches = array();
  //       preg_match(MODERNIZR_VERSION_REGEX, $modernizr, $matches);
  //       if (isset($matches[1])) {
  //         $version = $matches[1];
  //         if ($version) {
  //           cache_set('modernizr_version', $version);
  //         }
  //       }
  //       unset($modernizr);
  //     }
  //   }
  // }
  return '2.6.2';
}

/**
 * Implements MODULE_preprocess_html().
 */
function modernizr_preprocess_html(&$vars, $hook) {

  // This will set up all of our tests for Modernizr.
  modernizr_load_data();
}

/**
 * A function to generate the load data from the current themes.
 *
 * Reads async-loaded CSS/JS from theme .info files. Stores info in variable.
 * Prints Modernizr.load() calls into drupal_add_js() as inline settings.
 *
 * @return array
 */
function modernizr_load_data() {
  $load =& drupal_static(__FUNCTION__);
  if (!isset($load)) {

    // This is the first time this is called.
    global $base_url, $base_theme_info, $theme_info;
    $load = array();
    $num_tests = 0;

    // Make a list of base themes and the current theme.
    $themes = $base_theme_info;
    $themes[] = $theme_info;
    foreach (array_keys($themes) as $key) {
      $theme_path = dirname($themes[$key]->filename) . '/';
      if (isset($themes[$key]->info['modernizr'])) {

        // Loop through Modernizr calls and assemble Load variable.
        foreach (array_keys($themes[$key]->info['modernizr']) as $test) {

          // Skip the ['tests'] variable because it is reserved for selecting
          // specific tests that Modernizr must include.
          if ($test != 'tests') {

            // All other entries inside a theme's modernizr[] settings should be scanned
            $load[$num_tests]['test'] = $test;
            foreach (array_keys($themes[$key]->info['modernizr'][$test]) as $action) {
              foreach ($themes[$key]->info['modernizr'][$test][$action] as $asset) {

                // First figure out which property we're reading.
                // callback/complete need different processing than yep/nope/both/load
                $functions = array(
                  'callback',
                  'complete',
                );

                // Is this a function or a resource?
                if (in_array($action, $functions)) {

                  // It's a function
                  $load[$num_tests][$action][] = _modernizr_sanitize_callback($asset);
                }
                else {

                  // It's a resource
                  $load[$num_tests][$action][] = _modernizr_sanitize_resource($asset, $theme_path);
                }
              }
            }
            $num_tests++;
          }
        }
      }
    }
  }
  return $load;
}

/**
 * Helper function to render the Modernizr.load() calls.
 */
function _modernizr_load_generate() {
  $output = FALSE;

  // Get Modernizr.load() calls from the active theme.
  $theme = modernizr_load_data();

  // Collect data from modules that implement hook_modernizr_load().
  $modules = modernizr_load_list();

  // Combine the data from the .info file and the Drupal modules.
  // Themes go first because they are more visual and in most cases
  // it's probably best to load them first. Modules whose assets
  // truly need to be loaded first have hook_modernizr_load_alter()
  // at their disposal.
  $testObjects = array_merge($theme, $modules);

  // Build the Modernizr.load() commands.
  if (count($testObjects)) {
    $num_tests = 1;
    $output .= 'Modernizr.load([';
    foreach ($testObjects as $load) {
      $output .= $num_tests > 1 ? ',' : '';
      $output .= '{' . "\n";
      $output .= '  test: ' . $load['test'] . ',' . "\n";

      // Print each action and its resources
      $actions = array(
        'yep',
        'nope',
        'both',
        'load',
      );
      foreach ($actions as $action) {
        if (isset($load[$action])) {

          // Begin output for this action
          $output .= '  ' . sprintf('%-4s', $action) . ': ';

          // How many resources for this action?
          if (count($load[$action]) == 1) {

            // Single resource
            $output .= "'" . $load[$action][0] . "',\n";
          }
          else {

            // Multiple resources
            $output .= '[';
            foreach ($load[$action] as $resource) {
              $output .= "'" . $resource . "',";
            }

            // Truncate last comma
            $output = substr($output, 0, -1);
            $output .= "],\n";
          }
        }
      }

      // Output these two properties without quotes around the output
      $callbacks = array(
        'callback',
        'complete',
      );
      foreach ($callbacks as $action) {
        if (isset($load[$action])) {

          // Begin output for this action
          $output .= '  ' . sprintf('%-4s', $action) . ': ';

          // How many callbacks for this action?
          if (count($load[$action]) == 1) {

            // Single resource
            $output .= $load[$action][0] . ",\n";
          }
          else {

            // Multiple resources
            $output .= '[';
            foreach ($load[$action] as $callback) {
              $output .= $callback . ",";
            }

            // Truncate last comma
            $output = substr($output, 0, -1);
            $output .= "],\n";
          }
        }
      }

      // Truncate last comma and newline
      $output = substr($output, 0, -2);
      $output .= "\n}";
      $num_tests++;
    }

    // If more than one test was registered, finish the Array notation.
    // Finally, close the Modernizr.load() function parenthesis.
    $output .= $num_tests > 1 ? ']' : '';
    $output .= ');';
  }
  return $output;
}

/**
 * Implements MODULE_preprocess_maintenance_page().
 */
function modernizr_preprocess_maintenance_page(&$vars, $hook) {
  modernizr_preprocess_html($vars, $hook);
}

/**
 * Helper function to sanitize Modernizr.load() callbacks
 */
function _modernizr_sanitize_callback($callback) {
  global $base_url;
  $output = '';
  $function_regex = '/^function(\\s)*\\(\\)(\\s)*\\{(.*)\\}$/';

  // Save the people who don't wrap their code in anonymous functions.
  // Yes, an extra semi-colon has been added for safety :)
  $output = preg_match($function_regex, $callback) ? $callback : 'function(){' . $callback . ';}';
  return $output;
}

/**
 * Helper function to sanitize Modernizr.load() assets
 */
function _modernizr_sanitize_resource($resource, $theme_path) {
  global $base_url;
  $output = '';

  // If a path starts with 'sites' we assume they know exactly where they're
  // going. Otherwise, they seem like relative URLs so append theme path.
  $output = strpos($resource, 'sites/') !== FALSE ? $resource : $base_url . '/' . $theme_path . $resource;
  return $output;
}

/**
 * Helper function for hook_modernizr_info().
 * Returns a Modernizr argument's type.
 */
function _modernizr_get_type($arg) {
  $data = _modernizr_get_arg_info($arg, 'type');

  // Since community-created detects are by far the most likely unknown entry,
  // we assume that a value not found in modernizr.args.inc is a community detect.
  // Note: 'tests' does NOT need t() because it is a machine value.
  return $data ? $data : 'tests';
}

/**
 * Helper function for hook_modernizr_info().
 * Returns a Modernizr argument's description.
 */
function _modernizr_get_desc($arg) {
  $data = _modernizr_get_arg_info($arg, 'desc');

  // If we can't find a description, just admit it.
  return $data ? $data : t('No description available.');
}

/**
 * A helper function to get the information stored in modernizr.args.inc.
 *
 * @param string $arg
 *   The test machine name.
 * @param string $type (default: 'desc')
 *   The data wanted, currently just 'desc' or 'type'.
 * @return
 *   The data in the field, or FALSE if it doesn't exist.
 */
function _modernizr_get_arg_info($arg, $type = 'desc') {
  static $loaded = FALSE;
  if (!$loaded) {
    $loaded = module_load_include('inc', 'modernizr', 'modernizr.args');
  }
  $data = _modernizr_args_return($arg);

  // This data doesnt exist.
  return $data && isset($data[$type]) ? $data[$type] : FALSE;
}

/**
 * Helper function to pulls all tests from the current modernizr.js
 */
function _modernizr_current_build() {
  $tests =& drupal_static(__FUNCTION__);
  if (!isset($tests)) {
    $path = modernizr_get_path();
    $path_parts = explode('/', $path);
    $file = $path ? file_get_contents($path) : NULL;
    $filename = $path_parts[count($path_parts) - 1];
    $tests = array();

    // $matches holds two items:
    // - [0] the full URL
    // - [1] a string containing the args captured in the parens  vvvv
    $build_url = preg_match('/http:\\/\\/modernizr.com\\/download\\/#-(.*)/', $file, $matches);

    // Turn URL args into test entries for Drupal module
    if (isset($matches[1])) {
      $args_and_prefix = explode(':', $matches[1]);
      $build_args = explode('-', $args_and_prefix[0]);
      foreach ($build_args as $arg) {
        $tests[] = $arg;
      }
    }
    else {

      // Modernizr must not be downloaded, return null.
      return null;
    }
  }
  return $tests;
}

/**
 * Asks other Drupal modules which Modernizr tests they need.
 *
 * @return array
 */
function modernizr_api_list() {
  $tests =& drupal_static(__FUNCTION__);
  if (!isset($tests)) {

    // Grab all module implementations
    // Note: this is a slightly augmented version of module_invoke_all(), so
    // that we can know which module is providing which test.
    $hook = 'modernizr_info';
    foreach (module_implements($hook) as $module) {
      $function = $module . '_' . $hook;
      if (function_exists($function)) {
        $result = call_user_func($function);
        if (isset($result) && is_array($result)) {
          $tests[$module] = $result;
        }
      }
    }

    // Grabbing the information with an hook_alter is not enough for themes
    // because they will not all be active, nor their code added into the session.
    $themes = list_themes();
    $active_themes = array();
    foreach ($themes as $theme_name => $theme) {
      if ($theme->status == 1) {
        $active_themes[$theme_name] = $theme;
        if (isset($theme->base_themes)) {
          foreach ($theme->base_themes as $base_theme_name => $base_theme) {
            $active_themes[$base_theme_name] = $themes[$base_theme_name];
          }
        }
      }
    }

    // We now go into every active theme and pull from the .info file the tests.
    foreach ($active_themes as $active_theme) {
      $data = drupal_parse_info_file($active_theme->filename);
      if (isset($data['modernizr']) && isset($data['modernizr']['tests'])) {

        // There are modernizr tests within this theme.
        $theme_name = $data['name'];
        $tests[$theme_name] = $data['modernizr']['tests'];
      }
    }

    // The last thing we do is send it to have its data cleaned and organized.
    $tests = _modernizr_api_list_clean($tests);
  }
  return $tests;
}

/**
 * Cleans up the array of tests to be unified.
 *
 * @param $raw_tests array
 *  An array of tests provided by hook_modernizr_info.
 * @return array
 */
function _modernizr_api_list_clean($raw_tests) {
  $clean_tests = array();
  foreach ($raw_tests as $module => $tests) {
    foreach ($tests as $name => $data) {

      // First, we check and correct if the tests have been added using indexed
      // arrays, fixing the name variable.
      if (is_int($name) && !is_array($data)) {

        // The test is stored as a simple array, therefore the data is the name.
        $name = $data;
        $data = array();
      }
      elseif (is_int($name) && is_array($data)) {

        // Still stored as a indexed array, but the data is an array.
        $name = $data['name'];
      }

      // Now, we add these tests to our array of cleaned up data.
      if (isset($clean_tests[$name])) {

        // We already have the test, we are just going to add our module name.
        $clean_tests[$name]['source'][] = $module;
      }
      else {

        // The test has not been marked, we are adding it to the array.
        $clean_tests[$name] = $data;
        $clean_tests[$name]['source'] = array(
          $module,
        );
      }
    }
  }

  // Cleaning up the data to ensure all data we need is present.
  foreach ($clean_tests as $name => $clean_test) {
    $data = array(
      'name' => $name,
      'desc' => _modernizr_get_desc($name),
      'docs' => '',
      'camiuse' => '',
    );
    $clean_tests[$name] = array_merge($data, $clean_test);
  }
  return $clean_tests;
}

/**
 * Implements hook_modernizr_info().
 *
 * This function implements our own hook to ensure that any custom Modernizr
 * builds downloaded from either the settings screen or drush commands contain
 * the essential components needed to support the module's functionality.
 *
 * cssclasses
 * - Automatically adds cssclasses to download links since most users want this.
 *
 * html5shiv w/ printshiv
 * - Includes some utility JS that allows IE to recognize HTML5 elements
 *
 * load
 * - Includes yepnope.js as Modernizr.load() - allows conditional asynchronous
 *   CSS/JS loading.
 */
function modernizr_modernizr_info() {
  $items = array();
  $items[] = 'cssclasses';
  $items[] = 'printshiv';
  $items[] = 'load';
  return $items;
}

/**
 * Asks other Drupal modules for Modernizr.load() commands.
 *
 * @return array
 */
function modernizr_load_list($reset = FALSE) {
  $load =& drupal_static(__FUNCTION__);
  if (!isset($load) || $reset) {
    $load = module_invoke_all('modernizr_load');
    drupal_alter('modernizr_load', $load);
  }
  return $load;
}

Functions

Namesort descending Description
modernizr_api_list Asks other Drupal modules which Modernizr tests they need.
modernizr_get_filename Helper function to generate the current filename of the active Modernizr library
modernizr_get_path Returns the full path of modernizr, along with the filename.
modernizr_get_version Guesses the modernizr library version.
modernizr_libraries_info Implements hook_libraries_info().
modernizr_load_data A function to generate the load data from the current themes.
modernizr_load_list Asks other Drupal modules for Modernizr.load() commands.
modernizr_menu Implements hook_menu().
modernizr_modernizr_info Implements hook_modernizr_info().
modernizr_page_build Implements hook_page_build().
modernizr_permission Implements hook_permission().
modernizr_preprocess_html Implements MODULE_preprocess_html().
modernizr_preprocess_maintenance_page Implements MODULE_preprocess_maintenance_page().
_modernizr_api_list_clean Cleans up the array of tests to be unified.
_modernizr_current_build Helper function to pulls all tests from the current modernizr.js
_modernizr_get_arg_info A helper function to get the information stored in modernizr.args.inc.
_modernizr_get_desc Helper function for hook_modernizr_info(). Returns a Modernizr argument's description.
_modernizr_get_type Helper function for hook_modernizr_info(). Returns a Modernizr argument's type.
_modernizr_load_generate Helper function to render the Modernizr.load() calls.
_modernizr_sanitize_callback Helper function to sanitize Modernizr.load() callbacks
_modernizr_sanitize_resource Helper function to sanitize Modernizr.load() assets
_modernizr_scan_for_library Helper function to scan for acceptably named libraries

Constants

Globals