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; -}