security update core+modules

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-26 18:38:56 +02:00
parent 2f45ea820a
commit 7c96373038
1022 changed files with 30319 additions and 11259 deletions

3
sites/all/modules/less/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.project
.idea
/.settings/

View File

@@ -0,0 +1,136 @@
LESS CSS Preprocessor
=====================
This module allows for automatic compilation of LESS styles sheets.
Requirements
------------
LESS requires at least one of three possible engines available:
[oyejorge/less.php]: http://lessphp.gpeasy.com/
[less.js]: http://lesscss.org/usage/#command-line-usage
[leafo/lessphp]: http://leafo.net/lessphp/
[Command Line Requirements]: #command
- [oyejorge/less.php]
This is a pure PHP implementation, which is good for shared hosting, or if you don't feel comfortable
installing or configuring software on your server.
It lacks the ability to execute javascript embedded in .less files, so some LESS libraries might not work.
Requirements:
1. [Libraries](https://drupal.org/project/libraries)
2. [oyejorge/less.php] installed such that `Less.php` is located at `sites/all/libraries/less.php/Less.php`
- [less.js]
You can read about how to install here: [less.js]
Please read [Command Line Requirements] to ensure that *less.js* is installed properly.
- [leafo/lessphp] *Deprecated*
*leafo/lessphp* library unpacked so that `lessc.inc.php` is located at `sites/all/libraries/lessphp/lessc.inc.php`.
This library is no longer recommended as it lacks support for a majority of new features in the latest canonical LESS.
Once installed, you must select your engine on 'admin/config/development/less'.
Less engines written in PHP do not support embedded JavaScript evaluation.
Optional
--------
### Autoprefixer
[postcss/autoprefixer]: https://github.com/postcss/autoprefixer
[postcss/autoprefixer]
> Write your CSS rules without vendor prefixes (in fact, forget about them entirely)
>
> Autoprefixer will use the data based on current browser popularity and property support to apply prefixes for you.
Please read [Command Line Requirements] to ensure that *autoprefixer* is installed properly.
<a name="command"></a>
Command Line Requirements
-------------------------
Both [less.js] and [postcss/autoprefixer] require that PHP can call these programs directly from the command line.
It is up to the installer (you) to ensure that the `lessc` and/or `autoprefixer` are able to be called by PHP without a full path to the binary.
Thus is it required that you configure your PATH for the user that your PHP installation runs under to ensure that these programs can be run by name without the full path.
LESS Development:
Syntax: http://lesscss.org/features/
File placement:
If your source file was `sites/all/modules/test/test.css.less`
Then your compiled file will be `sites/[yoursite]/files/less/[random.string]/sites/all/modules/test/test.css`
Usage
-----
The following two examples provide equivalent functionality.
[drupal_add_css()]:
<?php
$module_path = drupal_get_path('module', 'less_demo');
drupal_add_css($module_path . '/styles/less_demo.css.less');
?>
[drupal_add_css()]: https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_add_css/7
[.info file]:
stylesheets[all][] = styles/less_demo.css.less
[.info file]: https://www.drupal.org/node/542202
For automatic variable and function association with non globally added
stylesheets, you can associate a stylesheet using this notation in .info files:
less[sheets][] = relative/path/to/stylesheet.css.less
Compatibility
-------------
Should work with most themes and caching mechanisms.
### CSS Aggregation
Fully compatible with "Optimize CSS files" setting on "Admin->Site configuration->Performance" (admin/settings/performance).
### RTL Support
RTL support will work as long as your file names end with ".css.less".
Assuming your file is named "somename.css.less", Drupal automatically looks for a file name "somename-rtl.css.less"
Variables
---------
Variable defaults can be defined in .info files for modules or themes. Any variables defined will be automatically available inside style sheets associated with the module or theme.
.info file:
less[vars][@varname] = #bada55
Look in less.api.php for LESS Variable hooks.

View File

@@ -1,16 +0,0 @@
LESS CSS Preprocessor
Files in lessphp/ are from the library at http://leafo.net/lessphp/
The LICENSE file in lessphp/ applies only to files within lessphp/
DIRECTIONS:
Can be invoked by either
drupal_add_css('filename.less')
or
stylesheets[all] = filename.less

View File

@@ -0,0 +1,156 @@
<?php
/**
* @file
* Contains 'LessAutoprefixer' class; an abstraction layer for command line Autoprefixer.
*/
/**
* 'Autoprefixer' class.
*/
class LessAutoprefixer {
// Base command is hardcoded here to reduce security vulnerability.
const BASE_COMMAND = 'autoprefixer';
protected $input_file = NULL;
protected $source_maps_enabled = FALSE;
/**
* Constructor function for 'LessAutoprefixer'.
*
* @param string $input_file
* Path for .less file relative to getcwd().
*/
protected function __construct($input_file) {
$this->input_file = $input_file;
}
/**
* @param string $input_file
*
* @return LessAutoprefixer
*/
public static function create($input_file) {
return new self($input_file);
}
/**
* Returns the version string from command line Autoprefixer.
*
* @return string|null
* Version string from Autoprefixer, or null if no version found.
*/
public static function version() {
$version = NULL;
try {
$version_response = self::create(NULL)->proc_open(array('--version'));
$version = preg_replace('/.*?([\d\.]+).*/', '$1', $version_response);
}
catch (Exception $e) {
}
return $version;
}
/**
* Enable source maps for current file, and configure source map paths.
*
* @param bool $enabled
* Set the source maps flag.
*/
public function source_maps($enabled) {
$this->source_maps_enabled = $enabled;
}
/**
* Provides list to command line arguments for execution.
*
* @return array
* Array of command line arguments.
*/
protected function command_arguments() {
$arguments = array();
// Set service map flags.
if ($this->source_maps_enabled) {
$arguments[] = '--map';
$arguments[] = '--inline-map';
}
// Input file should be last argument.
$arguments[] = $this->input_file;
return $arguments;
}
/**
* Executes auto-prefixing of LESS output file.
*
* @return string
* Compiled CSS.
*/
public function compile() {
return $this->proc_open($this->command_arguments());
}
protected function proc_open($command_arguments = array()) {
$output_data = NULL;
$command = implode(' ', array_merge(array(self::BASE_COMMAND), $command_arguments));
// Handles for data exchange.
$pipes = array(
0 => NULL, // STDIN
1 => NULL, // STDOUT
2 => NULL, // STDERR
);
// Sets permissions on $pipes.
$descriptors = array(
0 => array('pipe', 'r'), // STDIN
1 => array('pipe', 'w'), // STDOUT
2 => array('pipe', 'w'), // STDERR
);
try {
$process = proc_open($command, $descriptors, $pipes);
if (is_resource($process)) {
fclose($pipes[0]); // fclose() on STDIN executes $command, if program is expecting input from STDIN.
$output_data = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$error = stream_get_contents($pipes[2]);
fclose($pipes[2]);
if (!empty($error)) {
throw new Exception($error);
}
proc_close($process);
}
}
catch (Exception $e) {
throw $e;
}
return $output_data;
}
}

View File

@@ -0,0 +1,290 @@
<?php
/**
* @file
* Contains 'lessjs' class; an abstraction layer for command line less.js.
*/
/**
* 'lessjs' class.
*/
class Lessjs {
/**
* Base command is hardcoded here to reduce security vulnerability.
*
* @var string
*/
const BASE_COMMAND = 'lessc';
/**
* Path to .less input file.
*
* @var null|string
*/
protected $input_file;
/**
* @var string[] $include_paths
*
* @link http://lesscss.org/usage/#command-line-usage-include-paths
*/
protected $include_paths = array();
/**
* @var string[] $modify_variables
*
* @link http://lesscss.org/usage/#command-line-usage-modify-variable
*/
protected $modify_variables = array();
protected $source_maps_enabled = FALSE;
/**
* @var null|string $source_map_rootpath
*
* @link http://lesscss.org/usage/#command-line-usage-source-map-rootpath
*/
protected $source_map_rootpath = NULL;
/**
* @var null|string $source_map_basepath
*
* @link http://lesscss.org/usage/#command-line-usage-source-map-basepath
*/
protected $source_map_basepath = NULL;
/**
* Constructor function for 'lessjs'.
*
* @param string $input_file
* Path for .less file relative to getcwd().
*/
private function __construct($input_file) {
$this->input_file = $input_file;
}
public static function create($input_file = NULL) {
return new self($input_file);
}
/**
* Returns the version string from command line less.js.
*
* @return string|null
* Version string from less.js, or null if no version found.
*/
public static function version() {
$version = NULL;
try {
$version_response = self::create(NULL)->proc_open(array('--version'));
$version = preg_replace('/.*?([\d\.]+).*/', '$1', $version_response);
}
catch (Exception $e) {
}
return $version;
}
/**
* Add include path that will be set with '--include-path' argument.
*
* @link http://lesscss.org/usage/#command-line-usage-include-paths
*
* @param string $include_path
* Path relative to getcwd().
*/
public function include_path($include_path) {
$this->include_paths[] = $include_path;
}
/**
* Add LESS variable that will be set with the '--modify-var' argument.
*
* @param string $variable_name
* The variable name.
* @param string $variable_value
* The variable value.
*/
public function modify_var($variable_name, $variable_value) {
$this->modify_variables[$variable_name] = $variable_value;
}
/**
* Enable source maps for current file, and configure source map paths.
*
* @param bool $enabled
* Set the source maps flag.
* @param string $base_path
* Leading value to be stripped from each source map URL.
* @param string $root_path
* Value to be prepended to each source map URL.
*
* @link http://lesscss.org/usage/#command-line-usage-source-map-rootpath
* @link http://lesscss.org/usage/#command-line-usage-source-map-basepath
*/
public function source_maps($enabled, $base_path = NULL, $root_path = NULL) {
$this->source_maps_enabled = $enabled;
$this->source_map_basepath = $base_path;
$this->source_map_rootpath = $root_path;
}
/**
* Provides list to command line arguments for execution.
*
* @return string[]
* Array of command line arguments.
*/
private function command_arguments() {
$arguments = array();
// Add include paths.
if (count($this->include_paths) > 0) {
$arguments[] = '--include-path=' . implode(PATH_SEPARATOR, array_map('escapeshellarg', $this->include_paths));
// @link http://lesscss.org/usage/#command-line-usage-relative-urls
$arguments[] = '--relative-urls';
}
// Add any defined variables.
foreach ($this->modify_variables as $modify_variable_name => $modify_variable_value) {
/**
* @link http://lesscss.org/usage/#command-line-usage-modify-variable
*/
$arguments[] = '--modify-var=' . escapeshellarg($modify_variable_name . '=' . $modify_variable_value);
}
// Set source map flags.
if ($this->source_maps_enabled) {
if (isset($this->source_map_rootpath)) {
$arguments[] = '--source-map-rootpath=' . escapeshellarg($this->source_map_rootpath);
}
if (isset($this->source_map_basepath)) {
$arguments[] = '--source-map-basepath=' . escapeshellarg($this->source_map_basepath);
}
/**
* @link http://lesscss.org/usage/#command-line-usage-source-map-map-inline
*/
$arguments[] = '--source-map-map-inline';
}
// Input file should be last argument.
// @link http://lesscss.org/usage/#command-line-usage-command-line-usage
$arguments[] = $this->input_file;
return $arguments;
}
/**
* Returns list of files that input file depends on.
*
* @return string[]
* List of @import'ed files.
*/
public function depends() {
$output_key = 'depends';
$depends_arguments = array();
$depends_arguments[] = '--depends';
$depends_arguments[] = drupal_realpath(LESS_DIRECTORY) . DIRECTORY_SEPARATOR . $output_key;
$depends_files_spaced = $this->proc_open(array_merge($this->command_arguments(), $depends_arguments));
// {$output_key}: /path/to/file/1 /path/to/file/2
$depends_files_spaced = str_replace($output_key . ':', '', $depends_files_spaced);
return explode(' ', trim($depends_files_spaced));
}
/**
* Executes compilation of LESS input.
*
* @return string
* Compiled CSS.
*/
public function compile() {
return $this->proc_open($this->command_arguments());
}
/**
* Execute compilation command through proc_open().
*
* @param string[] $command_arguments
*
* @return null|string
* @throws Exception
*
* @see proc_open()
*/
private function proc_open(array $command_arguments = array()) {
$output_data = NULL;
$command = implode(' ', array_merge(array(self::BASE_COMMAND), $command_arguments));
// Handles for data exchange.
$pipes = array(
0 => NULL, // STDIN
1 => NULL, // STDOUT
2 => NULL, // STDERR
);
// Sets permissions on $pipes.
$descriptors = array(
0 => array('pipe', 'r'), // STDIN
1 => array('pipe', 'w'), // STDOUT
2 => array('pipe', 'w'), // STDERR
);
try {
$process = proc_open($command, $descriptors, $pipes);
if (is_resource($process)) {
fclose($pipes[0]); // fclose() on STDIN executes $command, if program is expecting input from STDIN.
$output_data = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$error = stream_get_contents($pipes[2]);
fclose($pipes[2]);
if (!empty($error)) {
throw new Exception($error);
}
proc_close($process);
}
}
catch (Exception $e) {
throw $e;
}
return $output_data;
}
}

View File

@@ -1,10 +1,20 @@
{
"name": "drupal/less",
"authors": [
{
"name": "corey.aufang",
"email": "corey.aufang@gmail.com"
}
],
"type": "drupal-module",
"description": "Automatically process any LESS files that are added using drupal_add_css or added through your theme's .info file.",
"description": "Automatically process any LESS files that are added using drupal_add_css or added through your theme/module's .info file.",
"support": {
"issues": "https://drupal.org/project/issues/less",
"source": "https://drupal.org/project/less"
},
"license": "GPL-2.0",
"require": {
"leafo/lessphp": "0.3.*"
"oyejorge/less.php": "1.7.0.*"
},
"minimum-stability": "stable"
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* Class \LessEngine
*/
abstract class LessEngine implements LessEngineInterface {
/**
* Path to the input .less file.
*
* @var string
*/
protected $input_file_path;
/**
* This will get populated with a list of files that $input_file_path depended
* on through @import statements.
*
* @var string[]
*/
protected $dependencies = array();
/**
* This contains any variables that are to be modified into the output.
*
* Key => value pairs, where the key is the LESS variable name.
*
* @var string[]
*/
protected $variables = array();
/**
* List of directories that are to be used for @import lookups.
*
* @var string[]
*/
protected $import_directories = array();
/**
* Flag if source maps are enabled.
*
* @var bool
*/
protected $source_maps_enabled = FALSE;
/**
* @var string|NULL
*/
protected $source_maps_base_path = NULL;
/**
* @var string|NULL
*/
protected $source_maps_root_path = NULL;
/**
* Basic constructor.
*
* Sets input_file_path property.
*
* @param string $input_file_path
*/
public function __construct($input_file_path) {
$this->input_file_path = $input_file_path;
}
/**
* {@inheritdoc}
*/
public function setImportDirectories(array $directories) {
$this->import_directories = $directories;
}
/**
* {@inheritdoc}
*/
public function setSourceMaps($enabled = FALSE, $base_path = NULL, $root_path = NULL) {
$this->source_maps_enabled = $enabled;
$this->source_maps_base_path = $base_path;
$this->source_maps_root_path = $root_path;
}
/**
* {@inheritdoc}
*/
public function modifyVariables(array $variables) {
$this->variables = $variables;
}
/**
* {@inheritdoc}
*/
public function getDependencies() {
return $this->dependencies;
}
/**
* {@inheritdoc}
*/
abstract public function compile();
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* Class \LessEngineLess_js
*/
class LessEngineLess_js extends LessEngine {
/**
* @var \Lessjs
*/
private $less_js_parser;
/**
* Instantiates new instances of \Lessjs.
*
* @param string $input_file_path
*
* @see \Lessjs
*/
public function __construct($input_file_path) {
parent::__construct($input_file_path);
$this->less_js_parser = Lessjs::create($this->input_file_path);
}
/**
* We override here because getting dependencies from less.js requires another
* full parse. This way we only do that if dependencies are requested.
*
* @return string[]
*
* @see \Lessjs::depends()
*/
public function getDependencies() {
$this->dependencies = $this->less_js_parser->depends();
return parent::getDependencies();
}
/**
* {@inheritdoc}
* This compiles using engine specific function calls.
*/
public function compile() {
$compiled_styles = NULL;
try {
$this->less_js_parser->source_maps($this->source_maps_enabled, $this->source_maps_base_path, $this->source_maps_root_path);
foreach ($this->import_directories as $directory) {
$this->less_js_parser->include_path($directory);
}
foreach ($this->variables as $var_name => $var_value) {
$this->less_js_parser->modify_var(trim($var_name, '@'), trim($var_value, ';'));
}
$compiled_styles = $this->less_js_parser->compile();
}
catch (Exception $e) {
throw $e;
}
return $compiled_styles;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* Class \LessEngineLess_php
*/
class LessEngineLess_php extends LessEngine {
/**
* @var \Less_Parser
*/
private $less_php_parser;
/**
* Instantiates new instances of \Less_Parser.
*
* @param string $input_file_path
*
* @see \Less_Parser
*/
public function __construct($input_file_path) {
parent::__construct($input_file_path);
$this->less_php_parser = new Less_Parser();
}
/**
* {@inheritdoc}
* This compiles using engine specific function calls.
*/
public function compile() {
$compiled_styles = NULL;
try {
if ($this->source_maps_enabled) {
$this->less_php_parser->SetOption('sourceMap', $this->source_maps_enabled);
$this->less_php_parser->SetOption('sourceMapBasepath', $this->source_maps_base_path);
$this->less_php_parser->SetOption('sourceMapRootpath', $this->source_maps_root_path);
}
// Less.js does not allow path aliasing. Set aliases to blank for consistency.
$this->less_php_parser->SetImportDirs(array_fill_keys($this->import_directories, ''));
$this->less_php_parser->parseFile($this->input_file_path);
$this->less_php_parser->ModifyVars($this->variables);
$compiled_styles = $this->less_php_parser->getCss();
$this->dependencies = $this->less_php_parser->AllParsedFiles();
}
catch (Exception $e) {
throw $e;
}
return $compiled_styles;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Class \LessEngineLessphp
*/
class LessEngineLessphp extends LessEngine {
/**
* @var \lessc
*/
private $less_php_parser;
/**
* Instantiates new instances of \lessc.
*
* @param string $input_file_path
*
* @see \lessc
*/
public function __construct($input_file_path) {
parent::__construct($input_file_path);
$this->less_php_parser = new lessc();
}
/**
* {@inheritdoc}
* This compiles using engine specific function calls.
*/
public function compile() {
$compiled_styles = NULL;
try {
foreach ($this->import_directories as $directory) {
$this->less_php_parser->addImportDir($directory);
}
$cache = $this->less_php_parser->cachedCompile($this->input_file_path);
$this->dependencies = array_keys($cache['files']);
$compiled_styles = $cache['compiled'];
}
catch (Exception $e) {
throw $e;
}
return $compiled_styles;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Interface \LessEngineInterface
*/
interface LessEngineInterface {
/**
* Set list of lookup directories for @import statements.
*
* @param string[] $directories
* Flat array of paths relative to DRUPAL_ROOT.
*/
public function setImportDirectories(array $directories);
/**
* Enable
*
* @param bool $enabled
* Set the source maps flag.
* @param string $base_path
* Leading value to be stripped from each source map URL.
* @link http://lesscss.org/usage/#command-line-usage-source-map-basepath
* @param string $root_path
* Value to be prepended to each source map URL.
* @link http://lesscss.org/usage/#command-line-usage-source-map-rootpath
*/
public function setSourceMaps($enabled, $base_path, $root_path);
/**
* Set/override variables.
*
* Variables defined here work in the "modify" method. They are applied after
* parsing but before compilation.
*
* @param string[] $variables
*
* @link http://lesscss.org/usage/#command-line-usage-modify-variable
*/
public function modifyVariables(array $variables);
/**
* Returns list of dependencies.
*
* Returns a list of files that included through @import statements. This list
* is used to determine if parent file needs to be recompiled based on changes
* in dependencies.
*
* @return string[]
* List of paths relative to DRUPAL_ROOT
*/
public function getDependencies();
/**
* This returns the compiled output from the LESS engine.
*
* All output, including source maps, should be contained within the returned
* string.
*
* @return string
* Plain CSS.
*
* @throws Exception
* Rethrows exception from implementation library.
*/
public function compile();
}

View File

@@ -0,0 +1,137 @@
<?php
/**
* @file
* Contains the administration pages for LESS.
*/
/**
* Form for LESS module settings.
*/
function less_settings_form($form, &$form_state) {
$form['less_flush'] = array(
'#type' => 'fieldset',
'#collapsible' => FALSE,
'#value' => t('Click this button to flag all LESS files for regeneration.'),
);
$form['less_flush']['flush'] = array(
'#type' => 'submit',
'#submit' => array('_flush_less'),
'#value' => t('Flush LESS files'),
);
$registered_engines = _less_get_engines();
$less_engines = array();
foreach ($registered_engines as $library => $engine) {
$less_engines[] = libraries_detect($library);
}
$less_engine_element = array(
'#type' => 'radios',
'#title' => t('LESS engine'),
'#options' => array(),
'#required' => TRUE,
'#default_value' => variable_get('less_engine', 'lessphp'),
);
foreach ($less_engines as $less_engine) {
$less_engine_element['#options'][$less_engine['machine name']] = $less_engine['name'];
$less_engine_element[$less_engine['machine name']] = array(
'#type' => 'radio',
'#title' => t('@engine_name - <a href="@vendor_url">@vendor_url</a>', array('@engine_name' => $less_engine['name'], '@vendor_url' => $less_engine['vendor url'])),
'#return_value' => $less_engine['machine name'],
'#description' => t('Missing - Click vendor link above to read installation instructions.'),
'#disabled' => empty($less_engine['installed']),
);
if ($less_engine['installed']) {
$less_engine_element[$less_engine['machine name']]['#description'] = t('v%version Installed', array('%version' => $less_engine['version']));
}
}
$form['less_engine'] = $less_engine_element;
$lessautoprefixer_library = libraries_detect('lessautoprefixer');
$form[LESS_AUTOPREFIXER] = array(
'#type' => 'checkbox',
'#title' => t('Use @name - <a href="@vendor_url">@vendor_url</a>', array('@name' => $lessautoprefixer_library['name'], '@vendor_url' => $lessautoprefixer_library['vendor url'])),
'#description' => t('Enable automatic prefixing of vendor CSS extensions.'),
'#default_value' => variable_get(LESS_AUTOPREFIXER, FALSE),
'#disabled' => empty($lessautoprefixer_library['installed']),
);
if ($lessautoprefixer_library['installed']) {
$form[LESS_AUTOPREFIXER]['#description'] .= '<br />'. t('v%version Installed', array('%version' => $lessautoprefixer_library['version']));
}
$form['developer_options'] = array(
'#type' => 'fieldset',
'#title' => t('Developer Options'),
'#collapsible' => TRUE,
'#collapsed' => !(variable_get(LESS_DEVEL, FALSE)),
);
$form['developer_options'][LESS_DEVEL] = array(
'#type' => 'checkbox',
'#title' => t('Developer Mode'),
'#description' => t('Enable developer mode to ensure LESS files are regenerated every page load.'),
'#default_value' => variable_get(LESS_DEVEL, FALSE),
);
$form['developer_options'][LESS_SOURCE_MAPS] = array(
'#type' => 'checkbox',
'#title' => t('Source Maps'),
'#description' => t('Enable source maps output while "Developer Mode" is enabled.'),
'#default_value' => variable_get(LESS_SOURCE_MAPS, FALSE),
'#states' => array(
'enabled' => array(
':input[name="' . LESS_DEVEL . '"]' => array('checked' => TRUE),
),
),
);
$form['developer_options'][LESS_WATCH] = array(
'#type' => 'checkbox',
'#title' => t('Watch Mode'),
'#description' => t('Enable watch mode while developer mode is active to automatically reload styles when changes are detected, including changes to @import-ed files. Does not cause a page reload.'),
'#default_value' => variable_get(LESS_WATCH, FALSE),
'#states' => array(
'enabled' => array(
':input[name="' . LESS_DEVEL . '"]' => array('checked' => TRUE),
),
),
);
$form['#submit'] = array('less_settings_form_submit');
return system_settings_form($form);
}
/**
* Form submission function.
*
* Trigger clear of LESS module cache data.
*/
function less_settings_form_submit($form, &$form_state) {
cache_clear_all('less:', 'cache', TRUE);
}
/**
* Submit handler for cache clear button.
*/
function _flush_less($form, &$form_state) {
less_flush_caches();
drupal_set_message(t('LESS files cache cleared.'), 'status');
}

View File

@@ -0,0 +1,209 @@
<?php
/**
* @file
* Contains Libraries API integration.
*/
/**
* Implements hook_libraries_info().
*/
function less_libraries_info() {
$libraries = array();
/**
* Legacy leafo/lessphp library.
*/
$libraries['lessphp'] = array(
'name' => 'lessphp (Not recommended)',
'vendor url' => 'http://leafo.net/lessphp/',
'download url' => 'http://leafo.net/lessphp/',
'version arguments' => array(
'file' => 'lessc.inc.php',
'pattern' => '/VERSION\s*=\s*["\']v?([\d\.]+)/',
'lines' => 50,
),
'files' => array(
'php' => array(
'lessc.inc.php',
),
),
);
_less_lessphp_locate($libraries['lessphp']);
/**
* Newer oyejorge/less.php library. Closer to canonical spec from lesscss.org.
*/
$libraries['less.php'] = array(
'name' => 'less.php',
'vendor url' => 'http://lessphp.gpeasy.com/',
'download url' => 'http://lessphp.gpeasy.com/#integration-with-other-projects',
'version arguments' => array(
'file' => 'Version.php',
'pattern' => '/version\s*=\s*["\']([\d\.]+)/',
'lines' => 20,
),
'files' => array(
'php' => array(
'Less.php',
),
),
'versions' => array(
'1.7.0' => array(),
),
);
_less_less_php_locate($libraries['less.php']);
/**
* Canonical version of LESS language.
*/
$libraries['less.js'] = array(
'name' => 'less.js',
'vendor url' => 'http://lesscss.org/',
'download url' => 'http://lesscss.org/usage/#using-less-environments',
'library path' => drupal_get_path('module', 'less') . '/classes',
'version callback' => array('Lessjs', 'version'),
/**
* Integer indexed 'version arguments' array causes 'version callback' to be
* run through call_user_func_array().
*
* @see call_user_func_array()
*/
'version arguments' => array(
0 => 'not used',
),
'files' => array(
'php' => array(
'class.lessjs.inc',
),
),
'versions' => array(
'1.5.0' => array(),
),
);
/**
* Autoprefixer
*/
$libraries['lessautoprefixer'] = array(
'name' => 'Autoprefixer',
'vendor url' => 'https://github.com/ai/autoprefixer',
'download url' => 'https://github.com/ai/autoprefixer/releases',
'library path' => drupal_get_path('module', 'less') . '/classes',
'version callback' => array('LessAutoprefixer', 'version'),
/**
* Integer indexed 'version arguments' array causes 'version callback' to be
* run through call_user_func_array().
*
* @see call_user_func_array()
*/
'version arguments' => array(
0 => 'not used',
),
'files' => array(
'php' => array(
'class.lessautoprefixer.inc',
),
),
'versions' => array(
'1.1' => array(),
),
);
return $libraries;
}
/**
* Locates oyejorge/less.php in the many possible places it could be.
*
* @param array $library
* Libraries definition array.
*/
function _less_less_php_locate(&$library) {
$locations = array();
// Primary libraries location
$locations[] = libraries_get_path('less.php');
// lessphp drop-in replacement location
$locations[] = libraries_get_path('lessphp');
// Composer location
$locations[] = drupal_get_path('module', 'less') . '/vendor/oyejorge/less.php';
$version_files = array(
'lib/Less/Version.php' => 'lessc.inc.php', // Source code
'Version.php' => 'Less.php', // Compiled
);
_less_libraries_determine_location($library, $locations, $version_files);
}
/**
* Locates leafo/lessphp in the many possible places it could be.
*
* @param array $library
* Libraries definition array.
*/
function _less_lessphp_locate(&$library) {
$locations = array();
// Primary libraries location
$locations[] = libraries_get_path('lessphp');
// Legacy bundled location
$locations[] = drupal_get_path('module', 'less') . '/lessphp';
// Composer location
$locations[] = drupal_get_path('module', 'less') . '/vendor/leafo/lessphp';
/*
* oyejorge/less.php does not have the actual version number in lessc.inc.php,
* so we don't have to worry about mistaken identity.
*/
$version_files = array(
'lessc.inc.php' => 'lessc.inc.php',
);
_less_libraries_determine_location($library, $locations, $version_files);
}
/**
* Helper function that checks locations for LESS libraries.
*
* @param array &$library
* Library in question. Paths to found libraries will be added here.
* @param array $locations
* Array of paths of potential library installs relative to DRUPAL_ROOT.
* @param array $version_files
* Array of key => value pairs, where key is location library version number,
* and value is the location of that file that to be included when this
* library is loaded with libraries_load().
*/
function _less_libraries_determine_location(array &$library, array $locations, array $version_files) {
foreach (array_filter($locations) as $location) {
foreach ($version_files as $version_file => $class_file) {
if (file_exists($location . DIRECTORY_SEPARATOR . $version_file)) {
$library['library path'] = $location;
$library['files'] = array(
'php' => array(
$class_file,
),
);
$library['version arguments']['file'] = $version_file;
return; // File has been found, skip remaining $locations and $version_files
}
}
}
}

View File

@@ -0,0 +1,281 @@
<?php
/**
* @file
* Contains functions related to compiling .less files.
*/
/**
* Attach LESS settings to each file as appropriate.
*
* @param array[] $item
* @param string $key
*/
function _less_attach_settings(&$item, $key) {
$defaults = array(
'less' => less_get_settings(), // Bare defaults for LESS.
);
// These items must be reset for consistent operation.
$nullify = array(
'less' => array(
'output_file' => NULL,
'build_required' => NULL,
),
);
// Merge in any info from $item.
$item = array_replace_recursive($defaults, $item, $nullify);
$item['less']['input_file'] = $item['data'];
$less_settings = less_get_settings(_less_file_owner($item['less']['input_file']));
// array_replace_recursive() works on keys, flip to not use numeric keys.
$less_settings['paths'] = array_flip($less_settings['paths']);
$item['less']['paths'] = array_flip($item['less']['paths']);
// Merge defaults with any per file settings.
$item['less'] = array_replace_recursive($less_settings, $item['less']);
// First array_flips before merge removed duplicates, so just flip back.
$item['less']['paths'] = array_flip($item['less']['paths']);
}
/**
* Determine output filename and add it to the settings array.
*
* @param array[] $item
* @param string $key
*/
function _less_output_path(&$item, $key) {
$input_file = $item['less']['input_file'];
$less_settings = $item['less'];
// array_multisort() the data so that the hash returns the same hash regardless order of data.
array_multisort($less_settings);
$output_path_array = array(
'!less_output_dir' => LESS_DIRECTORY,
// Strip '.css' extension of filenames following the RTL extension pattern.
'!input_file_basename' => basename(basename($input_file, '.less'), '.css'),
// drupal_json_encode() is used because serialize() throws an error with lambda functions.
'!settings_hash' => drupal_hash_base64(drupal_json_encode($less_settings)),
);
$output_path = format_string('!less_output_dir/!input_file_basename.!settings_hash.css', $output_path_array);
$item['less']['output_file'] = $output_path;
}
/**
* Check if the file needs to be rebuilt based on changes to @import'ed files.
*
* @param array[] $item
* @param string $key
*/
function _less_check_build(&$item, $key) {
$input_file = $item['less']['input_file'];
$build_required = FALSE;
// Set $rebuild if this file or its children have been modified.
if ($less_file_cache = cache_get('less:devel:' . drupal_hash_base64($input_file))) {
// Iterate over each file and check if there are any changes.
foreach ($less_file_cache->data as $filepath => $filemtime) {
// Only rebuild if there has been a change to a file.
if (is_file($filepath) && filemtime($filepath) > $filemtime) {
$build_required = TRUE;
break;
}
}
}
else {
// No cache data, force a rebuild for later comparison.
$build_required = TRUE;
}
$item['less']['build_required'] = $build_required;
}
/**
* Process a .less file and save the compiled styles.
*
* @param array[] $item
* @param string $key
*
* @see \LessEngineInterface
*/
function _less_process_file(&$item, $key) {
$less_settings = $item['less'];
// $output_file doesn't exist or is flagged for build.
if (!is_file($item['less']['output_file']) || !empty($item['less']['build_required'])) {
$output_data = NULL;
try {
$engine = less_get_engine($less_settings['input_file']);
$engine->setImportDirectories($less_settings['paths']);
if ($less_settings[LESS_DEVEL]) {
$engine->setSourceMaps($less_settings[LESS_SOURCE_MAPS], DRUPAL_ROOT, base_path());
}
$engine->modifyVariables($less_settings['variables']);
$output_data = $engine->compile();
if ($less_settings[LESS_DEVEL]) {
_less_cache_dependencies($less_settings['input_file'], $engine->getDependencies());
}
}
catch (Exception $e) {
$message_vars = array(
'@message' => $e->getMessage(),
'%input_file' => $item['less']['input_file'],
);
watchdog('LESS', 'LESS error: @message, %input_file', $message_vars, WATCHDOG_ERROR);
if (user_access(LESS_PERMISSION)) {
drupal_set_message(t('LESS error: @message, %input_file', $message_vars), 'error');
}
}
if (isset($output_data)) {
// Fix paths for images as .css is in different location.
$output_data = _less_rewrite_paths($item['less']['input_file'], $output_data);
// Ensure the destination directory exists.
if (_less_ensure_directory(dirname($item['less']['output_file']))) {
file_unmanaged_save_data($output_data, $item['less']['output_file'], FILE_EXISTS_REPLACE);
}
}
if (is_file($item['less']['output_file']) && $item['less'][LESS_AUTOPREFIXER]) {
if (($lessautoprefixer_library = libraries_load('lessautoprefixer')) && $lessautoprefixer_library['installed']) {
try {
LessAutoprefixer::create(drupal_realpath($item['less']['output_file']))->compile();
}
catch (Exception $e) {
$message_vars = array(
'@message' => $e->getMessage(),
'%input_file' => $item['less']['output_file'],
);
watchdog('LESS', 'Autoprefixer error: @message, %input_file', $message_vars, WATCHDOG_ERROR);
if (user_access(LESS_PERMISSION)) {
drupal_set_message(t('Autoprefixer error: @message, %input_file', $message_vars), 'error');
}
}
}
}
}
if (is_file($item['less']['output_file'])) {
// Set render path of the stylesheet to the compiled output.
$item['data'] = $item['less']['output_file'];
}
}
/**
* @param array[] $item
* @param string $key
*/
function _less_store_cache_info(&$item, $key) {
// Only match when output_file exists.
if ($item['data'] === $item['less']['output_file']) {
$less_watch_cache = $item;
$less_watch_cache['data'] = $item['less']['input_file'];
cache_set('less:watch:' . drupal_hash_base64(file_create_url($item['less']['output_file'])), $less_watch_cache);
// 'preprocess' being FALSE generates a discreet <link /> rather than an @import.
$item['preprocess'] = FALSE;
}
}
/**
* Normalize keeping track of changed files.
*
* @param string $input_file
* Path of source file.
* @param string[] $dependencies
* Array of files that are @import'ed in $input_file, recursively.
*/
function _less_cache_dependencies($input_file, $dependencies = array()) {
// Add $input_file to $dependencies as it is not in return from some engines.
$dependencies = array_merge(array($input_file), (array) $dependencies);
$watched_files = array();
foreach ($dependencies as $dependency) {
// Full path on file should enforce uniqueness in associative array.
$watched_files[drupal_realpath($dependency)] = filemtime($dependency);
}
cache_set('less:devel:' . drupal_hash_base64($input_file), $watched_files);
}
/**
* Copied functionality from drupal_build_css_cache() for our own purposes.
*
* This function processes $contents and rewrites relative paths to be absolute
* from web root. This is mainly used to ensure that compiled .less files still
* reference images at their original paths.
*
* @param string $input_filepath
* @param string $contents
*
* @return string
* Processed styles with replaced paths.
*
* @see drupal_build_css_cache()
*/
function _less_rewrite_paths($input_filepath, $contents) {
$output = '';
// Build the base URL of this CSS file: start with the full URL.
$css_base_url = file_create_url($input_filepath);
// Move to the parent.
$css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/'));
// Simplify to a relative URL if the stylesheet URL starts with the
// base URL of the website.
if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) {
$css_base_url = substr($css_base_url, strlen($GLOBALS['base_root']));
}
_drupal_build_css_path(NULL, $css_base_url . '/');
// Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
$output .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents);
return $output;
}

View File

@@ -0,0 +1,116 @@
<?php
/**
* @file
* Contains alterations to theme settings forms.
*/
/**
* Implements hook_form_alter().
*/
function less_form_alter(&$form, &$form_state, $form_id) {
if ($form_id === 'system_theme_settings') {
preg_match('/^theme_(?P<current_theme_key>\w+)_settings$/', $form['var']['#value'], $matches);
// only show settings on forms for themes, not "Global settings"
if (!empty($matches['current_theme_key'])) {
$current_theme_key = $matches['current_theme_key'];
$saved_settings = theme_get_setting('less', $current_theme_key);
$less_settings_form = array();
$theme_items = system_list('theme');
if (isset($theme_items[$current_theme_key]) && !empty($theme_items[$current_theme_key]->info['less']['vars'])) {
$less_settings_form[$current_theme_key] = _less_setting_form_element($current_theme_key, $theme_items[$current_theme_key]->info['less']['vars'], $saved_settings[$current_theme_key]);
}
// showing settings form for modules
$system_items = system_list('module_enabled');
$less_module_settings = array();
foreach ($system_items as $system_item_name => $system_item) {
if (!empty($system_item->info['less']['vars'])) {
$item_saved_settings = isset($saved_settings[$system_item_name]) ? $saved_settings[$system_item_name] : array();
$less_module_settings[$system_item_name] = _less_setting_form_element($system_item_name, $system_item->info['less']['vars'], $item_saved_settings, $system_item->info['name']);
$less_module_settings[$system_item_name]['#group'] = 'less][modules_list';
}
}
if (!empty($less_module_settings)) {
$less_settings_form['modules'] = array(
'#type' => 'fieldset',
'#title' => t('Modules'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#parents' => array('less'),
// make sure all settings are stored under the system name immediately inside the themes less settings array
);
$less_settings_form['modules']['modules_list'] = array(
'#type' => 'vertical_tabs',
);
$less_settings_form['modules'] += $less_module_settings;
}
if (!empty($less_settings_form)) {
$form['less'] = array(
'#type' => 'vertical_tabs',
'#prefix' => t('<h3>LESS settings</h3>'),
'#tree' => TRUE,
'#description' => t('These settings are per theme. Delete a value to use the default.'),
'#attached' => array(
'css' => array(
drupal_get_path('module', 'less') . '/styles/less.theme.css',
),
),
);
$form['less'] += $less_settings_form;
}
}
}
}
function _less_setting_form_element($system_name, $less_settings, $saved_settings, $title = NULL) {
$form = array(
'#type' => 'fieldset',
'#title' => isset($title) ? $title : $system_name,
'#attributes' => array(
'class' => array(
'less-theme-settings',
),
),
);
if (module_exists($system_name)) {
$less_settings = array_replace($less_settings, (array) module_invoke($system_name, 'less_variables'));
}
foreach ($less_settings as $var_name => $var_value) {
$form[$var_name] = array(
'#type' => 'textfield',
'#title' => $var_name,
'#default_value' => !empty($saved_settings[$var_name]) ? $saved_settings[$var_name] : NULL,
'#description' => t('Default value: %variable_value', array('%variable_value' => $var_value)),
'#attributes' => array('placeholder' => $var_value),
);
}
return $form;
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* @file
* Contains page callback for LESS watch functionality.
*/
/**
* Page callback for 'ajax/less/watch'.
*
* Handles AJAX requests to check for changes to files while in developer mode.
*/
function _less_watch() {
global $theme;
drupal_page_is_cacheable(FALSE);
$changed_files = array();
if (variable_get(LESS_WATCH, FALSE)) {
$files = (isset($_POST['less_files']) && is_array($_POST['less_files'])) ? $_POST['less_files'] : array();
foreach ($files as $file) {
$file_url_parts = drupal_parse_url($file);
if ($cache = cache_get('less:watch:' . drupal_hash_base64($file_url_parts['path']))) {
$cached_data = $cache->data;
$input_file = $cached_data['less']['input_file'];
$output_file = $cached_data['less']['output_file'];
$current_mtime = filemtime($output_file);
$theme = $cached_data['less']['theme'];
$styles = array(
'#items' => array(
$input_file => $cached_data,
),
);
$styles = _less_pre_render($styles);
if (filemtime($styles['#items'][$input_file]['data']) > $current_mtime) {
$changed_files[] = array(
'old_file' => $file_url_parts['path'],
'new_file' => file_create_url($styles['#items'][$input_file]['data']),
);
}
}
}
}
return $changed_files;
}

View File

@@ -0,0 +1,94 @@
<?php
/**
* @file
* Contains functions that handle WYSIWYG module integration.
*/
/**
* Implements hook_wysiwyg_editor_settings_alter().
*
* Check the CSS WYSIWYG setting for LESS files and replace with
* generated CSS files where necessary.
*/
function less_wysiwyg_editor_settings_alter(&$settings, $context) {
$wysiwyg = $context['editor']['name'];
// Each editor has a different $settings array key for CSS files.
$editors = array(
'tinymce' => 'content_css',
'fckeditor' => 'EditorAreaCSS',
'ckeditor' => 'contentsCss',
);
if (!empty($editors[$wysiwyg]) && !empty($settings[$editors[$wysiwyg]])) {
$stylesheets = $settings[$editors[$wysiwyg]];
// Keep track if comma separated paths, or array of paths.
$is_array = is_array($stylesheets);
if ($is_array === FALSE) {
// $stylesheets is a list of comma separated file paths.
$stylesheets = explode(',', $stylesheets);
}
// Prepare an array that can be handled by normal LESS module processing.
$styles = array(
'#items' => array(),
);
foreach ($stylesheets as $stylesheet) {
// Might contain ?query portion, separate parts.
$parts = drupal_parse_url($stylesheet);
// Paths are expected to be relative to DRUPAL_ROOT, trim leading '/'.
$path = trim($parts['path'], '/');
$styles['#items'][$path] = array(
'data' => $path,
);
}
$styles = _less_pre_render($styles);
$processed_stylesheets = array();
foreach ($styles['#items'] as $file) {
$processed_stylesheets[] = file_create_url($file['data']);
}
// Recombine file paths into comma separated list.
if ($is_array === FALSE) {
$processed_stylesheets = implode(',', $processed_stylesheets);
}
$settings[$editors[$wysiwyg]] = $processed_stylesheets;
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* form_id = 'wysiwyg_profile'
*/
function less_form_wysiwyg_profile_form_alter(&$form, $form_state, $form_id) {
$form['css']['css_path']['#description'] .= '<br />' . t('You may enter a path to a LESS file and it will be parsed automatically.');
}
/**
* Implements hook_ckeditor_settings_alter().
*/
function less_ckeditor_settings_alter(&$settings) {
$context = array(
'editor' => array(
'name' => 'ckeditor',
),
);
less_wysiwyg_editor_settings_alter($settings, $context);
}

View File

@@ -1,39 +0,0 @@
<?php
/**
* @file
* Contains the administration pages for LESS.
*
*/
function less_settings($form, &$form_state) {
$form['less_flush'] = array(
'#type' => 'fieldset',
'#collapsible' => FALSE,
'#value' => 'Click this button regenerate all LESS files once.',
);
$form['less_flush']['flush'] = array(
'#type' => 'submit',
'#submit' => array('_flush_less'),
'#value' => 'Flush LESS files',
);
$form['less_devel'] = array(
'#type' => 'checkbox',
'#title' => t('LESS developer mode'),
'#description' => t('Enable the developer mode to ensure LESS files are regenerated every page load, regardless of any change done to the LESS file (which may happen when using the @import notation, and changing only the imported file). Note that this setting does not override "Optimize CSS files" if set via <a href="@performance-url">Performance</a>.', array('@performance-url' => url('admin/config/development/performance'))),
'#default_value' => variable_get('less_devel', FALSE),
);
return system_settings_form($form);
}
function _flush_less($form, &$form_state) {
$less_path = file_default_scheme() . '://less';
file_unmanaged_delete_recursive($less_path);
drupal_set_message(t('LESS files flushed.'), 'status');
}

View File

@@ -0,0 +1,175 @@
<?php
/**
* @file
* Hooks provided by the Less module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Define LESS variables.
*
* Should return flat associative array, where key is variable name.
*
* Variables are lazy evaluated, so variables that depend on others do not have
* to appear in order.
*
* Variables returned by this function are cached, therefore values returned
* by this function should not change. If you need variables to change from page
* to page, use hook_less_variables_alter().
*
* @return array
*
* @see hook_less_variables_alter()
* @see hook_less_variables_SYSTEM_NAME_alter()
*/
function hook_less_variables() {
return array(
'@variable_name_1' => '#ccc',
'@variable_name_2' => 'darken(@variable_name_1, 30%)',
);
}
/**
* Alter LESS variables provided by other modules or themes.
*
* This is called before hook_less_variables_SYSTEM_NAME_alter().
*
* @param &string[] $less_variables
* Flat associative array of variables, where key is variable name.
* @param string $system_name
* A string of the system_name of the module or theme that this applies to.
*
* @see hook_less_variables()
* @see hook_less_variables_SYSTEM_NAME_alter()
*/
function hook_less_variables_alter(array &$less_variables, $system_name) {
if ($system_name === 'less_demo') {
$less_variables['@variable_name_1'] = '#ddd';
}
}
/**
* Alter LESS variables provided by other modules or themes.
*
* This is called after hook_less_variables_alter().
*
* @param &string[] $less_variables
* Flat associative array of variables, where key is variable name.
*
* @see hook_less_variables()
* @see hook_less_variables_alter()
*/
function hook_less_variables_SYSTEM_NAME_alter(array &$less_variables) {
$less_variables['@variable_name_2'] = 'lighten(@variable_name_1, 20%)';
}
/**
* Provide a list of lookup paths for @import statements in .less files.
*
* @return string[]
*/
function hook_less_paths() {
return array(
drupal_get_path('module', 'less_demo') . '/libs',
);
}
/**
* Alter LESS include paths.
*
* @param &string[] $less_paths
* @param string $system_name
*/
function hook_less_paths_alter(array &$less_paths, $system_name) {
if ($system_name === 'less_demo') {
$less_paths[] = drupal_get_path('module', 'less_demo') . '/other_path';
}
}
/**
* Alter LESS include paths for specific module/theme.
*
* @param &string[] $less_paths
*/
function hook_less_paths_SYSTEM_NAME_alter(array &$less_paths) {
}
/**
* @deprecated
*
* Define LESS functions.
*
* @return array
* An associative where keys are LESS functions and values are PHP function
* names or anonymous functions. Anonymous functions require PHP >= 5.3.
*
* @see hook_less_functions_alter()
* @see hook_less_functions_SYSTEM_NAME_alter()
*
* @link http://leafo.net/lessphp/docs/#custom_functions
*/
function hook_less_functions() {
return array(
'less_func_1' => 'php_func_1',
'less_func_2' => function ($arg) {
list($type, $delimiter, $value) = $arg;
return array($type, $delimiter, $value);
},
);
}
/**
* @deprecated
*
* Alter LESS functions defined by modules/themes.
*
* @param string[] $less_functions
* Flat associative array of functions, where key is LESS function name and
* value is PHP function name or Anonymous function:
* (http://php.net/manual/en/functions.anonymous.php)
* @param string $system_name
* A string of the system_name of the module or theme that this applies to.
*
* @see hook_less_functions()
* @see hook_less_functions_SYSTEM_NAME_alter()
*
* @link http://leafo.net/lessphp/docs/#custom_functions
*/
function hook_less_functions_alter(array &$less_functions, $system_name) {
}
/**
* @deprecated
*
* Alter LESS functions provided by a specific module/theme.
*
* @param string[] $less_functions
* Flat associative array of functions, where key is variable and value is
* function name or Anonymous function:
* (http://php.net/manual/en/functions.anonymous.php)
*
* @see hook_less_functions()
* @see hook_less_functions_alter()
*
* @link http://leafo.net/lessphp/docs/#custom_functions
*/
function hook_less_functions_SYSTEM_NAME_alter(array &$less_functions) {
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -1,10 +1,15 @@
<?php
function less_drush_cache_clear(&$types) {
$types['less'] = '_less_drush_cache_clear';
}
/**
* @file
* Contains Drush related functions.
*/
function _less_drush_cache_clear() {
_less_new_dir();
less_cron();
/**
* Implements hook_drush_cache_clear().
*
* This adds an option on drush 'cache-clear'.
*/
function less_drush_cache_clear(&$types) {
$types['less'] = 'less_flush_caches';
}

View File

@@ -2,13 +2,29 @@
name = "LESS CSS Preprocessor"
description = "Allows themes or modules to use LESS files."
php = 5.3
core = 7.x
configure = admin/config/development/less
; Information added by drupal.org packaging script on 2012-08-07
version = "7.x-2.6"
dependencies[] = libraries
files[] = engines/interface.LessEngine.inc
files[] = engines/abstract.LessEngine.inc
files[] = engines/engine.less_php.inc
files[] = engines/engine.lessphp.inc
files[] = engines/engine.less_js.inc
files[] = classes/class.lessjs.inc
files[] = classes/class.lessautoprefixer.inc
; Testing files
files[] = tests/less.test
; Information added by Drupal.org packaging script on 2015-01-30
version = "7.x-4.0"
core = "7.x"
project = "less"
datestamp = "1344374523"
datestamp = "1422653011"

View File

@@ -1,62 +1,71 @@
<?php
/**
* Implements HOOK_enable().
* @file
* Install, update, and uninstall functions for the less module.
*/
/**
* Implements hook_uninstall().
*/
function less_enable() {
drupal_theme_rebuild();
}
function less_uninstall() {
variable_del('less_devel');
variable_del('less_engine');
variable_del(LESS_DEVEL);
variable_del(LESS_WATCH);
variable_del(LESS_SOURCE_MAPS);
variable_del('less_dir');
cache_clear_all('less:', 'cache', TRUE);
}
/**
* Implementation of hook_requirements().
*
* @param $phase The phase in which hook_requirements is run: install or runtime.
* Implements hook_requirements().
*/
function less_requirements($phase) {
$requirements = array();
$lessc_exists = FALSE;
module_load_include('module', 'less');
if (function_exists('_less_inc')) {
$lessc_exists = _less_inc();
}
$t = get_t();
switch ($phase) {
case 'runtime':
if ($lessc_exists) {
$less_engine_loaded = _less_inc();
if (!empty($less_engine_loaded)) {
$loaded_engine = libraries_detect($less_engine_loaded);
$requirements['less_version'] = array(
'title' => t('LESS'),
'value' => isset(lessc::$VERSION) ? preg_replace('/([^0-9\.-])+/i', '', lessc::$VERSION) : '(less than v0.3.2)',
'description' => t('To check for newer versions of lessphp, go to <a href="!url" target="_blank">http://leafo.net/lessphp/</a>', array("!url" => url('http://leafo.net/lessphp/'))),
'title' => $t('LESS'),
'value' => $loaded_engine['name'] . ' - v' . $loaded_engine['version'],
'description' => $t('To check for newer versions go to <a href="@vendor_url" target="_blank">@vendor_url</a>.', array('@vendor_url' => $loaded_engine['vendor url'])),
'severity' => REQUIREMENT_OK,
);
}
if (variable_get('less_devel', FALSE)) {
$requirements['less_devel'] = array(
'title' => 'LESS developer mode',
'value' => t('Enabled'),
'description' => 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/config/development/less'))),
'severity' => REQUIREMENT_WARNING,
);
}
if (!$lessc_exists) {
else {
$requirements['less_library'] = array(
'title' => 'LESS',
'description' => t('The lessphp library was not detected. Please follow the instructions on the <a href="!url" target="_blank">LESS project page</a> to install the lessphp library.', array("!url" => url('http://drupal.org/project/less'))),
'title' => $t('LESS'),
'value' => '',
'description' => $t('A LESS library was not detected. Please follow the instructions on the <a href="!url" target="_blank">LESS project page</a> to install the a LESS library.', array("!url" => url('https://drupal.org/project/less'))),
'severity' => REQUIREMENT_ERROR,
);
}
if (variable_get(LESS_DEVEL, FALSE)) {
$requirements[LESS_DEVEL] = array(
'title' => 'LESS developer mode',
'value' => $t('Enabled'),
'description' => $t('LESS files are being checked on every request. Remember to <a href="!url">turn off</a> this feature on production websites.', array("!url" => url('admin/config/development/less'))),
'severity' => REQUIREMENT_WARNING,
);
}
break;
default:
break;
}
return $requirements;
}

View File

@@ -10,6 +10,48 @@
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().
*/
@@ -17,15 +59,30 @@ function less_menu() {
$items = array();
$items['admin/config/development/less'] = array(
'title' => 'LESS settings',
'title' => 'LESS',
'description' => 'Administer LESS settings',
'page callback' => 'drupal_get_form',
'page arguments' => array('less_settings'),
'page arguments' => array('less_settings_form'),
'access arguments' => array(LESS_PERMISSION),
'file' => 'less.admin.inc',
'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;
}
@@ -42,184 +99,563 @@ function less_permission() {
}
/**
* Builds the less cache
* 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 = variable_get('less_devel', FALSE);
$less_dir = variable_get('less_dir', '');
$less_devel = (bool) variable_get(LESS_DEVEL, FALSE);
// Force regeneration LESS files if developer mode is enabled
if ($less_devel || !$less_dir) {
$less_dir = _less_new_dir();
if ($less_devel && user_access(LESS_PERMISSION) && flood_is_allowed('less_devel_warning', 1)) {
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');
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/config/development/less'))), 'status');
}
}
$less_path = 'public://less/' . $less_dir;
foreach ($styles['#items'] as $key => $info) {
$input_file = $info['data'];
if (drupal_substr($input_file, -5) == '.less') {
$file_uri = file_uri_target($input_file);
$css_path = $less_path . '/' . dirname($file_uri ? $file_uri : $input_file);
if (!is_dir($css_path) && !file_prepare_directory($css_path, FILE_CREATE_DIRECTORY)) {
// 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 (!is_file($output_file)) {
if (_less_inc()) {
$less = new lessc();
$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');
}
}
$contents = drupal_load_stylesheet($input_file, FALSE);
// Build the base URL of this CSS file: start with the full URL.
$css_base_url = file_create_url($input_file);
// Move to the parent.
$css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/'));
// Simplify to a relative URL if the stylesheet URL starts with the
// base URL of the website.
if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) {
$css_base_url = substr($css_base_url, strlen($GLOBALS['base_root']));
}
_drupal_build_css_path(NULL, $css_base_url . '/');
// Prefix all paths within this CSS file, ignoring external and absolute paths.
$data = preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents);
try {
$output_data = $less->parse($data);
file_unmanaged_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');
}
}
}
$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');
if (is_file($output_file)) {
$styles['#items'][$key]['data'] = $output_file;
}
// 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().
*
* Flushes compiled LESS files during cache flush, except during cron.
*
* @return An array of cache table names.
* Triggers rebuild of all LESS files during cache flush, except during cron.
*/
function less_flush_caches() {
if (!drupal_static('less_cron')) {
_less_new_dir();
// Rebuild the less files directory.
_less_get_dir(TRUE);
cache_clear_all('less:', 'cache', TRUE);
}
less_clear_css_cache();
return array();
}
/**
* Implements hook_cron_queue_info().
* Deletes all stale compiled LESS files that are no longer in use.
*
* @see drupal_delete_file_if_stale().
*/
function less_cron_queue_info() {
drupal_static('less_cron', TRUE);
function less_clear_css_cache() {
file_scan_directory(LESS_DIRECTORY, '/.+/', array('callback' => 'drupal_delete_file_if_stale'));
}
/**
* Helper function to create a new less dir.
* 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_new_dir() {
$less_dir = uniqid('', TRUE);
$less_path = 'public://less/' . $less_dir;
file_prepare_directory($less_path, FILE_CREATE_DIRECTORY);
variable_set('less_dir', $less_dir);
return $less_dir;
}
/**
* Implements hook_cron().
*/
function less_cron() {
$less_dir = variable_get('less_dir', '');
function _less_get_dir($rebuild = FALSE) {
$less_dir = variable_get('less_dir');
$file_scan_options = array(
'nomask' => '/(\.\.?|CVS|' . preg_quote($less_dir) . ')$/', //adding current dir to excludes
'recurse' => FALSE,
);
$found_files = file_scan_directory('public://less', '/^.+$/', $file_scan_options);
foreach ($found_files as $found_file) {
file_unmanaged_delete_recursive($found_file->uri);
// 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)));
}
}
function less_element_info_alter(&$type) {
array_unshift($type['styles']['#pre_render'], '_less_pre_render');
return variable_get('less_dir');
}
/**
* Finds and loads the lessphp library
* 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)) {
// locations to check for lessphp, by order of preference
$include_locations = array();
$less_engine = variable_get('less_engine', 'lessphp');
// Composer created path
$include_locations[] = dirname(__FILE__) . '/vendor/autoload.php';
// load libraries module for during install
module_load_include('module', 'libraries');
if (function_exists('libraries_get_path')) {
// add libraries supported path
$include_locations[] = libraries_get_path('lessphp') . '/lessc.inc.php';
if (($less_engine_library = libraries_load($less_engine)) && $less_engine_library['installed']) {
$loaded = $less_engine;
}
// add legacy path as final possible location
$include_locations[] = dirname(__FILE__) . '/lessphp/lessc.inc.php';
foreach ($include_locations as $include_location) {
if (is_file($include_location)) {
require_once $include_location;
break;
}
}
$loaded = class_exists('lessc', TRUE);
}
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.');
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,25 @@
/**
* This was copied from twitter bootstrap's mixins.less and given a slight
* modification to make it a parametric mixin to hide from CSS output.
*
* Look at these locations for more information:
* http://lesscss.org/#-parametric-mixins
*/
// Clearfix
// --------
// For clearing floats like a boss h5bp.com/q
.clearfix() {
*zoom: 1;
&:before,
&:after {
display: table;
content: "";
// Fixes Opera/contenteditable bug:
// http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952
line-height: 0;
}
&:after {
clear: both;
}
}

View File

@@ -0,0 +1,10 @@
.gradient(@gradient_top: #bada55, @gradient_bottom: darken(@gradient_top, 10%)) {
background-color: @gradient_bottom;
background-image: -moz-linear-gradient(top, @gradient_top 0%, @gradient_bottom 100%);
background-image: -ms-linear-gradient(top, @gradient_top 0%, @gradient_bottom 100%);
background-image: -o-linear-gradient(top, @gradient_top 0%, @gradient_bottom 100%);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, @gradient_top), color-stop(1, @gradient_bottom));
background-image: -webkit-linear-gradient(top, @gradient_top 0%, @gradient_bottom 100%);
background-image: linear-gradient(top, @gradient_top 0%, @gradient_bottom 100%);
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* Callback that provides styleable content for demonstration purposes.
*/
function _less_demo_page() {
global $theme_key;
$file_less_settings = array(
'variables' => array(
'@gradient_end' => '#bada55',
),
);
$output = array(
'#attached' => array(
'css' => array(
drupal_get_path('module', 'less_demo') . '/styles/less_demo.drupal_add_css.css.less' => array(
'less' => $file_less_settings,
)
),
),
);
$output['page'] = array(
'#type' => 'container',
'#attributes' => array(
'id' => 'less_demo_gradient',
),
);
$output['page']['less_demo_logo'] = array(
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => array(
'class' => array(
'less_demo_logo',
),
),
'#value' => '', // '#value' is required for closing tag.
);
$output['page']['less_demo_theme_link'] = array(
'#type' => 'html_tag',
'#tag' => 'p',
'#value' => l('Configure current theme', 'admin/appearance/settings/' . $theme_key),
);
return $output;
}

View File

@@ -0,0 +1,30 @@
name = "LESS CSS Preprocessor - DEMO"
description = "Demonstrate LESS module functionality and implementation examples."
core = 7.x
dependencies[] = less
configure = admin/config/development/less/demo
; Sheets with .less extension will automatically receive variables below.
stylesheets[all][] = styles/less_demo.info.css.less
; Variables defined here will automatically be available inside sheets
; registered to this module.
; Variables are lazy evaluated, so they don't have to be defined in order.
less[vars][@gradient_end] = darken(@gradient_start, 10%);
less[vars][@gradient_start] = #0779bf;
less[vars][@text_glow] = lighten(@gradient_start, 10%);
; Register non-global sheets to automatically receive variables associated with
; this module.
less[sheets][] = styles/less_demo.drupal_add_css.css.less
; Information added by Drupal.org packaging script on 2015-01-30
version = "7.x-4.0"
core = "7.x"
project = "less"
datestamp = "1422653011"

View File

@@ -0,0 +1,86 @@
<?php
/**
* @file
* Contains implementations of Less module hooks.
*/
/**
* Implements hook_less_variables().
*/
function less_demo_less_variables() {
return array(
'@text_glow' => 'black', // This will take precedence over .info file value.
'@test' => '#ccc', // This value is defined for the first time here.
);
}
/**
* Implements hook_less_variables_alter().
*/
function less_demo_less_variables_alter(&$less_variables, $system_name) {
if ($system_name === 'less_demo') {
$less_variables['@text_glow'] = 'blue';
}
}
/**
* Implements hook_less_variables_SYSTEM_NAME_alter().
*/
function less_demo_less_variables_less_demo_alter(&$less_variables) {
$less_variables['@test'] = 'green';
}
/**
* Implements hook_less_functions().
*/
function less_demo_less_functions() {
return array(
'swap' => '_less_demo_reverse',
);
}
/**
* Implements hook_less_functions_alter().
*/
function less_demo_less_functions_alter(&$less_functions, $system_name) {
if ($system_name === 'less_demo') {
}
}
/**
* Implements hook_less_functions_SYSTEM_NAME_alter().
*/
function less_demo_less_functions_less_demo_alter(&$less_functions) {
}
/**
* Implements hook_less_paths().
*/
function less_demo_less_paths() {
return array(
drupal_get_path('module', 'less_demo') . '/libs',
);
}
/**
* LESS function that takes a series of arguments and reverses their order.
*/
function _less_demo_reverse($arg) {
list($type, $delimiter, $value) = $arg;
if ($type === 'list') {
$arg = array($type, $delimiter, array_reverse($value));
}
return $arg;
}

View File

@@ -0,0 +1,30 @@
<?php
/**
* Implements hook_menu().
*/
function less_demo_menu() {
$items = array();
$items['admin/config/development/less/demo'] = array(
'title' => 'LESS Demo',
'description' => 'Demonstration for less features',
'page callback' => '_less_demo_page',
'access arguments' => array(LESS_PERMISSION),
'file' => 'less_demo.demo_page.inc',
'type' => MENU_LOCAL_TASK,
'weight' => 1,
);
return $items;
}
/**
* Implements hook_admin_paths().
*/
function less_demo_admin_paths() {
return array(
'admin/config/development/less/demo' => FALSE,
);
}

View File

@@ -0,0 +1,9 @@
@import '../imports/less_demo.imported.css.less';
#header {
.gradient(@gradient_start, @gradient_end) !important;
}
.less_demo_swap {
margin: swap(10px, 15px);
}

View File

@@ -0,0 +1,58 @@
@import '../imports/less_demo.imported.css.less';
@import '../imports/less_demo.clearfix.css.less';
/**
* The following variables defined in less_demo.info:
* @gradient_start
* @gradient_end
* @text_glow
*/
@gradient_start: blue;
@gradient_end: black;
@text_glow: green;
@test: red;
#less_demo_gradient {
.clearfix;
.gradient(@gradient_start, @gradient_end);
font-family: Arial, Helvetica, sans-serif;
font-size: 80px;
text-shadow: 0 0 0 transparent, 0 0 10px @text_glow;
text-align: center;
a {
color: @test;
}
.less_demo_logo {
background: transparent url(../images/logo.png) center center no-repeat;
height:200px;
@media only screen and (max-width: 960px) {
float:left;
width: 199px;
}
}
p {
@media only screen and (max-width: 960px) {
float: left;
}
}
.less_demo_logo {
&:after {
display: block;
content: token("Site name from token in .less file: [site:name]");
font-size: 20px;
}
}
}

View File

@@ -0,0 +1,91 @@
/**
* @file
* Contains JS required for less watch functionality.
*/
;(function ($) {
"use strict";
Drupal.behaviors.less_watch = {
attach: function (context, settings) {
var watched_files = [],
$watched_links;
$watched_links = $('head link[src$=".less"]', context).each(function () {
// Only grab the portion of the url up to, but not including, the '?'.
watched_files.push($(this).attr('href'));
});
if (watched_files.length > 0) {
// Modeled after example @ http://www.erichynds.com/javascript/a-recursive-settimeout-pattern/
var poller = {
failed_requests: 0,
// starting interval in milliseconds
interval: 500,
// kicks off the setTimeout
init: function(){
setTimeout(
$.proxy(this.getData, this), // ensures 'this' is the poller obj inside getData, not the window object
this.interval
);
},
// get AJAX data + respond to it
getData: function() {
var self = this;
$.ajax({
type: 'POST',
url: settings.basePath + 'ajax/less/watch',
data: {
less_files: watched_files
},
// On success, reset failed request counter and update style links.
success: function ( response ) {
self.failed_requests = 0;
$.each(response, function (index, value) {
var old_file = value.old_file,
new_file = value.new_file;
// Math.random() at the end forces a reload of the file.
$('head link[href^="' + old_file + '"]', context).replaceWith($('<link type="text/css" rel="stylesheet" media="all" />').attr('href', new_file + '?' + Math.random()));
watched_files[$.inArray(old_file, watched_files)] = new_file;
});
},
// On failure, count failed request and increase interval.
error: function () {
++self.failed_requests;
self.interval += 500;
},
// On success or failure, restart
complete: function () {
if( ++self.failed_requests < 10 ){
self.init();
}
}
});
}
};
poller.init();
}
}
};
})(jQuery);

View File

@@ -0,0 +1,3 @@
fieldset.vertical-tabs-pane fieldset.less-theme-settings legend {
display: none;
}

View File

@@ -0,0 +1,90 @@
<?php
class LessUnitTest extends DrupalUnitTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Less Unit Tests',
'description' => 'Tests functions that only act on input and do not require a DB.',
'group' => 'Less',
);
}
public function setUp() {
module_load_include('module', 'less');
module_load_include('inc', 'less', 'includes/less.process');
parent::setUp();
}
/**
* Test the _less_output_path() function.
*
* @see _less_output_path()
*/
public function test_less_output_path() {
$less_item = array(
'less' => array(
'build_cache_id' => 'Zxz5NVNO0Ad9miD2JeGw1B_3auVKtmJJ7ksCdxaeZ0A',
'variables' => array(
'@gradient_end' => 'darken(@gradient_start, 10%);',
'@gradient_start' => '#0779bf;',
'@text_glow' => 'blue',
'@test' => 'green',
'@header_from' => 'test',
),
'functions' => array(
'token' => '_less_token_replace',
'swap' => '_less_demo_reverse',
),
'paths' => array(
0 => 'sites/all/modules/custom/less/less_demo/libs',
),
'less_autoprefixer' => TRUE,
'less_devel' => FALSE,
'less_source_maps' => FALSE,
'theme' => 'seven',
'output_file' => NULL,
'build_required' => NULL,
'input_file' => 'sites/all/modules/custom/less/less_demo/styles/less_demo.info.css.less',
),
);
_less_output_path($less_item, NULL);
$expected_output_file = 'public://less/less_demo.info.aIR_TiwEIzIVLaH9dWKmPfbShza_NoIVp_TPs0tPvaM.css';
$this->assertIdentical($less_item['less']['output_file'], $expected_output_file, 'Output file location processes correctly.');
}
/**
* Test the _less_rewrite_paths() function.
*
* @see _less_rewrite_paths()
*/
public function test_less_rewrite_paths() {
$input_file_path = 'sites/all/modules/custom/less/less_demo/styles/less_demo.info.css.less';
$less_compiled_css = <<<'EOD'
#less_demo_gradient .less_demo_logo {
background: transparent url(../images/logo.png) center center no-repeat;
}
EOD;
$expected_rewritten_output_data = <<<EOD
#less_demo_gradient .less_demo_logo {
background: transparent url({$GLOBALS['base_path']}sites/all/modules/custom/less/less_demo/images/logo.png) center center no-repeat;
}
EOD;
$actual_rewritten_output_data = _less_rewrite_paths($input_file_path, $less_compiled_css);
$this->assertIdentical($actual_rewritten_output_data, $expected_rewritten_output_data, 'Rewritten paths are correct.');
}
}