You are here

less.module in Less CSS Preprocessor 6.2

Handles compiling of .less files.

The theme system allows for nearly all output of the Drupal system to be customized by user themes.

File

less.module
View source
<?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');

/**
 * Implements hook_menu().
 */
function less_menu() {
  $items = array();
  $items['admin/settings/less'] = array(
    'title' => 'LESS settings',
    'description' => 'Administer LESS settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'less_settings',
    ),
    'access arguments' => array(
      LESS_PERMISSION,
    ),
    'file' => 'less.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function less_perm() {
  return array(
    LESS_PERMISSION,
  );
}

/**
 * Builds the less cache
 */
function _less_build(&$vars, $hook) {
  $css = $vars['css'];
  $less_devel = variable_get('less_devel', FALSE);
  $less_dir = variable_get('less_dir', '');

  // Flush compiled LESS files if developer mode is enabled
  if (!$less_dir || $less_devel) {
    $less_dir = _less_new_dir();
    if ($less_devel && user_access(LESS_PERMISSION) && flood_is_allowed('less_devel_warning', 1)) {
      flood_register_event('less_devel_warning');
      drupal_set_message(t('LESS files are being regenerated on every request. Remember to <a href="!url">turn off</a> this feature on production websites.', array(
        "!url" => url('admin/settings/less'),
      )), 'status');
    }
  }
  $less_path = file_directory_path() . '/less/' . $less_dir;
  foreach ($css as $media => $types) {
    foreach ($types as $type => $files) {
      $files_keys = array_keys($files);
      foreach ($files_keys as $key => $input_file) {
        if (drupal_substr($input_file, -5) == '.less') {
          $css_path = $less_path . '/' . dirname($input_file);
          if (!is_dir($css_path) && !@mkdir($css_path, 0775, TRUE)) {

            // There is a problem with the directory.
            $param = array(
              '%dir' => $css_path,
            );
            if (user_access(LESS_PERMISSION)) {
              drupal_set_message(t('LESS could not create a directory in %dir', $param), 'error');
            }
            watchdog('LESS', t('LESS could not create a directory in %dir', $param), array(), WATCHDOG_ERROR);
            return;
          }
          $output_file = $css_path . '/' . basename($input_file, '.less');

          // correct file names of files not following the .css.less naming convention
          if (drupal_substr($output_file, -4) != '.css') {
            $output_file .= '.css';
          }
          if (!file_exists($output_file)) {
            if (_less_inc()) {
              $less = new lessc();
              $contents = drupal_load_stylesheet($input_file, FALSE);
              $base = base_path() . dirname($input_file) . '/';
              _drupal_build_css_path(NULL, $base);

              // Prefix all paths within this CSS file, ignoring external and absolute paths.
              $data = preg_replace_callback('/url\\([\'"]?(?![a-z]+:|\\/+)([^\'")]+)[\'"]?\\)/i', '_drupal_build_css_path', $contents);
              try {
                $output_data = $less
                  ->parse($data);
                file_save_data($output_data, $output_file, FILE_EXISTS_REPLACE);
              } catch (Exception $e) {
                $message = 'LESS error: @message, %input_file';
                $message_vars = array(
                  '@message' => $e
                    ->getMessage(),
                  '%input_file' => $input_file,
                );
                watchdog('LESS', $message, $message_vars, WATCHDOG_ERROR);
                if (user_access(LESS_PERMISSION)) {
                  drupal_set_message(t($message, $message_vars), 'error');
                }
              }
            }
          }
          if (file_exists($output_file)) {
            array_splice($files_keys, $key, 1, $output_file);
          }
        }
      }
      if (!empty($files)) {
        $css[$media][$type] = array_combine($files_keys, $files);
      }
    }
  }
  if ($vars['show_messages']) {
    $vars['messages'] .= theme('status_messages');
  }
  $vars['css'] = $css;
  $vars['styles'] = drupal_get_css($vars['css']);
}

/**
 * Implements MODULE_preprocess_page().
 */
function less_preprocess_page(&$vars, $hook) {
  _less_build($vars, $hook);
}

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

/**
 * Recursively delete a path.
 *
 * Lifted from imagecache, with thanks to dopry/drewish.
 */
function _less_recursive_delete($path) {
  if (is_file($path) || is_link($path)) {
    unlink($path);
  }
  elseif (is_dir($path)) {
    $d = dir($path);
    while (($entry = $d
      ->read()) !== FALSE) {
      if ($entry == '.' || $entry == '..') {
        continue;
      }
      $entry_path = $path . '/' . $entry;
      _less_recursive_delete($entry_path);
    }
    $d
      ->close();
    rmdir($path);
  }
}

/**
 * Implementation of hook_flush_caches().
 *
 * Flushes compiled LESS files during cache flush, except during cron.
 *
 * @return An array of cache table names.
 */
function less_flush_caches() {
  $semaphore = variable_get('cron_semaphore', FALSE);
  if (!$semaphore) {
    _less_new_dir();
  }
  return array();
}

/**
 * Helper function to create a new less dir.
 */
function _less_new_dir() {
  $less_dir = uniqid('', TRUE);
  $less_path = file_directory_path() . '/less/' . $less_dir;
  @mkdir($less_path, 0775, TRUE);
  variable_set('less_dir', $less_dir);
  return $less_dir;
}

/**
 * Implements hook_cron().
 */
function less_cron() {
  $less_dir = variable_get('less_dir', '');
  $found_files = file_scan_directory(file_directory_path() . '/less', '^.+$', array(
    '.',
    '..',
    'CVS',
    $less_dir,
  ), 0, FALSE);
  foreach ($found_files as $found_file) {
    _less_recursive_delete($found_file->filename);
  }
}
function _less_inc() {
  if (!class_exists('lessc', FALSE)) {
    $include_locations = array(
      'lessphp/lessc.inc.php',
    );

    // load libraries module for during install
    module_load_include('module', 'libraries');
    if (function_exists('libraries_get_path')) {
      array_unshift($include_locations, libraries_get_path('lessphp') . '/lessc.inc.php');
    }
    foreach ($include_locations as $include_location) {
      if (file_exists($include_location)) {
        require_once $include_location;
        break;
      }
    }
  }
  return class_exists('lessc', FALSE);
}

Functions

Namesort descending Description
less_cron Implements hook_cron().
less_flush_caches Implementation of hook_flush_caches().
less_menu Implements hook_menu().
less_perm Implements hook_permission().
less_preprocess_maintenance_page Implements MODULE_preprocess_maintenance_page().
less_preprocess_page Implements MODULE_preprocess_page().
_less_build Builds the less cache
_less_inc
_less_new_dir Helper function to create a new less dir.
_less_recursive_delete Recursively delete a path.

Constants

Namesort descending Description
LESS_PERMISSION @file Handles compiling of .less files.