(variable_get('modernizr_type', MODERNIZR_TYPE_DEFAULT) != 'inline') ? 'file' : 'inline', 'scope' => variable_get('modernizr_scope', MODERNIZR_SCOPE_DEFAULT), 'group' => MODERNIZR_SCRIPT_GROUP, 'weight' => MODERNIZR_SCRIPT_WEIGHT, 'every_page' => TRUE, 'preprocess' => 0, 'defer' => (variable_get('modernizr_type', MODERNIZR_TYPE_DEFAULT) == 'defer') ? TRUE : FALSE, ); } /** * Implements hook_page_build(). * * We used to use hook_init(), but that loads the JS files needlessly * on AJAX requests, private file requests, etc. */ function modernizr_page_build(&$page) { global $base_url; $modernizr_js_settings = _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 { // First, figure out if we're inlining. if (in_array(variable_get('modernizr_type', MODERNIZR_TYPE_DEFAULT), array('sync', 'defer'))) { // We are loading external script. Load file path. $modernizr_file = modernizr_get_path(); } else { // We are inlining. Load contents of file instead of path. $modernizr_file = file_get_contents(modernizr_get_path()); } // With no Libraries API, load the regular way. drupal_add_js( $modernizr_file, $modernizr_js_settings ); } // We want yepnope() 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()) { // Modernizr v3 removed the ability to include yepnope.js directly in the // custom builds. To ensure that previous users of this module can continue // without breaking changes, we need to load a copy of yepnope manually, // which Modernizr detects and aliases to yepnope(). if ($output && variable_get('modernizr_cb_load', MODERNIZR_YEPNOPE_DEFAULT)) { $yepnope_settings = $modernizr_js_settings; $yepnope_settings['type'] = 'inline'; $yepnope_settings['weight'] = MODERNIZR_SCRIPT_WEIGHT - 1; // yepnope.js drupal_add_js( file_get_contents(drupal_get_path('module', 'modernizr') . '/js/yepnope.js'), $yepnope_settings ); $inline_js_settings = $modernizr_js_settings; $inline_js_settings['type'] = 'inline'; $inline_js_settings['weight'] = MODERNIZR_SCRIPT_WEIGHT + 1; // yepnope() statements drupal_add_js( $output, $inline_js_settings ); } // If there are yepnope commands being requested, but the module does not // have yepnope enabled, warn the user in the console. else if ($output && !variable_get('modernizr_cb_load', MODERNIZR_YEPNOPE_DEFAULT)) { drupal_add_js('console.warn("The Modernizr module is receiving requests to use yepnope.js but that option is currently disabled. Please enable yepnope.js by loading ' . $base_url . '/admin/config/development/modernizr/settings' . '");', array('type' => 'inline')); } } } /** * Implements hook_permission(). */ function modernizr_permission() { return array( 'administer modernizr' => array( 'title' => t('Administer Modernizr'), 'description' => t('Perform administration tasks for Modernizr.'), ), ); } /** * Implements hook_menu(). */ function modernizr_menu() { $items = array(); // Rebuild Modernizr $items['admin/config/development/modernizr/rebuild'] = array( 'title' => 'Rebuild Modernizr', 'description' => 'Queries Drupal for Modernizr dependencies and generates a custom link to the Modernizr builder.', 'page callback' => 'modernizr_generate_url', 'file' => 'modernizr.admin.inc', 'type' => MENU_DEFAULT_LOCAL_TASK, 'access arguments' => array('administer modernizr'), ); // Module settings $items['admin/config/development/modernizr/settings'] = array( 'title' => 'Modernizr settings', 'description' => 'Administrative settings for Modernizr module.', 'page callback' => 'drupal_get_form', 'page arguments' => array('modernizr_admin'), 'file' => 'modernizr.admin.inc', 'type' => MENU_LOCAL_TASK, 'access arguments' => array('administer modernizr'), ); // Admin menu item (duplicate of "Rebuild Modernizr") $items['admin/config/development/modernizr'] = array( 'title' => 'Modernizr', 'description' => 'Queries Drupal for Modernizr dependencies and generates a custom link to the Modernizr builder.', 'page callback' => 'modernizr_generate_url', 'file' => 'modernizr.admin.inc', 'type' => MENU_NORMAL_ITEM, 'access arguments' => array('administer modernizr'), ); return $items; } /** * Implements hook_libraries_info(). * * @return array */ function modernizr_libraries_info() { $modernizr_js_settings = _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) { // Get possible paths for the file. $paths = _modernizr_get_paths(); // Scan directories for files $path = _modernizr_scan_for_library($paths); } return $path; } /** * Helper function to scan for acceptably named libraries */ function _modernizr_get_paths() { $paths = &drupal_static(__FUNCTION__); if ($paths === NULL) { $paths = array(); foreach (_modernizr_library_search_paths() as $search_path) { $library_path = $search_path . '/modernizr'; if (file_exists($library_path)) { $paths[] = $library_path; } } } return $paths; } /** * Get library search paths. * * Original logic was taken from Libraries 7.x-2.3 since it doesn't provide a * way to look up its search paths. * * @see libraries_get_libraries() */ function _modernizr_library_search_paths() { $searchdir = array(); $profile = drupal_get_path('profile', drupal_get_profile()); $config = conf_path(); // Similar to 'modules' and 'themes' directories in the root directory, // certain distributions may want to place libraries into a 'libraries' // directory in Drupal's root directory. $searchdir[] = 'libraries'; // Always search sites/all/libraries. $searchdir[] = 'sites/all/libraries'; // $profile should never be empty in a proper Drupal setup. Check to make sure // it exists before adding path. if ($profile) { // Similar to 'modules' and 'themes' directories inside an installation // profile, installation profiles may want to place libraries into a // 'libraries' directory. $searchdir[] = $profile . '/libraries'; } // $config should never be empty in a proper Drupal setup. Check to make sure // it exists before adding path. if ($config) { // Also search sites//*. $searchdir[] = $config . '/libraries'; } return $searchdir; } /** * Helper function to scan for acceptably named libraries */ function _modernizr_scan_for_library($paths) { $path = ''; if (is_array($paths) && !empty($paths)) { foreach ($paths as $p) { if ($files = file_scan_directory($p, MODERNIZR_FILENAME_REGEX)) { $path = reset($files)->uri; break; } } } return $path; } /** * Helper function to fetch 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[0])) { $version = $matches[0]; if ($version) { cache_set('modernizr_version', $version); } } unset($modernizr); } } } return $version; } /** * 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 yepnope() 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_key => $test) { // If no tests are defined, simply add them as resources for loading them unconditionally. if (is_numeric($test)) { $load[] = array(_modernizr_sanitize_resource($themes[$key]->info['modernizr'][$test_key], $theme_path)); } // Skip the ['tests'] variable because it is reserved for selecting // specific tests that Modernizr must include. elseif ($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 yepnope() calls. */ function _modernizr_load_generate() { $output = FALSE; // Get yepnope() 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. $test_objects = array_merge($theme, $modules); // Build the yepnope() commands. if (count($test_objects)) { $num_tests = 0; $items = array(); foreach ($test_objects as $load) { // If test is defined, this entry will be an object. if (isset($load['test'])) { $item = '{' . "\n"; $item .= ' 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 $item .= ' ' . sprintf('%-4s', $action) . ': '; // How many resources for this action? if (count($load[$action]) == 1) { // Single resource $item .= "'" . $load[$action][0] . "',\n"; } else { // Multiple resources $item .= '['; foreach ($load[$action] as $resource) { $item .= "'" . $resource . "',"; } // Truncate last comma $item = substr($item, 0, -1); $item .= "],\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 $item .= ' ' . sprintf('%-4s', $action) . ': '; // How many callbacks for this action? if (count($load[$action]) == 1) { // Single resource $item .= $load[$action][0] . ",\n"; } else { // Multiple resources $item .= '['; foreach ($load[$action] as $callback) { $item .= $callback . ","; } // Truncate last comma $item = substr($item, 0, -1); $item .= "],\n"; } } } // Truncate last comma and newline $item = substr($item, 0, -2); $item .= "\n}"; $num_tests++; } // No test is defined, add the resource(s) to the loader unconditionally. else { $resources = array(); foreach ($load as $resource) { $resources[] = "'" . $resource . "'"; } $item = implode(",\n", $resources); $num_tests++; } $items[] = $item; } $output .= 'yepnope('; // Issue commands as array if there is more than resource to load. $output .= ($num_tests > 1) ? '[' : ''; // Add commands. $output .= implode(",\n", $items); // Finally, close the yepnope() 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 yepnope() 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 yepnope() assets */ function _modernizr_sanitize_resource($resource, $theme_path) { global $base_path, $base_url; $output = ''; // If a path starts with 'sites/' or 'profiles/' we assume they know exactly // where they're going. Otherwise, they seem like relative URLs so append // theme path. $output = (strpos($resource, 'sites/') === 0 || strpos($resource, 'profiles/') === 0) ? $base_path . $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('/https?:\/\/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. * * html5shiv w/ printshiv * - Includes some utility JS that allows IE to recognize HTML5 elements. */ function modernizr_modernizr_info() { $items = array(); // If a site admin has chosen to require printshiv, add it to dependencies. if (variable_get('modernizr_cb_printshiv', FALSE)) { $items[] = 'printshiv'; } return $items; } /** * Asks other Drupal modules for yepnope() 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; } /** * Private function to look for missing Modernizr tests. */ function _modernizr_info_missing_tests() { $requested_tests = modernizr_api_list(); $current_build = _modernizr_current_build(); $missing_tests = array(); if (is_null($current_build)) { // There is no installed version of Modernizr. Return all tests. return $requested_tests; } foreach($requested_tests as $test => $test_info) { if (!in_array($test, $current_build)) { $missing_tests[$test] = $test_info; } } return $missing_tests; }