diff --git a/classes/class.lessautoprefixer.inc b/classes/class.lessautoprefixer.inc
deleted file mode 100644
index 719774c..0000000
--- a/classes/class.lessautoprefixer.inc
+++ /dev/null
@@ -1,159 +0,0 @@
-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;
-
- if (function_exists('proc_open')) {
-
- 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;
- }
-}
diff --git a/classes/class.lessjs.inc b/classes/class.lessjs.inc
deleted file mode 100644
index 4d67517..0000000
--- a/classes/class.lessjs.inc
+++ /dev/null
@@ -1,293 +0,0 @@
-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;
-
- if (function_exists('proc_open')) {
-
- 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;
- }
-}
diff --git a/config/install/less.settings.yml b/config/install/less.settings.yml
new file mode 100644
index 0000000..cd969e9
--- /dev/null
+++ b/config/install/less.settings.yml
@@ -0,0 +1,6 @@
+engine: ~
+autoprefixer: false
+developer_options:
+ devel: false
+ source_maps: false
+ watch_mode: false
diff --git a/config/schema/less.schema.yml b/config/schema/less.schema.yml
new file mode 100644
index 0000000..2a15725
--- /dev/null
+++ b/config/schema/less.schema.yml
@@ -0,0 +1,25 @@
+# Schema for the configuration files of the less module.
+
+less.settings:
+ type: config_object
+ label: 'LESS settings'
+ mapping:
+ engine:
+ type: string
+ label: 'Engine'
+ autoprefixer:
+ type: boolean
+ label: 'Use Autoprefixer'
+ developer_options:
+ type: mapping
+ label: 'Developer Options'
+ mapping:
+ devel:
+ type: boolean
+ label: 'LESS developer mode'
+ source_maps:
+ type: boolean
+ label: 'Source Maps'
+ watch_mode:
+ type: boolean
+ label: 'Watch Mode'
diff --git a/engines/abstract.LessEngine.inc b/engines/abstract.LessEngine.inc
deleted file mode 100644
index 852c6b7..0000000
--- a/engines/abstract.LessEngine.inc
+++ /dev/null
@@ -1,101 +0,0 @@
- 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;
- }
-}
diff --git a/engines/engine.less_js.inc b/engines/engine.less_js.inc
deleted file mode 100644
index 50f1b2c..0000000
--- a/engines/engine.less_js.inc
+++ /dev/null
@@ -1,71 +0,0 @@
-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;
- }
-}
diff --git a/engines/engine.less_php.inc b/engines/engine.less_php.inc
deleted file mode 100644
index 6b0f9d8..0000000
--- a/engines/engine.less_php.inc
+++ /dev/null
@@ -1,63 +0,0 @@
-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;
- }
-}
diff --git a/engines/engine.lessphp.inc b/engines/engine.lessphp.inc
deleted file mode 100644
index 290c7c0..0000000
--- a/engines/engine.lessphp.inc
+++ /dev/null
@@ -1,54 +0,0 @@
-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;
- }
-}
diff --git a/engines/interface.LessEngine.inc b/engines/interface.LessEngine.inc
deleted file mode 100644
index c08aae7..0000000
--- a/engines/interface.LessEngine.inc
+++ /dev/null
@@ -1,67 +0,0 @@
- url('http://php.net/manual/en/function.proc-open.php'),
- '@disable_functions_url' => url('http://php.net/manual/en/ini.core.php#ini.disable-functions'),
- );
-
- drupal_set_message(t('PHP function proc_open() is currently disabled. You will be unable to less.js or Autoprefixer.', $message_vars), 'warning');
- }
-
- $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 - @vendor_url', 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 - @vendor_url', 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) && !empty($lessautoprefixer_library['installed']),
- '#disabled' => empty($lessautoprefixer_library['installed']),
- );
-
- if ($lessautoprefixer_library['installed']) {
- $form[LESS_AUTOPREFIXER]['#description'] .= '
'. 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');
-}
diff --git a/includes/less.libraries.inc b/includes/less.libraries.inc
deleted file mode 100644
index f5ea0e5..0000000
--- a/includes/less.libraries.inc
+++ /dev/null
@@ -1,209 +0,0 @@
- '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
- }
- }
- }
-}
diff --git a/includes/less.process.inc b/includes/less.process.inc
deleted file mode 100644
index 6aa4c5a..0000000
--- a/includes/less.process.inc
+++ /dev/null
@@ -1,281 +0,0 @@
- 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 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;
-}
diff --git a/includes/less.theme.inc b/includes/less.theme.inc
index ae6e9d1..0abc8cd 100755
--- a/includes/less.theme.inc
+++ b/includes/less.theme.inc
@@ -71,11 +71,6 @@ function less_form_alter(&$form, &$form_state, $form_id) {
'#prefix' => t('
LESS settings
'),
'#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;
@@ -102,7 +97,7 @@ function _less_setting_form_element($system_name, $less_settings, $saved_setting
}
foreach ($less_settings as $var_name => $var_value) {
-
+
$form[$var_name] = array(
'#type' => 'textfield',
'#title' => $var_name,
@@ -111,6 +106,6 @@ function _less_setting_form_element($system_name, $less_settings, $saved_setting
'#attributes' => array('placeholder' => $var_value),
);
}
-
+
return $form;
}
diff --git a/includes/less.watch.inc b/includes/less.watch.inc
deleted file mode 100644
index c630372..0000000
--- a/includes/less.watch.inc
+++ /dev/null
@@ -1,61 +0,0 @@
-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;
-}
-
diff --git a/includes/less.wysiwyg.inc b/includes/less.wysiwyg.inc
index 14ac494..6362bb0 100644
--- a/includes/less.wysiwyg.inc
+++ b/includes/less.wysiwyg.inc
@@ -29,40 +29,42 @@ function less_wysiwyg_editor_settings_alter(&$settings, $context) {
// Keep track if comma separated paths, or array of paths.
$is_array = is_array($stylesheets);
- // $stylesheets is an array or comma separated list of file paths.
- $stylesheets = $is_array ? $stylesheets : explode(',', $stylesheets);
+ if ($is_array === FALSE) {
- // Create a 'value => value' array to preserve original stylesheet order.
- $stylesheets = drupal_map_assoc($stylesheets);
-
- // Do not attempt to process remote or regular CSS files.
- $local_stylesheets = array_filter(array_map('parse_url', $stylesheets), function ($value) {
- return empty($value['host']) && preg_match('/.*\.less$/i', $value['path']) == 1;
- });
+ // $stylesheets is a list of comma separated file paths.
+ $stylesheets = explode(',', $stylesheets);
+ }
// Prepare an array that can be handled by normal LESS module processing.
- $items = array_map(function ($stylesheet) {
- return array(
- 'data' => trim($stylesheet['path'], '/'),
- );
- }, $local_stylesheets);
+ $styles = array(
+ '#items' => array(),
+ );
+
+ foreach ($stylesheets as $stylesheet) {
- $processed_styles = _less_pre_render(array('#items' => $items));
+ // Might contain ?query portion, separate parts.
+ $parts = drupal_parse_url($stylesheet);
- // Compiled path is in 'data' index of each sheet item.
- $compiled_sheets = array_map(function ($value) { return $value['data']; }, $processed_styles['#items']);
+ // Paths are expected to be relative to DRUPAL_ROOT, trim leading '/'.
+ $path = trim($parts['path'], '/');
+
+ $styles['#items'][$path] = array(
+ 'data' => $path,
+ );
+ }
- // WYSIWYGs can't handle Drupal public:// paths, so get an accessible URL.
- $compiled_sheets = array_map('file_create_url', $compiled_sheets);
+ $styles = _less_pre_render($styles);
- // Replace paths to compiled output paths based on key (path) of original file.
- $processed_stylesheets = array_replace($stylesheets, $compiled_sheets);
+ $processed_stylesheets = array();
- // Need a integer indexed array so JSON encoding doesn't attempt to make a object.
- $processed_stylesheets = array_values($processed_stylesheets);
+ foreach ($styles['#items'] as $file) {
+ $processed_stylesheets[] = file_create_url($file['data']);
+ }
// Recombine file paths into comma separated list.
- $processed_stylesheets = $is_array ? $processed_stylesheets : implode(',', $processed_stylesheets);
+ if ($is_array === FALSE) {
+ $processed_stylesheets = implode(',', $processed_stylesheets);
+ }
$settings[$editors[$wysiwyg]] = $processed_stylesheets;
}
diff --git a/less.api.php b/less.api.php
index b158d92..86a9034 100644
--- a/less.api.php
+++ b/less.api.php
@@ -12,12 +12,12 @@
/**
* 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().
@@ -38,45 +38,23 @@ function hook_less_variables() {
* 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()
+ * @see hook_less_variables_SYSTEM_NAME_alter()
*/
-function hook_less_variables_SYSTEM_NAME_alter(array &$less_variables) {
-
- $less_variables['@variable_name_2'] = 'lighten(@variable_name_1, 20%)';
+function hook_less_variables_alter(array &$variables) {
+ $variables['@variable_name_1'] = '#ddd';
}
/**
- * Provide a list of lookup paths for @import statements in .less files.
+ * Provide a list of lookup directories for @import statements in .less files.
*
* @return string[]
*/
-function hook_less_paths() {
-
+function hook_less_import_directories() {
return array(
drupal_get_path('module', 'less_demo') . '/libs',
);
@@ -85,89 +63,10 @@ function hook_less_paths() {
/**
* 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
+ * @param &string[] $import_directories
*/
-function hook_less_functions_SYSTEM_NAME_alter(array &$less_functions) {
-
+function hook_less_import_directories_alter(array &$import_directories) {
+ $import_directories[] = drupal_get_path('module', 'less_demo') . '/other_path';
}
/**
diff --git a/less.drush.inc b/less.drush.inc
index f9c7c31..2fcbf4f 100644
--- a/less.drush.inc
+++ b/less.drush.inc
@@ -7,9 +7,11 @@
/**
* Implements hook_drush_cache_clear().
- *
+ *
* This adds an option on drush 'cache-clear'.
+ *
+ * @inheritdoc
*/
-function less_drush_cache_clear(&$types) {
- $types['less'] = 'less_flush_caches';
+function less_drush_cache_clear(&$types, $include_bootstrapped_types) {
+ $types['less'] = 'less_clear_css_cache_files';
}
diff --git a/less.info b/less.info
deleted file mode 100644
index 9b58e4f..0000000
--- a/less.info
+++ /dev/null
@@ -1,23 +0,0 @@
-
-name = "LESS CSS Preprocessor"
-description = "Allows themes or modules to use LESS files."
-
-php = 5.3
-
-core = 7.x
-
-configure = admin/config/development/less
-
-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
diff --git a/less.info.yml b/less.info.yml
new file mode 100644
index 0000000..705cec4
--- /dev/null
+++ b/less.info.yml
@@ -0,0 +1,7 @@
+name: 'LESS CSS Preprocessor'
+type: module
+description: 'Allows themes or modules to use LESS files.'
+core: 8.x
+package: Other
+
+configure: less.admin_settings
diff --git a/less.install b/less.install
index 63018f7..33c3f63 100644
--- a/less.install
+++ b/less.install
@@ -4,24 +4,19 @@
* @file
* Install, update, and uninstall functions for the less module.
*/
-
+
/**
* Implements hook_uninstall().
*/
function less_uninstall() {
+ /** @var \Drupal\Core\File\FileSystemInterface $fileSystem */
+ $fileSystem = \Drupal::service('file_system');
+
+ // Delete cached css files.
+ $fileSystem->rmdir('public://less');
- // Ensure Less module constants are available during uninstall.
- drupal_load('module', 'less');
-
- 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);
+ // Delete state for cached css files.
+ \Drupal::state()->delete('less_css_cache_files');
}
/**
@@ -29,46 +24,52 @@ function less_uninstall() {
*/
function less_requirements($phase) {
$requirements = array();
-
- $t = get_t();
-
+
switch ($phase) {
case 'runtime':
-
- $less_engine_loaded = _less_inc();
+ /** @var \Drupal\less\Plugin\LessEngineManager $engineManager */
+ $engineManager = \Drupal::service('plugin.manager.less_engine');
+
+ /** @var \Drupal\Core\Config\Config $config */
+ $config = \Drupal::service('config.factory')->get('less.settings');
- if (!empty($less_engine_loaded)) {
-
- $loaded_engine = libraries_detect($less_engine_loaded);
-
- $requirements['less_version'] = array(
- 'title' => $t('LESS'),
- 'value' => $loaded_engine['name'] . ' - v' . $loaded_engine['version'],
- 'description' => $t('To check for newer versions go to @vendor_url.', array('@vendor_url' => $loaded_engine['vendor url'])),
+ $pluginId = $config->get('engine');
+ $pluginDefinition = $engineManager->getDefinition($pluginId);
+
+ if (!empty($pluginDefinition)) {
+ $less_version_info = t('@title, version @version', [
+ '@title' => $pluginDefinition['title'],
+ '@version' => call_user_func([$pluginDefinition['class'], 'getVersion'])
+ ]);
+ $requirements['less_engine'] = array(
+ 'title' => t('LESS CSS Preprocessor'),
+ 'value' => $less_version_info,
+ 'description' => t('To check for newer versions go to :url.', array(':url' => $pluginDefinition['url'])),
'severity' => REQUIREMENT_OK,
);
}
else {
- $requirements['less_library'] = array(
- 'title' => $t('LESS'),
+ $requirements['less_engine'] = array(
+ 'title' => t('LESS CSS Preprocessor'),
'value' => '',
- 'description' => $t('A LESS library was not detected. Please follow the instructions on the LESS project page to install the a LESS library.', array("!url" => url('https://drupal.org/project/less'))),
+ 'description' => t('A LESS library was not detected. Please follow the instructions on the LESS project page to install the a LESS library.', array(':url' => 'https://drupal.org/project/less')),
'severity' => REQUIREMENT_ERROR,
);
}
- if (variable_get(LESS_DEVEL, FALSE)) {
- $requirements[LESS_DEVEL] = array(
+
+ if ($config->get('developer_options.devel')) {
+ $requirements['less_devel_mode'] = array(
'title' => 'LESS developer mode',
- 'value' => $t('Enabled'),
- 'description' => $t('LESS files are being checked on every request. Remember to turn off this feature on production websites.', array("!url" => url('admin/config/development/less'))),
+ 'value' => t('Enabled'),
+ 'description' => t('LESS files are being created on every request. Remember to turn off this feature on production websites.', [':url' => \Drupal\Core\Url::fromRoute('less.admin_settings')->toString()]),
'severity' => REQUIREMENT_WARNING,
);
}
break;
-
+
default:
break;
}
-
+
return $requirements;
}
diff --git a/less.links.menu.yml b/less.links.menu.yml
new file mode 100644
index 0000000..fa25bee
--- /dev/null
+++ b/less.links.menu.yml
@@ -0,0 +1,5 @@
+less.admin_settings:
+ title: 'LESS settings'
+ description: 'Administer LESS settings.'
+ route_name: less.admin_settings
+ parent: 'system.admin_config_development'
diff --git a/less.links.task.yml b/less.links.task.yml
new file mode 100644
index 0000000..6d58eca
--- /dev/null
+++ b/less.links.task.yml
@@ -0,0 +1,5 @@
+less.admin_settings:
+ title: 'Settings'
+ route_name: less.admin_settings
+ base_route: less.admin_settings
+ weight: 0
diff --git a/less.module b/less.module
index 524b8c8..b15b3aa 100644
--- a/less.module
+++ b/less.module
@@ -8,654 +8,277 @@
* customized by user themes.
*/
-define('LESS_PERMISSION', 'administer less');
-
-define('LESS_AUTOPREFIXER', 'less_autoprefixer');
-
-define('LESS_DEVEL', 'less_devel');
-define('LESS_WATCH', 'less_watch');
-define('LESS_SOURCE_MAPS', 'less_source_maps');
-
-define('LESS_DIRECTORY', 'public://less');
-
-require_once dirname(__FILE__) . '/includes/less.libraries.inc';
-require_once dirname(__FILE__) . '/includes/less.wysiwyg.inc';
-require_once dirname(__FILE__) . '/includes/less.theme.inc';
-
-/**
- * Implements hook_hook_info().
- */
-function less_hook_info() {
-
- $less_hooks = array(
- 'engines',
- 'variables',
- 'paths',
- 'functions',
- );
-
- $hooks = array();
-
- /**
- * We don't have to worry about less_HOOK_SYSTEM_NAME_alter variations here
- * as less_HOOK_alter is run immediately before and should include the
- * MODULE.less.inc file containing any
- * less_HOOK_SYSTEM_NAME_alter() implementations.
- */
- foreach ($less_hooks as $hook) {
- $hooks[] = 'less_' . $hook;
- $hooks[] = 'less_' . $hook . '_alter';
- }
-
- return array_fill_keys($hooks, array(
- 'group' => 'less',
- ));
-}
-
-/**
- * Implements hook_menu().
- */
-function less_menu() {
- $items = array();
-
- $items['admin/config/development/less'] = array(
- 'title' => 'LESS',
- 'description' => 'Administer LESS settings',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('less_settings_form'),
- 'access arguments' => array(LESS_PERMISSION),
- 'file' => 'includes/less.admin.inc',
- 'type' => MENU_NORMAL_ITEM,
- );
-
- $items['admin/config/development/less/settings'] = array(
- 'title' => 'LESS Settings',
- 'type' => MENU_DEFAULT_LOCAL_TASK,
- );
-
- $items['ajax/less/watch'] = array(
- 'title' => 'LESS watch callback',
- 'type' => MENU_CALLBACK,
- 'page callback' => '_less_watch',
- 'access callback' => 'variable_get',
- 'access arguments' => array(LESS_WATCH, FALSE),
- 'delivery callback' => 'drupal_json_output',
- 'file' => 'includes/less.watch.inc',
- );
-
- return $items;
-}
-
-/**
- * Implements hook_permission().
- */
-function less_permission() {
- return array(
- LESS_PERMISSION => array(
- 'title' => t('Administer LESS'),
- 'description' => t('Access the LESS settings page and view debug messages.'),
- ),
- );
-}
-
-/**
- * Implements hook_element_info_alter().
- */
-function less_element_info_alter(&$type) {
-
- // Prepend to the list of #pre_render functions so it runs first.
- array_unshift($type['styles']['#pre_render'], '_less_pre_render');
-
- if (variable_get(LESS_DEVEL, FALSE)) {
-
- // Must run after drupal_pre_render_styles() to attach any attributes.
- array_push($type['styles']['#pre_render'], '_less_attach_src');
- }
-}
+use Drupal\Core\Asset\AttachedAssetsInterface;
+use Drupal\Core\Asset\CssOptimizer;
/**
- * Add original .less file path as 'src' attribute to .
- *
- * @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()
+ * Implements hook_css_alter().
+ *
+ * Convert the LESS files to CSS.
+ *
+ * @param $css
+ * An array of all CSS items (files and inline CSS) being requested on the page.
+ * @param \Drupal\Core\Asset\AttachedAssetsInterface $assets
+ * The assets attached to the current response.
+ *
+ * @see Drupal\Core\Asset\LibraryResolverInterface::getCssAssets()
*/
-function _less_attach_src($styles) {
-
- foreach (element_children($styles) as $key) {
-
- // If its a , 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']);
+function less_css_alter(&$css, AttachedAssetsInterface $assets) {
+ // Prepare a map of .less to .css files.
+ $map = \Drupal::state()->get('less_css_cache_files') ?: [];
+
+ foreach ($css AS &$style) {
+ if ($style['type'] == 'file' && substr($style['data'], -5) == '.less') {
+ $source_file_path = $style['data'];
+ if ($compiled_file_info = _less_process_file($source_file_path)) {
+ // Save the state after each change.
+ $style['data'] = $compiled_file_info['destination_file_path'];
+ $map[$source_file_path] = $compiled_file_info['destination_file_path'];
+ \Drupal::state()->set('less_css_cache_files', $map);
}
}
}
-
- 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.
+ * Helper function for hook_css_alter().
+ *
+ * @param string $source_file_path
+ * A relative path to the source file.
+ *
+ * @param bool $process_only
+ * Whether or not to just compile the file. Used in devel mode.
+ *
+ * @return array|null
+ * Information about the compiled CSS or NULL.
*/
-function _less_pre_render($styles) {
-
- $less_devel = (bool) variable_get(LESS_DEVEL, FALSE);
-
- if ($less_devel) {
-
- if (variable_get(LESS_WATCH, FALSE)) {
- drupal_add_js(drupal_get_path('module', 'less') . '/scripts/less.watch.js');
- }
-
- // Warn users once every hour that less is checking for file modifications.
- if (user_access(LESS_PERMISSION) && flood_is_allowed('less_devel_warning', 1)) {
- flood_register_event('less_devel_warning');
-
- $message_vars = array(
- '@url' => url('admin/config/development/less'),
- );
- drupal_set_message(t('LESS files are being checked for modifications on every request. Remember to turn off this feature on production websites.', $message_vars), 'status');
- }
- }
-
- $less_items = array_intersect_key($styles['#items'], array_flip(_less_children($styles['#items'])));
+function _less_process_file($source_file_path, $process_only = FALSE) {
+ /** @var \Drupal\Core\Config\Config $config */
+ $config = \Drupal::service('config.factory')->get('less.settings');
+
+ /** @var \Drupal\Core\File\FileSystemInterface $fileSystem */
+ $fileSystem = \Drupal::service('file_system');
- if (!empty($less_items)) {
-
- require_once dirname(__FILE__) . '/includes/less.process.inc';
-
- // Attach settings to each item.
- array_walk($less_items, '_less_attach_settings');
+ /** @var \Drupal\less\Plugin\LessEngineManager $engineManager */
+ $engineManager = \Drupal::service('plugin.manager.less_engine');
- // Determine output path for each item.
- array_walk($less_items, '_less_output_path');
+ /** @var \Drupal\less\Plugin\LessEngineInterface $engine */
+ $engine = $engineManager->createEngine();
- // Check for rebuild each page.
- if ($less_devel) {
+ $engine->setSource($source_file_path);
- array_walk($less_items, '_less_check_build');
+ // If the file does not exist, process the original LESS file and output
+ // the data into the temporary file.
+ $computed_style = NULL;
+ $create_destination_file = !$engine->destinationExists() && !$config->get('developer_options.devel');
+ if ($create_destination_file || $process_only) {
+ if ($config->get('developer_options.devel')) {
+ $engine->setSourceMaps($config->get('developer_options.source_maps'), DRUPAL_ROOT, base_path());
}
- // Compile '.less' files.
- array_walk($less_items, '_less_process_file');
+ // Build the destination folder tree if it doesn't already exist.
+ $directory = $fileSystem->dirname($engine->getDestinationUri());
+ if (!$process_only && !file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+ throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $directory));
+ }
- // Store cache information.
- if ($less_devel) {
+ $less_import_directories = less_get_import_directories();
+ if (!empty($less_import_directories)) {
+ $engine->setImportDirectories($less_import_directories);
+ }
- array_walk($less_items, '_less_store_cache_info');
+ $less_variables = less_get_variables();
+ if (!empty($less_variables)) {
+ $engine->modifyVariables($less_variables);
}
- $styles['#items'] = array_replace($styles['#items'], $less_items);
- }
-
- return $styles;
-}
+ try {
+ $computed_style = $engine->compile();
-/**
- * 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;
-}
+ // Fix paths for images as the .css is in different location.
+ $css_optimizer = new CssOptimizer();
-/**
- * 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() {
+ // Return the path to where this CSS file originated from, stripping
+ // off the name of the file at the end of the path.
+ $css_optimizer->rewriteFileURIBasePath = base_path() . dirname($source_file_path) . '/';
- drupal_static('less_cron', TRUE);
-}
+ // Convert all relative paths with absolute paths.
+ $computed_style = preg_replace_callback(
+ '/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i',
+ array($css_optimizer, 'rewriteFileURI'),
+ $computed_style
+ );
+ } catch (\Exception $exception) {
+ watchdog_exception('less', $exception, $exception->getMessage(), [], 'error');
+ }
-/**
- * Implements hook_flush_caches().
- *
- * Triggers rebuild of all LESS files during cache flush, except during cron.
- */
-function less_flush_caches() {
- if (!drupal_static('less_cron')) {
-
- // Rebuild the less files directory.
- _less_get_dir(TRUE);
- cache_clear_all('less:', 'cache', TRUE);
- }
+ if (!$process_only && isset($computed_style)) {
+ file_unmanaged_save_data($computed_style, $engine->getDestinationUri(), FILE_EXISTS_REPLACE);
+ }
- less_clear_css_cache();
+ $use_autoprefixer = FALSE;
+ if ($use_autoprefixer) {
+ try {
+ // $autoprefixer = new AutoprefixerCliWrapper($compiled_file_real_path);
+ // $autoprefixer->compile();
+ } catch (\Exception $exception) {
+ watchdog_exception('less', $exception, $exception->getMessage(), [], 'error');
+ }
+ }
+ }
- return array();
-}
+ $return = [
+ 'source_file_path' => $source_file_path,
+ ];
-/**
- * Deletes all stale compiled LESS files that are no longer in use.
- *
- * @see drupal_delete_file_if_stale().
- */
-function less_clear_css_cache() {
-
- file_scan_directory(LESS_DIRECTORY, '/.+/', array('callback' => 'drupal_delete_file_if_stale'));
-}
+ if ($config->get('developer_options.devel')) {
+ if ($process_only) {
+ return $return + [
+ 'computed_style' => $computed_style,
+ ];
+ }
-/**
- * Get/(re)generate current 'less_dir' variable.
- *
- * @param bool $rebuild
- * Flag to rebuild compiled output.
- *
- * @return string
- * current 'less_dir' Drupal variable value.
- */
-function _less_get_dir($rebuild = FALSE) {
- $less_dir = variable_get('less_dir');
-
- // If drupal variable 'less_dir' is not set, empty, or manually reset, then
- // generate a new unique id and save it.
- if ($rebuild || empty($less_dir)) {
-
- // Set the less directory variable.
- variable_set('less_dir', drupal_hash_base64(uniqid('', TRUE)));
+ // Point to the compiled file instead of the source file.
+ return $return + [
+ 'destination_file_path' => $engine->uriToRelativePath($engine->getDestinationUri()),
+ ];
}
-
- return variable_get('less_dir');
-}
-/**
- * Loads the selected LESS engine, or 'lessphp' for legacy reasons.
- *
- * @return bool
- * TRUE if selected LESS engine is loaded.
- */
-function _less_inc() {
- static $loaded = NULL;
-
- if (!isset($loaded)) {
-
- $less_engine = variable_get('less_engine', 'lessphp');
-
- if (($less_engine_library = libraries_load($less_engine)) && $less_engine_library['installed']) {
- $loaded = $less_engine;
- }
+ // Point to the compiled file instead of the source file.
+ if ($engine->destinationExists()) {
+ return $return + [
+ 'destination_file_path' => $engine->uriToRelativePath($engine->getDestinationUri()),
+ ];
}
-
- return $loaded;
+
+ return NULL;
}
/**
- * 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.
+ * Clear the less cache 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));
- }
- }
+function less_clear_css_cache_files() {
+ $delete_stale = function ($uri) {
+ // Default stale file threshold is 30 days.
+ if (REQUEST_TIME - filemtime($uri) > \Drupal::config('system.performance')->get('stale_file_threshold')) {
+ // Delete the file.
+ file_unmanaged_delete($uri);
+
+ // Get the map of .less to .css files.
+ $map = \Drupal::state()->get('less_css_cache_files') ?: [];
+ if ($key = array_search($uri, $map)) {
+ unset($map[$key]);
}
-
- cache_set('less:stylesheets', $static_stylesheets);
- cache_set('less:defaults', $static_defaults);
+ \Drupal::state()->set('less_css_cache_files', $map);
}
- }
-
-}
+ };
-/**
- * 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;
-}
+ // Delete cached less files.
+ file_scan_directory('public://less', '/.*/', array('callback' => $delete_stale));
-/**
- * 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')));
+ /** @var \Drupal\Core\File\FileSystemInterface $fileSystem */
+ $fileSystem = \Drupal::service('file_system');
+
+ // Delete remaining empty directories.
+ $empty_directories = less_find_empty_directories('public://less');
+ foreach ($empty_directories as $empty_directory) {
+ if (is_dir($empty_directory)) {
+ $fileSystem->rmdir($empty_directory);
}
-
- // 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().
+ * COPIED FROM DRUSH.
+ * @see drush_find_empty_directories();
+ *
+ * Return an array of empty directories.
*
- * @param string[] $arg
+ * Walk a directory and return an array of subdirectories that are empty. Will
+ * return the given directory if it's empty.
+ * If a list of items to exclude is provided, subdirectories will be considered
+ * empty even if they include any of the items in the list.
+ *
+ * @param string $dir
+ * Path to the directory to work in.
+ * @param array $exclude
+ * Array of files or directory to exclude in the check.
*
* @return array
+ * A list of directory paths that are empty. A directory is deemed to be empty
+ * if it only contains excluded files or directories.
*/
-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);
+function less_find_empty_directories($dir, $exclude = array()) {
+ // Skip files.
+ if (!is_dir($dir)) {
+ return array();
+ }
+ $to_exclude = array_merge(array('.', '..'), $exclude);
+ $empty_dirs = array();
+ $dir_is_empty = TRUE;
+ foreach (scandir($dir) as $file) {
+ // Skip excluded directories.
+ if (in_array($file, $to_exclude)) {
+ continue;
}
-
- 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);
- }
-
+ // Recurse into sub-directories to find potentially empty ones.
+ $subdir = $dir . '/' . $file;
+ $empty_dirs += less_find_empty_directories($subdir, $exclude);
+ // $empty_dir will not contain $subdir, if it is a file or if the
+ // sub-directory is not empty. $subdir is only set if it is empty.
+ if (!isset($empty_dirs[$subdir])) {
+ $dir_is_empty = 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';
+ if ($dir_is_empty) {
+ $empty_dirs[$dir] = $dir;
+ }
+ return $empty_dirs;
}
/**
- * Implements hook_less_engines().
+ * Get the variables as defined by hook_less_variables().
*
- * @return string[]
+ * @see hook_less_variables()
+ * @return array
*/
-function less_less_engines() {
+function less_get_variables() {
+ $less_variables = drupal_static(__FUNCTION__);
- return array(
- 'less.php' => 'LessEngineLess_php',
- 'lessphp' => 'LessEngineLessphp',
- 'less.js' => 'LessEngineLess_js',
- );
-}
+ if (isset($less_variables)) {
+ return $less_variables;
+ }
-/**
- * @return \LessEngineInterface[]
- */
-function _less_get_engines() {
+ /** @var \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler */
+ $moduleHandler = \Drupal::service('module_handler');
- $registered_engines = module_invoke_all('less_engines');
- drupal_alter('less_engines', $registered_engines);
+ /** @var array $less_variables */
+ $less_variables = $moduleHandler->invokeAll('less_variables');
+ $moduleHandler->alter('less_variables', $less_variables);
- return $registered_engines;
+ return $less_variables;
}
/**
- * @param $input_file_path
- *
- * @return \LessEngine
+ * Get a list of directories the parser should use for determining import paths.
*
- * @throws Exception
+ * @see hook_import_directories()
+ * @return array
*/
-function less_get_engine($input_file_path) {
-
- $engines = _less_get_engines();
- $selected_engine = _less_inc();
+function less_get_import_directories() {
+ $less_import_directories = drupal_static(__FUNCTION__);
- if (!empty($engines[$selected_engine])) {
+ if (isset($less_import_directories)) {
+ return $less_import_directories;
+ }
- $class_name = $engines[$selected_engine];
+ /** @var \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler */
+ $moduleHandler = \Drupal::service('module_handler');
- return new $class_name($input_file_path);
- }
- else {
+ /** @var array $less_import_directories */
+ $less_import_directories = $moduleHandler->invokeAll('less_import_directories');
+ $moduleHandler->alter('less_import_directories', $less_import_directories);
- throw new Exception('Unable to load LessEngine.');
- }
+ return $less_import_directories;
}
diff --git a/less.permissions.yml b/less.permissions.yml
new file mode 100644
index 0000000..763c1f6
--- /dev/null
+++ b/less.permissions.yml
@@ -0,0 +1,5 @@
+
+administer less:
+ title: 'Administer LESS'
+ description: 'Access the LESS settings page and view debug messages.'
+ restrict access: true
diff --git a/less.routing.yml b/less.routing.yml
new file mode 100644
index 0000000..ff78c70
--- /dev/null
+++ b/less.routing.yml
@@ -0,0 +1,25 @@
+less.admin_settings:
+ path: '/admin/config/development/less'
+ defaults:
+ _form: '\Drupal\less\Form\SettingsForm'
+ _title: 'LESS settings'
+ requirements:
+ _permission: 'administer less'
+
+less.watch_controller:
+ path: '/ajax/less/watch'
+ defaults:
+ _controller: '\Drupal\less\Controller\LessWatchController::watch'
+ _title: 'LESS watch'
+ requirements:
+ _permission: 'administer less'
+
+less.cached_file_private:
+ path: '/system/files/less/{cache_id}/{scheme}'
+ defaults:
+ _controller: '\Drupal\less\Controller\LessCachedFileDownloadController::deliver'
+ requirements:
+ _access: 'TRUE'
+
+route_callbacks:
+ - '\Drupal\less\Routing\LessCachedFileRoutes::routes'
diff --git a/less.services.yml b/less.services.yml
new file mode 100644
index 0000000..54eb1c9
--- /dev/null
+++ b/less.services.yml
@@ -0,0 +1,11 @@
+services:
+ plugin.manager.less_engine:
+ class: Drupal\less\Plugin\LessEngineManager
+ parent: default_plugin_manager
+ arguments: ['@config.factory']
+
+ path_processor.less_cached_files:
+ class: Drupal\less\PathProcessor\PathProcessorLessCachedFiles
+ arguments: ['@stream_wrapper_manager']
+ tags:
+ - { name: path_processor_inbound, priority: 300 }
diff --git a/src/Annotation/LessEngine.php b/src/Annotation/LessEngine.php
new file mode 100644
index 0000000..f57b850
--- /dev/null
+++ b/src/Annotation/LessEngine.php
@@ -0,0 +1,49 @@
+input_file = $input_file;
+ }
+
+ /**
+ * @param string $input_file
+ *
+ * @return AutoprefixerCliWrapper
+ */
+ 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;
+ }
+}
diff --git a/src/Controller/LessCachedFileDownloadController.php b/src/Controller/LessCachedFileDownloadController.php
new file mode 100644
index 0000000..f90e1ef
--- /dev/null
+++ b/src/Controller/LessCachedFileDownloadController.php
@@ -0,0 +1,117 @@
+lock = $lock;
+ $this->fileSystem = $file_system;
+ $this->engineManager = $engineManager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('file_system'),
+ $container->get('lock'),
+ $container->get('plugin.manager.less_engine')
+ );
+ }
+
+ /**
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The request object.
+ * @param string $scheme
+ * The file scheme, defaults to 'private'.
+ * @param $cache_id
+ *
+ * @return Response
+ */
+ public function deliver(Request $request, $scheme, $cache_id) {
+ // Validate the request.
+ $css_js_query_string = \Drupal::state()->get('system.css_js_query_string');
+ if ($cache_id != $css_js_query_string) {
+ throw new NotFoundHttpException();
+ }
+
+ // Prepare the destination file URI.
+ $file_uri = $scheme . '://less/' . $cache_id . '/' . $request->query->get('file');
+
+ // Get the relative target path.
+ $target_path = LessEngineBase::uriToRelativePath($file_uri);
+
+ // Get the map of .less to .css files.
+ $map = \Drupal::state()->get('less_css_cache_files') ?: [];
+ $source_file_path = array_search($target_path, $map);
+ if (empty($source_file_path)) {
+ throw new NotFoundHttpException();
+ }
+
+ if ($compiled_file_info = _less_process_file($source_file_path, TRUE)) {
+ $response = new Response();
+ $response->headers->set('Content-Type', 'text/css');
+ $response->setContent($compiled_file_info['computed_style']);
+
+ return $response;
+ }
+
+ throw new NotFoundHttpException();
+ }
+
+}
diff --git a/src/Controller/LessWatchController.php b/src/Controller/LessWatchController.php
new file mode 100644
index 0000000..27de749
--- /dev/null
+++ b/src/Controller/LessWatchController.php
@@ -0,0 +1,134 @@
+engineManager = $engineManager;
+ $this->configFactory = $config_factory;
+ $this->request = $request;
+ $this->cache = $cache;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('plugin.manager.less_engine'),
+ $container->get('config.factory'),
+ $container->get('request_stack')->getCurrentRequest(),
+ $container->get('cache.render')
+ );
+ }
+
+ /**
+ * Hello.
+ *
+ * @return string
+ * Return Hello string.
+ */
+ public function watch() {
+ global $theme;
+
+ /** @var \Drupal\Core\Config\Config $config */
+ $config = $this->config('less.settings');
+
+ $changed_files = array();
+
+ if ($config->get('developer_options.watch_mode')) {
+
+ $files = $this->request->get('less_files', []);
+
+ foreach ($files as $file) {
+ $file_url_parts = UrlHelper::parse($file);
+
+ $cid = 'less:watch:' . Crypt::hashBase64($file_url_parts['path']);
+ if ($cache = $this->cache->get($cid)) {
+
+ $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 new JsonResponse($changed_files);
+ }
+
+}
diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php
new file mode 100644
index 0000000..95dcf78
--- /dev/null
+++ b/src/Form/SettingsForm.php
@@ -0,0 +1,203 @@
+engineManager = $engineManager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('config.factory'),
+ $container->get('plugin.manager.less_engine')
+ );
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'settings_form';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEditableConfigNames() {
+ return ['less.settings'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ /** @var \Drupal\Core\Config\Config $config */
+ $config = $this->config('less.settings');
+
+ $form['clear_cache'] = array(
+ '#type' => 'details',
+ '#title' => t('Clear cache'),
+ '#open' => TRUE,
+ );
+
+ // TODO: Only clear relevant caches.
+ $form['clear_cache']['clear'] = array(
+ '#type' => 'submit',
+ '#value' => t('Clear all caches'),
+ '#submit' => array('::submitCacheClear'),
+ );
+
+ $form['engine'] = [
+ '#type' => 'radios',
+ '#title' => $this->t('LESS engine'),
+ '#options' => [],
+ '#required' => TRUE,
+ '#default_value' => $config->get('engine'),
+ ];
+
+ foreach ($this->engineManager->getDefinitions() as $id => $definition) {
+ $form['engine']['#options'][$id] = $definition['title'];
+
+ $version = call_user_func([$definition['class'], 'getVersion']);
+ $title = $this->t('@title - :url', [
+ '@title' => $definition['title'],
+ ':url' => $definition['url']
+ ]);
+
+ $form['engine'][$id] = array(
+ '#type' => 'radio',
+ '#title' => $title,
+ '#return_value' => $id,
+ '#description' => $definition['description'],
+ '#disabled' => empty($version),
+ );
+
+ if (!empty($version)) {
+ $form['engine'][$id]['#description'] .= ' ' . t('Installed version: @version', array('@version' => $version)) . '';
+ }
+ }
+
+ $is_autoprefixer_installed = FALSE;
+ $form['autoprefixer'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Use @name - :url', array('@name' => 'Autoprefixer', ':url' => 'https://github.com/postcss/autoprefixer')),
+ '#description' => t('Enable automatic prefixing of vendor CSS extensions.'),
+ '#default_value' => $config->get('autoprefixer'),
+ '#disabled' => !$is_autoprefixer_installed,
+ );
+
+ $form['developer_options'] = array(
+ '#type' => 'fieldset',
+ '#title' => $this->t('Developer Options'),
+ '#tree' => TRUE,
+ );
+
+ $form['developer_options']['devel'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('LESS developer mode'),
+ '#description' => $this->t('Enable developer mode to ensure LESS files are regenerated every page load.'),
+ '#default_value' => $config->get('developer_options.devel'),
+ ];
+
+ $form['developer_options']['source_maps'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Source Maps'),
+ '#description' => $this->t('Enable source maps output while "Developer Mode" is enabled.'),
+ '#default_value' => $config->get('developer_options.source_maps'),
+ '#states' => array(
+ 'enabled' => array(
+ ':input[name="developer_options[devel]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ $form['developer_options']['watch_mode'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Watch Mode'),
+ '#description' => $this->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' => $config->get('developer_options.watch_mode'),
+ '#states' => array(
+ 'enabled' => array(
+ ':input[name="developer_options[devel]"]' => array('checked' => TRUE),
+ ),
+ ),
+ ];
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Submit'),
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ /** @var \Drupal\Core\Config\Config $config */
+ $config = $this->config('less.settings');
+
+ $config
+ ->set('engine', $form_state->getValue('engine'))
+ ->set('autoprefixer', $form_state->getValue('autoprefixer'))
+ ->set('developer_options.devel', $form_state->getValue(['developer_options', 'devel']))
+ ->set('developer_options.source_maps', $form_state->getValue(['developer_options', 'source_maps']))
+ ->set('developer_options.watch_mode', $form_state->getValue(['developer_options', 'watch_mode']))
+ ->save();
+
+ parent::submitForm($form, $form_state);
+ }
+
+ /**
+ * Clears the caches.
+ */
+ public function submitCacheClear(array &$form, FormStateInterface $form_state) {
+ // TODO: Only clear relevant caches.
+ drupal_flush_all_caches();
+ drupal_set_message(t('Caches cleared.'));
+ }
+
+}
diff --git a/src/LessCliWrapper.php b/src/LessCliWrapper.php
new file mode 100644
index 0000000..f828110
--- /dev/null
+++ b/src/LessCliWrapper.php
@@ -0,0 +1,289 @@
+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;
+ }
+}
diff --git a/src/PathProcessor/PathProcessorLessCachedFiles.php b/src/PathProcessor/PathProcessorLessCachedFiles.php
new file mode 100644
index 0000000..f5d432f
--- /dev/null
+++ b/src/PathProcessor/PathProcessorLessCachedFiles.php
@@ -0,0 +1,74 @@
+streamWrapperManager = $stream_wrapper_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function processInbound($path, Request $request) {
+ /** @noinspection PhpUndefinedMethodInspection */
+ $directory_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath();
+
+ if (strpos($path, '/' . $directory_path . '/less/') === 0) {
+ $path_prefix = '/' . $directory_path . '/less/';
+ $scheme = 'public';
+ }
+ elseif (strpos($path, '/system/files/less/') === 0) {
+ $path_prefix = '/system/files/less/';
+ $scheme = 'private';
+ }
+ else {
+ return $path;
+ }
+
+ // Strip out path prefix.
+ $rest = preg_replace('|^' . preg_quote($path_prefix, '|') . '|', '', $path);
+
+ // Provide the requested file path to:
+ // \Drupal\less\Controller\LessCachedFileDownloadController.
+ if (substr_count($rest, '/') >= 1) {
+ list($cache_id, $file) = explode('/', $rest, 2);
+
+ // Set the file as query parameter.
+ $request->query->set('file', $file);
+
+ return $path_prefix . $cache_id . '/' . $scheme;
+ }
+ else {
+ return $path;
+ }
+ }
+
+}
diff --git a/src/Plugin/LessEngine/LeafoLessphp.php b/src/Plugin/LessEngine/LeafoLessphp.php
new file mode 100644
index 0000000..a338241
--- /dev/null
+++ b/src/Plugin/LessEngine/LeafoLessphp.php
@@ -0,0 +1,70 @@
+import_directories as $directory) {
+ $parser->addImportDir($directory);
+ }
+
+ $parser->setVariables($this->variables);
+
+ $cache = $parser->cachedCompile($this->configuration['source_path']);
+
+ $this->dependencies = array_keys($cache['files']);
+
+ $compiled_styles = $cache['compiled'];
+ }
+ catch (\Exception $e) {
+ throw $e;
+ }
+
+ return $compiled_styles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ static public function getVersion() {
+ if (isset(\lessc::$VERSION)) {
+ return \lessc::$VERSION;
+ }
+
+ return NULL;
+ }
+}
diff --git a/src/Plugin/LessEngine/LessLessJs.php b/src/Plugin/LessEngine/LessLessJs.php
new file mode 100644
index 0000000..5715b3b
--- /dev/null
+++ b/src/Plugin/LessEngine/LessLessJs.php
@@ -0,0 +1,83 @@
+dependencies = $this->parser->depends();
+
+ return parent::getDependencies();
+ }
+
+ /**
+ * This compiles using engine specific function calls.
+ *
+ * {@inheritdoc}
+ */
+ public function compile() {
+ $compiled_styles = NULL;
+
+ $parser = LessCliWrapper::create($this->configuration['source_path']);
+ try {
+ $parser->source_maps($this->source_maps_enabled, $this->source_maps_base_path, $this->source_maps_root_path);
+
+ foreach ($this->import_directories as $directory) {
+ $parser->include_path($directory);
+ }
+
+ foreach ($this->variables as $var_name => $var_value) {
+ $parser->modify_var(trim($var_name, '@'), trim($var_value, ';'));
+ }
+
+ $compiled_styles = $parser->compile();
+ }
+ catch (\Exception $e) {
+ throw $e;
+ }
+
+ return $compiled_styles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ static public function getVersion() {
+ return LessCliWrapper::version();
+ }
+}
diff --git a/src/Plugin/LessEngine/OyejorgeLessPhp.php b/src/Plugin/LessEngine/OyejorgeLessPhp.php
new file mode 100644
index 0000000..d292cb2
--- /dev/null
+++ b/src/Plugin/LessEngine/OyejorgeLessPhp.php
@@ -0,0 +1,79 @@
+source_maps_enabled) {
+
+ $parser->SetOption('sourceMap', $this->source_maps_enabled);
+
+ $parser->SetOption('sourceMapBasepath', $this->source_maps_base_path);
+ $parser->SetOption('sourceMapRootpath', $this->source_maps_root_path);
+ }
+
+ // Less.js does not allow path aliasing. Set aliases to blank for consistency.
+ $parser->SetImportDirs(array_fill_keys($this->import_directories, ''));
+
+ $parser->parseFile($this->configuration['source_path']);
+
+ $parser->ModifyVars($this->variables);
+
+ $compiled_styles = $parser->getCss();
+
+ $this->dependencies = $parser->AllParsedFiles();
+ }
+ catch (\Exception $e) {
+ throw $e;
+ }
+
+ return $compiled_styles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ static public function getVersion() {
+ if (class_exists('\Less_Version')) {
+ return \Less_Version::version;
+ }
+
+ return NULL;
+ }
+
+}
diff --git a/src/Plugin/LessEngineBase.php b/src/Plugin/LessEngineBase.php
new file mode 100644
index 0000000..932d890
--- /dev/null
+++ b/src/Plugin/LessEngineBase.php
@@ -0,0 +1,147 @@
+ 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;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setSource($path) {
+ if (empty($path)) {
+ throw new \InvalidArgumentException('No source file path given.');
+ }
+
+ if (!file_exists($path)) {
+ throw new \InvalidArgumentException('Source file does not exist.');
+ }
+
+ // Set the source file path.
+ $this->configuration['source_path'] = $path;
+
+ // Prepare a relative directory path for the destination file.
+ $destination_sub_path = substr($path, 0, -5); // Remove the '.less' extension.
+ if (substr($destination_sub_path, -4) == '.css') {
+ $destination_sub_path = substr($destination_sub_path, 0, -4); // Remove '.css' extension if it exists.
+ }
+
+ // Create full path to the destination file.
+ $cache_id = \Drupal::state()->get('system.css_js_query_string');
+ $destination_uri = 'public://less/' . $cache_id . '/' . $destination_sub_path . '.css';
+ $this->configuration['destination_uri'] = $destination_uri;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getDestinationUri() {
+ return $this->configuration['destination_uri'];
+ }
+
+ /**
+ * Gets the file to the compiled file as required by \Drupal\Core\Asset\AssetResolver.
+ *
+ * @see \Drupal\Core\Asset\AssetResolver
+ *
+ * @inheritdoc
+ */
+ static public function uriToRelativePath($uri) {
+ $file_url = file_create_url($uri);
+ $compiled_file_relative_url = file_url_transform_relative($file_url);
+ return ltrim($compiled_file_relative_url, '/');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destinationExists() {
+ /** @var \Drupal\Core\File\FileSystemInterface $fileSystem */
+ $fileSystem = \Drupal::service('file_system');
+
+ if (file_exists($fileSystem->realpath($this->configuration['destination_uri']))) {
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * {@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;
+ }
+}
diff --git a/src/Plugin/LessEngineInterface.php b/src/Plugin/LessEngineInterface.php
new file mode 100644
index 0000000..6fbfc03
--- /dev/null
+++ b/src/Plugin/LessEngineInterface.php
@@ -0,0 +1,112 @@
+alterInfo('less_less_engine_info');
+ $this->setCacheBackend($cache_backend, 'less_less_engine_plugins');
+ $this->config = $config_factory->get('less.settings');
+ }
+
+ /**
+ * @return \Drupal\less\Plugin\LessEngineInterface
+ */
+ public function createEngine() {
+ $plugin_id = $this->config->get('engine');
+
+ /** @var \Drupal\less\Plugin\LessEngineInterface $engine */
+ $engine = $this->createInstance($plugin_id);
+
+ return $engine;
+ }
+
+}
diff --git a/src/Routing/LessCachedFileRoutes.php b/src/Routing/LessCachedFileRoutes.php
new file mode 100644
index 0000000..03d443a
--- /dev/null
+++ b/src/Routing/LessCachedFileRoutes.php
@@ -0,0 +1,65 @@
+streamWrapperManager = $stream_wrapper_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('stream_wrapper_manager')
+ );
+ }
+
+ /**
+ * Returns an array of route objects.
+ *
+ * @return \Symfony\Component\Routing\Route[]
+ * An array of route objects.
+ */
+ public function routes() {
+ $routes = array();
+
+ $directory_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath();
+
+ // If clean URLs are enabled and the compiled file already exists, PHP will be bypassed.
+ $routes['less.cached_file_public'] = new Route(
+ '/' . $directory_path . '/less/{cache_id}/{scheme}',
+ array(
+ '_controller' => '\Drupal\less\Controller\LessCachedFileDownloadController::deliver',
+ ),
+ array(
+ '_access' => 'TRUE',
+ )
+ );
+ return $routes;
+ }
+
+}
diff --git a/styles/less.theme.css b/styles/less.theme.css
deleted file mode 100644
index 5588d69..0000000
--- a/styles/less.theme.css
+++ /dev/null
@@ -1,3 +0,0 @@
-fieldset.vertical-tabs-pane fieldset.less-theme-settings legend {
- display: none;
-}