123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661 |
- <?php
- /**
- * @file
- * Handles compiling of .less files.
- *
- * The theme system allows for nearly all output of the Drupal system to be
- * customized by user themes.
- */
- define('LESS_PERMISSION', 'administer less');
- define('LESS_AUTOPREFIXER', 'less_autoprefixer');
- define('LESS_DEVEL', 'less_devel');
- define('LESS_WATCH', 'less_watch');
- define('LESS_SOURCE_MAPS', 'less_source_maps');
- define('LESS_DIRECTORY', 'public://less');
- require_once dirname(__FILE__) . '/includes/less.libraries.inc';
- require_once dirname(__FILE__) . '/includes/less.wysiwyg.inc';
- require_once dirname(__FILE__) . '/includes/less.theme.inc';
- /**
- * Implements hook_hook_info().
- */
- function less_hook_info() {
- $less_hooks = array(
- 'engines',
- 'variables',
- 'paths',
- 'functions',
- );
- $hooks = array();
- /**
- * We don't have to worry about less_HOOK_SYSTEM_NAME_alter variations here
- * as less_HOOK_alter is run immediately before and should include the
- * MODULE.less.inc file containing any
- * less_HOOK_SYSTEM_NAME_alter() implementations.
- */
- foreach ($less_hooks as $hook) {
- $hooks[] = 'less_' . $hook;
- $hooks[] = 'less_' . $hook . '_alter';
- }
- return array_fill_keys($hooks, array(
- 'group' => 'less',
- ));
- }
- /**
- * Implements hook_menu().
- */
- function less_menu() {
- $items = array();
- $items['admin/config/development/less'] = array(
- 'title' => 'LESS',
- 'description' => 'Administer LESS settings',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('less_settings_form'),
- 'access arguments' => array(LESS_PERMISSION),
- 'file' => 'includes/less.admin.inc',
- 'type' => MENU_NORMAL_ITEM,
- );
- $items['admin/config/development/less/settings'] = array(
- 'title' => 'LESS Settings',
- 'type' => MENU_DEFAULT_LOCAL_TASK,
- );
- $items['ajax/less/watch'] = array(
- 'title' => 'LESS watch callback',
- 'type' => MENU_CALLBACK,
- 'page callback' => '_less_watch',
- 'access callback' => 'variable_get',
- 'access arguments' => array(LESS_WATCH, FALSE),
- 'delivery callback' => 'drupal_json_output',
- 'file' => 'includes/less.watch.inc',
- );
- return $items;
- }
- /**
- * Implements hook_permission().
- */
- function less_permission() {
- return array(
- LESS_PERMISSION => array(
- 'title' => t('Administer LESS'),
- 'description' => t('Access the LESS settings page and view debug messages.'),
- ),
- );
- }
- /**
- * Implements hook_element_info_alter().
- */
- function less_element_info_alter(&$type) {
-
- // Prepend to the list of #pre_render functions so it runs first.
- array_unshift($type['styles']['#pre_render'], '_less_pre_render');
- if (variable_get(LESS_DEVEL, FALSE)) {
-
- // Must run after drupal_pre_render_styles() to attach any attributes.
- array_push($type['styles']['#pre_render'], '_less_attach_src');
- }
- }
- /**
- * Add original .less file path as 'src' attribute to <link />.
- *
- * @param array $styles
- * CSS style tags after drupal_pre_render_styles() has run.
- *
- * @return array
- * Styles array with 'src' attributes on LESS files.
- *
- * @see drupal_pre_render_styles()
- */
- function _less_attach_src($styles) {
-
- foreach (element_children($styles) as $key) {
-
- // If its a <link />, then most likely its a compiled .less file.
- if ($styles[$key]['#tag'] == 'link') {
-
- // Hashes are generated based on the URL without the query portion.
- $file_url_parts = drupal_parse_url($styles[$key]['#attributes']['href']);
-
- // If we have a match, it means it is a compiled .less file.
- if ($cache = cache_get('less:watch:' . drupal_hash_base64($file_url_parts['path']))) {
-
- // Some inspectors allow 'src' attribute to open from a click.
- $styles[$key]['#attributes']['src'] = url($cache->data['less']['input_file']);
- }
- }
- }
-
- return $styles;
- }
- /**
- * Pre-render function for 'style' elements.
- *
- * Key place where .less files are detected and processed.
- *
- * @param array $styles
- * All 'style' elements that are to display on the page.
- *
- * @return array
- * Modified style elements pointing to compiled LESS output.
- */
- function _less_pre_render($styles) {
-
- $less_devel = (bool) variable_get(LESS_DEVEL, FALSE);
-
- if ($less_devel) {
-
- if (variable_get(LESS_WATCH, FALSE)) {
- drupal_add_js(drupal_get_path('module', 'less') . '/scripts/less.watch.js');
- }
-
- // Warn users once every hour that less is checking for file modifications.
- if (user_access(LESS_PERMISSION) && flood_is_allowed('less_devel_warning', 1)) {
- flood_register_event('less_devel_warning');
-
- $message_vars = array(
- '@url' => url('admin/config/development/less'),
- );
- drupal_set_message(t('LESS files are being checked for modifications on every request. Remember to <a href="@url">turn off</a> this feature on production websites.', $message_vars), 'status');
- }
- }
-
- $less_items = array_intersect_key($styles['#items'], array_flip(_less_children($styles['#items'])));
- if (!empty($less_items)) {
-
- require_once dirname(__FILE__) . '/includes/less.process.inc';
-
- // Attach settings to each item.
- array_walk($less_items, '_less_attach_settings');
- // Determine output path for each item.
- array_walk($less_items, '_less_output_path');
- // Check for rebuild each page.
- if ($less_devel) {
- array_walk($less_items, '_less_check_build');
- }
- // Compile '.less' files.
- array_walk($less_items, '_less_process_file');
- // Store cache information.
- if ($less_devel) {
- array_walk($less_items, '_less_store_cache_info');
- }
- $styles['#items'] = array_replace($styles['#items'], $less_items);
- }
-
- return $styles;
- }
- /**
- * Implements hook_admin_menu_cache_info().
- */
- function less_admin_menu_cache_info() {
-
- $caches = array();
-
- // Add item to admin_menu's flush caches menu.
- $caches['less'] = array(
- 'title' => t('LESS compiled files'),
- 'callback' => 'less_flush_caches',
- );
-
- return $caches;
- }
- /**
- * Implements hook_cron_queue_info().
- *
- * This hook runs before cache flush during cron. Reliably lets us know if its
- * cron or not.
- */
- function less_cron_queue_info() {
- drupal_static('less_cron', TRUE);
- }
- /**
- * Implements hook_flush_caches().
- *
- * Triggers rebuild of all LESS files during cache flush, except during cron.
- */
- function less_flush_caches() {
- if (!drupal_static('less_cron')) {
-
- // Rebuild the less files directory.
- _less_get_dir(TRUE);
- cache_clear_all('less:', 'cache', TRUE);
- }
- less_clear_css_cache();
- return array();
- }
- /**
- * Deletes all stale compiled LESS files that are no longer in use.
- *
- * @see drupal_delete_file_if_stale().
- */
- function less_clear_css_cache() {
- file_scan_directory(LESS_DIRECTORY, '/.+/', array('callback' => 'drupal_delete_file_if_stale'));
- }
- /**
- * Get/(re)generate current 'less_dir' variable.
- *
- * @param bool $rebuild
- * Flag to rebuild compiled output.
- *
- * @return string
- * current 'less_dir' Drupal variable value.
- */
- function _less_get_dir($rebuild = FALSE) {
- $less_dir = variable_get('less_dir');
-
- // If drupal variable 'less_dir' is not set, empty, or manually reset, then
- // generate a new unique id and save it.
- if ($rebuild || empty($less_dir)) {
-
- // Set the less directory variable.
- variable_set('less_dir', drupal_hash_base64(uniqid('', TRUE)));
- }
-
- return variable_get('less_dir');
- }
- /**
- * Loads the selected LESS engine, or 'lessphp' for legacy reasons.
- *
- * @return bool
- * TRUE if selected LESS engine is loaded.
- */
- function _less_inc() {
- static $loaded = NULL;
-
- if (!isset($loaded)) {
-
- $less_engine = variable_get('less_engine', 'lessphp');
-
- if (($less_engine_library = libraries_load($less_engine)) && $less_engine_library['installed']) {
- $loaded = $less_engine;
- }
- }
-
- return $loaded;
- }
- /**
- * Keeps track of .less file "ownership".
- *
- * This keeps track of which modules and themes own which .less files, and any
- * variable defaults those system items define.
- *
- * Only tracks .less files that are added through .info files.
- */
- function _less_registry() {
- $static_stylesheets = &drupal_static('less_stylesheets');
- $static_defaults = &drupal_static('less_defaults');
-
- if (!isset($static_stylesheets) || !isset($static_defaults)) {
-
- if (($cache_stylesheets = cache_get('less:stylesheets')) && ($cache_defaults = cache_get('less:defaults'))) {
- $static_stylesheets = $cache_stylesheets->data;
- $static_defaults = $cache_defaults->data;
- }
- else {
-
- $system_types = array(
- 'module_enabled',
- 'theme',
- );
-
- foreach ($system_types as $system_type) {
- $system_items = system_list($system_type);
-
- foreach ($system_items as $system_item_name => $system_item) {
-
- // Register all globally included .less stylesheets.
- if (!empty($system_item->info['stylesheets'])) {
- foreach ($system_item->info['stylesheets'] as $stylesheets) {
- foreach ($stylesheets as $stylesheet) {
- if (_less_is_less_filename($stylesheet)) {
- $static_stylesheets[$stylesheet] = $system_item_name;
- }
- }
- }
- }
-
- // Process LESS settings from .info files.
- if (isset($system_item->info['less']) && is_array($system_item->info['less'])) {
-
- // Register all non-global stylesheets.
- if (isset($system_item->info['less']['sheets']) && is_array($system_item->info['less']['sheets'])) {
-
- $system_item_path = drupal_get_path($system_item->type, $system_item->name);
-
- foreach ($system_item->info['less']['sheets'] as $stylesheet) {
- $static_stylesheets[$system_item_path . '/' . $stylesheet] = $system_item_name;
- }
- }
-
- // Register variable defaults.
- if (isset($system_item->info['less']['vars']) && is_array($system_item->info['less']['vars'])) {
- $static_defaults[$system_item_name] = $system_item->info['less']['vars'];
- }
- }
-
- // Invoke hook_less_variables(), results should be static.
- if (module_exists($system_item_name) && ($module_defaults = module_invoke($system_item_name, 'less_variables'))) {
- $static_defaults[$system_item_name] = array_replace((array) $static_defaults[$system_item_name], array_filter($module_defaults));
- }
- }
- }
- cache_set('less:stylesheets', $static_stylesheets);
- cache_set('less:defaults', $static_defaults);
- }
- }
-
- }
- /**
- * Returns .less file "owner".
- *
- * Returns the owning module/theme for a passed in .less file, or NULL.
- * Only can resolve .less files that are added using .info files.
- *
- * @param string $filepath
- * System path to .less file, relative to DRUPAL_ROOT.
- *
- * @return string|NULL
- * System name of .less file "owner" or NULL in case of no known "owner".
- */
- function _less_file_owner($filepath) {
- // Use the advanced drupal_static() pattern, since this is called very often.
- static $drupal_static_fast;
- if (!isset($drupal_static_fast)) {
- $drupal_static_fast['cache'] = &drupal_static('less_stylesheets');
-
- if (!isset($drupal_static_fast['cache'])) {
- _less_registry();
- }
- }
- $stylesheets_cache = &$drupal_static_fast['cache'];
-
- return isset($stylesheets_cache[$filepath]) ? $stylesheets_cache[$filepath] : NULL;
- }
- /**
- * Returns the compiled list of variables and functions for a module/theme.
- *
- * @param string $system_name
- * Module/theme system name. NULL is cast to empty string for array indexes.
- */
- function less_get_settings($system_name = NULL) {
-
- // Use the advanced drupal_static() pattern, since this is called very often.
- static $drupal_static_fast;
- if (!isset($drupal_static_fast)) {
- $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
- }
- $less_settings_static = &$drupal_static_fast['cache'];
-
-
- if (!isset($less_settings_static[$system_name])) {
-
- global $theme;
- $valid_module = !empty($system_name) && module_exists($system_name);
- $theme_settings = theme_get_setting('less', $theme);
-
- $defaults_cache = &drupal_static('less_defaults');
-
- if (!isset($defaults_cache)) {
- _less_registry();
- }
-
- // Defaults.
- $data = array(
- 'build_cache_id' => _less_get_dir(),
- 'variables' => array(),
- 'functions' => array(
- 'token' => '_less_token_replace',
- ),
- 'paths' => array(),
- LESS_AUTOPREFIXER => (bool) variable_get(LESS_AUTOPREFIXER, FALSE),
- LESS_DEVEL => (bool) variable_get(LESS_DEVEL, FALSE),
- LESS_SOURCE_MAPS => (bool) variable_get(LESS_SOURCE_MAPS, FALSE),
- 'theme' => $theme,
- );
-
-
- /*
- * Compile the LESS variables.
- */
- // Cached default variables from .info files and hook_less_variables().
- if (!empty($defaults_cache[$system_name])) {
- $data['variables'] = array_replace($data['variables'], array_filter($defaults_cache[$system_name]));
- }
-
- // Saved variable values from current theme.
- if (!is_null($theme_settings) && !empty($theme_settings[$system_name])) {
- $data['variables'] = array_replace($data['variables'], array_filter($theme_settings[$system_name]));
- }
-
- // Prevent $system_name from being altered.
- $alter_system_name = $system_name;
- // Invoke hook_less_variables_alter().
- drupal_alter('less_variables', $data['variables'], $alter_system_name);
- // Invoke hook_less_variables_SYSTEM_NAME_alter().
- drupal_alter('less_variables_' . $system_name, $data['variables']);
-
-
- /*
- * Grab the LESS functions.
- *
- * LESS functions are not stored in the cache table since they could be
- * anonymous functions.
- */
- if ($valid_module && module_hook($system_name, 'less_functions')) {
- $data['functions'] = array_replace($data['functions'], (array) module_invoke($system_name, 'less_functions'));
- }
-
- // Prevent $system_name from being altered.
- $alter_system_name = $system_name;
- // Invoke hook_less_functions_alter().
- drupal_alter('less_functions', $data['functions'], $alter_system_name);
- // Invoke hook_less_functions_SYSTEM_NAME_alter().
- drupal_alter('less_functions_' . $system_name, $data['functions']);
-
-
- /*
- * Grab the LESS include paths.
- *
- */
- if ($valid_module && module_hook($system_name, 'less_paths')) {
- $data['paths'] = array_unique(array_merge($data['paths'], (array) module_invoke($system_name, 'less_paths')));
- }
-
- // Prevent $system_name from being altered.
- $alter_system_name = $system_name;
- // Invoke hook_less_paths_alter().
- drupal_alter('less_paths', $data['paths'], $alter_system_name);
- // Invoke hook_less_paths_SYSTEM_NAME_alter().
- drupal_alter('less_paths_' . $system_name, $data['paths']);
-
- $data['paths'] = array_unique($data['paths']);
-
- $less_settings_static[$system_name] = $data;
- }
-
- // Don't need to test isset(), there will always be data at $system_name.
- return $less_settings_static[$system_name];
- }
- /**
- * Handler for LESS function token().
- *
- * @param string[] $arg
- *
- * @return array
- */
- function _less_token_replace($arg) {
- list($type, $delimiter, $value) = $arg;
-
- return array($type, $delimiter, array(token_replace($value[0])));
- }
- /**
- * Helper function that attempts to create a folder if it doesn't exist.
- *
- * Locks are used to help avoid concurrency collisions.
- *
- * @param string $directory_path
- * Directory of which to create/confirm existence.
- *
- * @return bool
- * Value indicating existence of directory.
- */
- function _less_ensure_directory($directory_path) {
-
- $is_dir = is_dir($directory_path);
-
- if (!$is_dir) {
-
- $lock_id = 'less_directory_' . md5($directory_path);
-
- // Attempt to create directory only 3 times, else delay is too long.
- for ($i = 0; $i < 3; $i++) {
-
- if (lock_acquire($lock_id) && $is_dir = file_prepare_directory($directory_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
- // Creation was successful, cancel the 'for' loop;
- break;
- }
-
- lock_wait($lock_id, 1);
- }
-
- lock_release($lock_id);
-
- if (!$is_dir) {
- // There is a problem with the directory.
- $message_vars = array(
- '%dir' => $directory_path,
- );
-
- watchdog('LESS', 'LESS could not create a directory in %dir', $message_vars, WATCHDOG_ERROR);
-
- if (user_access(LESS_PERMISSION)) {
- drupal_set_message(t('LESS could not create a directory in %dir', $message_vars), 'error', FALSE);
- }
-
- }
- }
-
- return $is_dir;
- }
- /**
- * Return keys from array that match '.less' file extension.
- *
- * @param array $items
- * An array where keys are expected to be filepaths.
- *
- * @return array
- * Array of matching filepaths.
- */
- function _less_children($items) {
-
- return array_filter(array_keys($items), '_less_is_less_filename');
-
- }
- /**
- * Check if filename has '.less' extension.
- *
- * @param string $filename
- * File name/path to search for '.less' extension.
- *
- * @return bool
- * TRUE if $filename does end with '.less'.
- */
- function _less_is_less_filename($filename) {
-
- return drupal_substr($filename, -5) === '.less';
- }
- /**
- * Implements hook_less_engines().
- *
- * @return string[]
- */
- function less_less_engines() {
- return array(
- 'less.php' => 'LessEngineLess_php',
- 'lessphp' => 'LessEngineLessphp',
- 'less.js' => 'LessEngineLess_js',
- );
- }
- /**
- * @return \LessEngineInterface[]
- */
- function _less_get_engines() {
- $registered_engines = module_invoke_all('less_engines');
- drupal_alter('less_engines', $registered_engines);
- return $registered_engines;
- }
- /**
- * @param $input_file_path
- *
- * @return \LessEngine
- *
- * @throws Exception
- */
- function less_get_engine($input_file_path) {
- $engines = _less_get_engines();
- $selected_engine = _less_inc();
- if (!empty($engines[$selected_engine])) {
- $class_name = $engines[$selected_engine];
- return new $class_name($input_file_path);
- }
- else {
- throw new Exception('Unable to load LessEngine.');
- }
- }
|