Skip to content
Snippets Groups Projects
Commit a78f3af9 authored by Tim Bozeman's avatar Tim Bozeman Committed by Tim Bozeman
Browse files

Issue #3281243 by Tim Bozeman: Handle recreation of assets if they do not...

Issue #3281243 by Tim Bozeman: Handle recreation of assets if they do not exist (deployment to new environment)
parent f499ccf7
No related branches found
No related tags found
No related merge requests found
......@@ -10,6 +10,7 @@ declare(strict_types=1);
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Serialization\Yaml;
use Drupal\component_library\Entity\ComponentLibraryAsset;
use Drupal\component_library\Entity\ComponentLibraryPattern;
use Drupal\component_library\Form\ComponentOverrideForm;
use Drupal\Core\Asset\AttachedAssetsInterface;
......@@ -23,17 +24,16 @@ use Drupal\Core\Url;
*/
function component_library_library_info_build(): array {
$libraries = [];
\Drupal::service('component_library.asset')->processAllAssetPaths(static function (array $path_info) use (&$libraries): void {
$type = $path_info['type'];
$path = $path_info['path'];
$id = $path_info['id'];
if ($type === 'css') {
$libraries["component_library.$id"][$type]['theme'][$path] = [];
}
else {
$libraries["component_library.$id"][$type][$path] = [];
foreach (ComponentLibraryAsset::loadMultiple() as $asset) {
foreach ($asset->getFiles() as $file) {
if ($file['type'] === 'css') {
$libraries["component_library.{$asset->id()}"][$file['type']]['theme'][$file['path']] = [];
}
else {
$libraries["component_library.{$asset->id()}"][$file['type']][$file['path']] = [];
}
}
});
}
return $libraries;
}
......@@ -42,15 +42,15 @@ function component_library_library_info_build(): array {
* Implements hook_css_alter().
*/
function component_library_css_alter(&$css, AttachedAssetsInterface $assets): void {
\Drupal::service('component_library.asset')->processAllAssetPaths(static function (array $path_info) use (&$css): void {
$path = $path_info['path'];
$path = ltrim($path, '/');
if ($path_info['type'] === 'css' && !empty($css[$path])) {
// Ensure our css overrides are loaded after the theme.
$css[$path]['group'] = CSS_AGGREGATE_THEME;
$css[$path]['weight'] = 999_999_999_999;
foreach (ComponentLibraryAsset::loadMultiple() as $asset) {
foreach ($asset->getFiles() as $file) {
if ($file['type'] === 'css' && !empty($css[$file['path']])) {
// Ensure our css overrides are loaded after the theme.
$css[$file['path']]['group'] = CSS_AGGREGATE_THEME;
$css[$file['path']]['weight'] = 999_999_999_999;
}
}
});
}
}
/**
......
......@@ -8,6 +8,41 @@ declare(strict_types=1);
*/
use Drupal\Core\Database\Database;
use Drupal\component_library\Entity\ComponentLibraryAsset;
/**
* Migrate assets to new files data model.
*/
function component_library_post_update_asset_files(&$sandbox): void {
foreach (ComponentLibraryAsset::loadMultiple() as $id => $asset) {
$files = [];
if (!empty($asset->css)) {
_migrate_files_to_new_data_model($asset->css, $files, $id, 'css');
}
if (!empty($asset->js)) {
_migrate_files_to_new_data_model($asset->js, $files, $id, 'js');
}
$asset->setFiles($files);
$files_with_paths = $asset->getFiles();
$asset->setFiles($files_with_paths);
$asset->save();
}
}
/**
* Helper to migrate asset files data model.
*/
function _migrate_files_to_new_data_model(array $old_files, array &$new_files, string $id, $type) {
$count = 0;
foreach ($old_files as $file) {
$new_files[] = [
'name' => \sprintf('%s%s', str_replace('_', '-', $id), $count > 0 ? '-' . $count : ''),
'type' => $type,
'code' => $file['code'],
];
$count++;
}
}
/**
* Rename entity prefix for pattens and variations.
......
......@@ -26,7 +26,12 @@ services:
- { name: event_subscriber }
component_library.asset:
class: Drupal\component_library\Asset
arguments: []
arguments: ['@file_system', '@library.discovery','@config.factory']
component_library.asset.generate_files:
arguments: ['@config.factory', '@component_library.asset']
class: Drupal\component_library\EventSubscriber\GenerateAssetFiles
tags:
- { name: event_subscriber }
component_library.override_mode:
class: Drupal\component_library\OverrideMode
arguments:
......
......@@ -77,7 +77,7 @@ component_library.override.*:
type: string
label: Library
component_library.asset.*:
component_library.component_library_asset.*:
type: config_entity
label: Component Library Asset
mapping:
......@@ -92,9 +92,9 @@ component_library.asset.*:
theme:
type: string
label: Theme
css:
files:
type: sequence
label: CSS
label: Files
sequence:
mapping:
name:
......@@ -103,17 +103,12 @@ component_library.asset.*:
code:
type: string
label: Code
js:
type: sequence
label: Javascript
sequence:
mapping:
name:
type:
type: string
label: Name
code:
label: Type
path:
type: string
label: Code
label: path
component_library.override_mode.settings:
type: config_object
......
......@@ -4,33 +4,66 @@ declare(strict_types=1);
namespace Drupal\component_library;
use Drupal\Core\Cache\Cache;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Asset\LibraryDiscoveryInterface;
use Drupal\component_library\Entity\ComponentLibraryAsset;
/**
* Asset file handler service.
*/
final class Asset {
public function processAllAssetPaths(callable $callback, $url = TRUE): void {
$assets = ComponentLibraryAsset::loadMultiple();
foreach ($assets as $asset) {
$paths = $asset->get('paths');
foreach ($paths as $path_info) {
$path_info['id'] = $asset->id();
if ($url) {
$path_info['path'] = $this->fileUrlGenerator($path_info['path']);
}
\call_user_func($callback, $path_info);
}
}
private FileSystemInterface $fileSystem;
private ConfigFactoryInterface $configFactory;
private LibraryDiscoveryInterface $libraryDiscovery;
public function __construct(FileSystemInterface $file_system, LibraryDiscoveryInterface $library_discovery, ConfigFactoryInterface $config_factory) {
$this->fileSystem = $file_system;
$this->configFactory = $config_factory;
$this->libraryDiscovery = $library_discovery;
}
private function fileUrlGenerator($file_uri): string {
if (\version_compare(\Drupal::VERSION, '9.3', '>=')) {
// phpcs:ignore DrupalPractice.Objects.GlobalDrupal.GlobalDrupal
return \Drupal::service('file_url_generator')->generateString($file_uri);
// phpcs:enable
/**
* Generate library files.
*
* Writes the asset files to the public files dir.
*
* @param \Drupal\component_library\Entity\ComponentLibraryAsset $asset
* The asset that needs files generated.
*/
public function generateLibraryFiles(ComponentLibraryAsset $asset) {
foreach ($asset->getFiles() as $file) {
$directory = \dirname($file['path']);
$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
$this->fileSystem->saveData(
$file['code'],
$file['path'],
FileSystemInterface::EXISTS_REPLACE
);
}
else {
return \file_create_url($file_uri);
$this->libraryDiscovery->clearCachedDefinitions();
}
/**
* Clear files.
*
* Deletes existing asset files with or without aggregation enabled.
*
* @param \Drupal\component_library\Entity\ComponentLibraryAsset $asset
* The component_library_asset to clean up files for.
*/
public function clearFiles(ComponentLibraryAsset $asset): void {
Cache::invalidateTags(['library_info']);
foreach (['js', 'css'] as $asset_type) {
$aggregation_enabled = $this->configFactory->get('system.performance')->get("$asset_type.preprocess");
if ($aggregation_enabled) {
$this->fileSystem->deleteRecursive('public://' . $asset_type . '/');
}
}
$path = 'public://component_library_assets/' . $asset->id();
$this->fileSystem->deleteRecursive($path);
}
}
......@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Drupal\component_library\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
......@@ -47,9 +46,7 @@ use Drupal\Core\Entity\EntityStorageInterface;
* "id",
* "label",
* "theme",
* "css",
* "js",
* "paths",
* "files",
* }
* )
*/
......@@ -71,47 +68,46 @@ final class ComponentLibraryAsset extends ConfigEntityBase {
protected string $theme;
/**
* The css code of the library.
* An array of file attributes with the following elements:
* - 'code': The JS or CSS file contents.
* - 'name': The file name (without extension).
* - 'type': The file extension.
* - 'path': The path to the generated file.
*/
protected array $css = [];
/**
* The javascript code of the library.
*/
protected array $js = [];
/**
* The library files that were generated.
*/
protected array $paths = [];
protected array $files = [];
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities): void {
/** @var \Drupal\component_library\Entity\ComponentLibraryAsset $entity */
foreach ($entities as $entity) {
self::clearFiles($entity);
$asset_service = \Drupal::service('component_library.asset');
foreach ($entities as $asset) {
$asset_service->clearFiles($asset);
}
}
/**
* Clears existing library files and ensures working if aggregation is active.
* Get files.
*
* @param \Drupal\component_library\Entity\ComponentLibraryAsset $asset
* The component_library_asset to clean up files for.
* @return array
* An array of files and their attributes.
*/
public static function clearFiles(ComponentLibraryAsset $asset): void {
Cache::invalidateTags(['library_info']);
$file_system = \Drupal::service('file_system');
foreach (['js', 'css'] as $asset_type) {
$aggregation_enabled = \Drupal::config('system.performance')->get($asset_type . '.preprocess');
if ($aggregation_enabled) {
$file_system->deleteRecursive('public://' . $asset_type . '/');
}
public function getFiles(): array {
foreach ($this->files as &$file) {
$file['path'] = sprintf('public://component_library_assets/%s/%s/%s.%s', $this->id(), $file['type'], $file['name'], $file['type']);
}
$path = 'public://component_library_assets/' . $asset->get('id');
$file_system->deleteRecursive($path);
return $this->files;
}
/**
* Set files.
*
* @param array $files
* An array of files and their attributes.
*/
public function setFiles(array $files): void {
$this->files = $files;
}
}
<?php
declare(strict_types=1);
namespace Drupal\component_library\EventSubscriber;
use Drupal\component_library\Asset;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigImporterEvent;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\component_library\Entity\ComponentLibraryAsset;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Regenerates library assets on config import.
*/
final class GenerateAssetFiles implements EventSubscriberInterface {
const CONFIG_NAMESPACE = 'component_library.component_library_asset';
private Asset $asset;
private ConfigFactoryInterface $configFactory;
public function __construct(ConfigFactoryInterface $config_factory, Asset $asset) {
$this->configFactory = $config_factory;
$this->asset = $asset;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events = [];
$events[ConfigEvents::SAVE][] = ['onConfigSave'];
$events[ConfigEvents::IMPORT][] = ['onConfigImport'];
return $events;
}
/**
* The config save event listener.
*
* @param \Drupal\Core\Config\ConfigCrudEvent $event
* The config crud event.
*/
public function onConfigSave(ConfigCrudEvent $event): void {
$config = $event->getConfig();
if ($this->isAssetConfig($config->getName())) {
$this->generateFiles($config->getRawData()['id']);
}
}
/**
* Generates library asset files on config import.
*
* @param \Drupal\Core\Config\ConfigImporterEvent $event
* The config import event.
*/
public function onConfigImport(ConfigImporterEvent $event): void {
$changes = $event->getChangelist();
$configs = \array_merge($changes['create'], $changes['update'], $changes['rename']);
foreach ($configs as $config_name) {
if ($this->isAssetConfig($config_name)) {
$this->generateFiles($this->configFactory->get($config_name)->getRawData()['id']);
}
}
}
/**
* Generate CSS and JS files.
*
* @param string $config_name
* The ComponentLibraryAsset ID.
*/
private function generateFiles(string $config_name): void {
$asset = ComponentLibraryAsset::load($config_name);
$this->asset->clearFiles($asset);
$this->asset->generateLibraryFiles($asset);
}
/**
* Is not asset config.
*
* @param string $config_name
* The config name.
*
* @return bool
* Whether or not the config is a ComponentLibraryAsset.
*/
private function isAssetConfig(string $config_name): bool {
return \str_starts_with($config_name, self::CONFIG_NAMESPACE);
}
}
......@@ -21,6 +21,7 @@ final class IgnoreTemplate implements EventSubscriberInterface {
private array $ignoredOriginalThemeHooks = [
'views_view_field',
'rdf_metadata',
'region',
'toolbar',
'html',
];
......
......@@ -4,15 +4,10 @@ declare(strict_types=1);
namespace Drupal\component_library\Form;
use Drupal\component_library\Entity\ComponentLibraryAsset;
use Drupal\Core\Asset\LibraryDiscoveryInterface;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -20,35 +15,16 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
final class ComponentLibraryAssetForm extends EntityForm {
/**
* The theme handler.
*/
protected ThemeHandlerInterface $themeHandler;
/**
* The Drupal file system.
*/
protected FileSystemInterface $fileSystem;
/**
* The library discovery.
*/
protected LibraryDiscoveryInterface $libraryDiscovery;
/**
* Constructs a new ComponentLibraryAssetForm.
*
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The Drupal file system.
* @param \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery
* The library discovery.
*/
public function __construct(ThemeHandlerInterface $theme_handler, FileSystemInterface $file_system, LibraryDiscoveryInterface $library_discovery) {
public function __construct(ThemeHandlerInterface $theme_handler) {
$this->themeHandler = $theme_handler;
$this->fileSystem = $file_system;
$this->libraryDiscovery = $library_discovery;
}
/**
......@@ -57,16 +33,14 @@ final class ComponentLibraryAssetForm extends EntityForm {
public static function create(ContainerInterface $container): self {
return new self(
$container->get('theme_handler'),
$container->get('file_system'),
$container->get('library.discovery')
);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state): array {
$form = parent::form($form, $form_state);
public function buildForm(array $form, FormStateInterface $form_state): array {
$form = parent::buildForm($form, $form_state);
$form['label'] = [
'#type' => 'textfield',
......@@ -93,67 +67,155 @@ final class ComponentLibraryAssetForm extends EntityForm {
'#type' => 'select',
'#title' => $this->t('Theme'),
'#empty_option' => $this->t('- Select -'),
'#default_value' => $this->entity->get('theme') ?? $form_state->getValue('theme') ?? '',
'#default_value' => $form_state->getValue('theme') ?? $this->entity->get('theme') ?? '',
'#options' => $themes,
'#required' => TRUE,
];
if (!$form_state->get('library_files')) {
$form_state->set('library_files', [
'css' => $this->getFilesDefaultValue('css', $form_state),
'js' => $this->getFilesDefaultValue('js', $form_state),
]);
}
foreach (\array_keys($form_state->get('library_files')) as $asset_type) {
$default_files = $form_state->get('library_files')[$asset_type];
$this->addMultiValueField($form, $asset_type, $default_files);
$form['files'] = [
'#type' => 'fieldset',
'#tree' => TRUE,
'#title' => $this->t('Files'),
'#attributes' => ['id' => 'asset-files'],
];
$files = $this->getFiles($form_state);
if ($files) {
$delta = 0;
$asset_type_count = [
'css' => 0,
'js' => 0,
];
foreach ($files as $file) {
// Keep track of how many of each type of file we have.
// The first js or css file name will be the same as the Asset ID. Then
// the files will be numbered sequentially if there are multiple of the
// same type. e.g. script.js, script-1.js.
$count = &$asset_type_count[$file['type']];
$name = \sprintf('%s%s', \str_replace('_', '-', $this->entity->id()), $count > 0 ? '-' . $count : '');
$filename = \sprintf('%s.%s', $name, $file['type']);
$form['files'][$delta] = [
'name' => [
'#type' => 'value',
'#value' => $name,
],
'type' => [
'#type' => 'value',
'#value' => $file['type'],
],
'code' => [
'#type' => 'codemirror',
'#codemirror' => [
'autoCloseTags' => TRUE,
'buttons' => [
'undo',
'redo',
'enlarge',
'shrink',
],
'foldGutter' => TRUE,
'lineNumbers' => TRUE,
'lineWrapping' => TRUE,
'styleActiveLine' => TRUE,
'toolbar' => TRUE,
'lineSeparator' => "\n",
'mode' => $file['type'] == 'js' ? 'javascript' : $file['type'],
],
'#title' => $filename,
'#default_value' => $file['code'],
],
'delete' => [
'#tree' => FALSE,
'#type' => 'submit',
'#value' => $this->t('Delete @filename', ['@filename' => $filename]),
'#name' => "delete::$delta",
'#submit' => [[$this, 'deleteFile']],
'#ajax' => [
'callback' => [$this, 'fileAjax'],
'wrapper' => 'asset-files',
'effect' => 'fade',
],
'#attributes' => [
'class' => [
'button',
'button--danger',
],
],
'#delta' => $delta,
],
];
// Reset the codemirror value when a file is deleted.
[$delete] = explode('::', $form_state->getUserInput()['_triggering_element_name'] ?? '');
if ($delete === 'delete') {
$form['files'][$delta]['code']['#value'] = $file['code'];
}
$delta++;
$count++;
}
}
$form['actions']['add_css'] = [
'#type' => 'submit',
'#value' => $this->t('Add CSS'),
'#name' => 'add_css',
'#submit' => [[$this, 'addFile']],
'#ajax' => [
'callback' => [$this, 'fileAjax'],
'wrapper' => 'asset-files',
'effect' => 'fade',
],
'#asset_type' => 'css',
'#weight' => 6,
];
$form['actions']['add_js'] = [
'#type' => 'submit',
'#value' => $this->t('Add JS'),
'#name' => 'add_js',
'#submit' => [[$this, 'addFile']],
'#ajax' => [
'callback' => [$this, 'fileAjax'],
'wrapper' => 'asset-files',
'effect' => 'fade',
],
'#asset_type' => 'js',
'#weight' => 7,
];
$form['#attached']['library'][] = 'codemirror_editor/editor';
return $form;
}
/**
* AJAX callback to add/remove a multivalue element on the CSS fieldset.
* Submit button handler.
*/
public function ajaxCssFiles(array $form, FormStateInterface $form_state): array {
return $form['css'];
public function addFile(array $form, FormStateInterface $form_state) {
$files = $this->getFiles($form_state);
$files[] = [
'type' => $form_state->getTriggeringElement()['#asset_type'],
];
$this->setFiles($files, $form_state);
}
/**
* AJAX callback to add/remove a multivalue element on the JS fieldset.
* Submit button handler.
*/
public function ajaxJsFiles(array $form, FormStateInterface $form_state): array {
return $form['js'];
public function deleteFile(array $form, FormStateInterface $form_state) {
$files = $this->getFiles($form_state);
unset($files[$form_state->getTriggeringElement()['#delta']]);
$this->setFiles($files, $form_state);
}
/**
* {@inheritdoc}
* AJAX callback to add/remove a file.
*/
public function validateForm(array &$form, FormStateInterface $form_state): void {
$library_files = $form_state->get('library_files');
$trigger = $form_state->getTriggeringElement();
if (isset($trigger['#name']) && \strpos($trigger['#name'], 'add_button') !== FALSE) {
$asset_type = \substr($trigger['#name'], \strlen('add_button_'));
$library_files[$asset_type][] = ['name' => '', 'code' => ''];
$form_state->set('files_altered', [$asset_type => TRUE]);
}
if (isset($trigger['#parents'][1]) && isset($trigger['#name']) && \strpos($trigger['#name'], 'remove_button') !== FALSE) {
$asset_type = \substr($trigger['#name'], \strlen('remove_button_' . $trigger['#parents'][1]) + 1);
\array_splice($library_files[$asset_type], $trigger['#parents'][1], 1);
$form_state->set('files_altered', [$asset_type => TRUE]);
}
$form_state->set('library_files', $library_files);
public function fileAjax(array $form, FormStateInterface $form_state): array {
return $form['files'];
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
// Re-create the files on disk and make sure they are read when aggregation
// is enabled.
ComponentLibraryAsset::clearFiles($this->entity);
$this->generateLibraryFiles($form_state);
$result = parent::save($form, $form_state);
$message_args = ['%label' => $this->entity->label()];
......@@ -166,168 +228,37 @@ final class ComponentLibraryAssetForm extends EntityForm {
}
/**
* {@inheritdoc}
*/
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state): void {
foreach (\array_keys($form_state->get('library_files')) as $asset_type) {
$value = \array_filter($form_state->getValue($asset_type), static fn ($element) => !($element instanceof TranslatableMarkup) && !empty($element['name']) && !empty($element['code']));
$form_state->setValue($asset_type, $value);
}
parent::copyFormValuesToEntity($entity, $form, $form_state);
}
/**
* Determines default value for the given multivalue field.
* Get files.
*
* @param string $asset_type
* The file type/field to get the default value for.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state to pull values from if file values were already changed.
* The form state.
*
* @return array
* Array of names and codes for the given file type/field.
* An array of file values.
*/
private function getFilesDefaultValue($asset_type, FormStateInterface $form_state): array {
return empty($form_state->get('files_altered')[$asset_type])
? $this->getEntity()->get($asset_type)
: $form_state->getValue($asset_type);
private function getFiles(FormStateInterface $form_state) {
return $form_state->getValue('files') ?? $this->entity->getFiles();
}
/**
* Adds multivalue field consisting of name and code subfields to given form.
*
* @param array $form
* The form to add the fields to.
* @param string $asset_type
* The asset type of the field, either css or js.
* @param array $default_files
* The default values for the fields.
*/
private function addMultiValueField(array &$form, $asset_type, array $default_files): void {
$ajax_callback = 'ajax' . \ucfirst($asset_type) . 'Files';
$form[$asset_type] = [
'#type' => 'details',
'#open' => TRUE,
'#tree' => TRUE,
'#title' => $this->t('@asset_type Files', ['@asset_type' => \strtoupper($asset_type)]),
'#prefix' => '<div id="' . $asset_type . '-container">',
'#suffix' => '</div>',
'add_button_' . $asset_type => [
'#type' => 'submit',
'#value' => $this->t('Add file'),
'#name' => 'add_button_' . $asset_type,
'#weight' => 999,
'#limit_validation_errors' => TRUE,
'#executes_submit_callback' => FALSE,
'#ajax' => [
'callback' => [$this, $ajax_callback],
'wrapper' => $asset_type . '-container',
'effect' => 'fade',
],
],
];
foreach ($default_files as $delta => $values) {
$form[$asset_type][$delta] = [
'#type' => 'container',
];
$form[$asset_type][$delta]['name'] = [
'#type' => 'machine_name',
'#title' => $this->t('Library Name'),
'#maxlength' => 255,
'#default_value' => $values['name'],
'#description' => $this->t('Machine name for the library asset file.'),
'#machine_name' => [
'exists' => [$this, 'fileExists'],
],
];
$form[$asset_type][$delta]['code'] = [
'#type' => 'codemirror',
'#codemirror' => [
'autoCloseTags' => TRUE,
'buttons' => [
'undo',
'redo',
'enlarge',
'shrink',
],
'foldGutter' => TRUE,
'lineNumbers' => TRUE,
'lineWrapping' => TRUE,
'styleActiveLine' => TRUE,
'toolbar' => TRUE,
'lineSeparator' => "\n",
'mode' => $asset_type == 'js' ? 'javascript' : $asset_type,
],
'#title' => $this->t('Code'),
'#default_value' => $values['code'],
];
$form[$asset_type][$delta]['remove_button'] = [
'#type' => 'submit',
'#value' => $this->t('Remove file'),
'#name' => 'remove_button_' . $delta . '_' . $asset_type,
'#limit_validation_errors' => TRUE,
'#executes_submit_callback' => FALSE,
'#ajax' => [
'callback' => [$this, $ajax_callback],
'wrapper' => $asset_type . '-container',
'effect' => 'fade',
],
];
}
}
/**
* Writes the code saved in the css and js fields into the public files dir.
* Set files.
*
* @param array $files
* An array of file values.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state after submission of the form.
* The form state.
*/
private function generateLibraryFiles(FormStateInterface $form_state): void {
$paths = [];
foreach (\array_keys($form_state->get('library_files')) as $asset_type) {
$dir = 'public://component_library_assets/' . $form_state->getValue('id') . '/' . $asset_type;
$this->fileSystem->prepareDirectory($dir, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
foreach ($form_state->getValue($asset_type) as $library_file) {
$file_name = $library_file['name'];
$file_path = "$dir/$file_name.$asset_type";
$path = $this->fileSystem->saveData(
$library_file['code'],
$file_path,
FileSystemInterface::EXISTS_REPLACE
);
$paths[] = [
'type' => $asset_type,
'path' => $path,
];
}
private function setFiles(array $files, FormStateInterface $form_state) {
if (count($files) > 1) {
\ksort($files);
}
$this->getEntity()->set('paths', $paths);
$this->libraryDiscovery->clearCachedDefinitions();
}
/**
* Machine name exists callback.
*
* Ensure that within the same library we don't duplicate file names.
*/
public function fileExists($entity_id, array $element, FormStateInterface $form_state) {
$files = $this->getFilesDefaultValue($element['#parents'][0], $form_state);
if ($files) {
$count = 0;
foreach($files as $file) {
if (!empty($file['name']) && $file['name'] === $entity_id) {
$count++;
}
}
if ($count > 1) {
return TRUE;
}
else {
// If ksort doesn't run, lets still set the delta to 0.
$key = \array_key_first($files);
$files = [$files[$key]];
}
return FALSE;
$form_state->setValue('files', $files);
$form_state->setRebuild();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment