adaptivetheme-8.x-3.x-dev/at_core/src/Layout/LayoutSubmit.php
at_core/src/Layout/LayoutSubmit.php
<?php
namespace Drupal\at_core\Layout;
use Drupal\at_core\File\FileOperations;
use Drupal\at_core\File\DirectoryOperations;
use Drupal\Component\Utility\Unicode;
use Symfony\Component\Yaml\Parser;
use Drupal\Component\Utility\Html;
use Drupal\at_core\Theme\ThemeInfo;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Extension\ThemeExtensionList;
/**
*
*/
class LayoutSubmit {
/**
* The layout configuration.
*
* @var array
*/
public $layoutConfig;
/**
* The CSS configuration.
*
* @var array
*/
public $cssConfig;
/**
* The layout name.
*
* @var string
*/
public $layoutName;
/**
* The layout path.
*
* @var string
*/
public $layoutPath;
/**
* The form values.
*
* @var array
*/
public $formValues;
/**
* The active theme name.
*/
protected $theme_name;
/**
* Form state values.
*/
protected $values;
/**
* Constructor.
*/
public function __construct($theme_name, $values) {
$this->theme_name = $theme_name;
$layout_data = new LayoutCompatible($this->theme_name);
$layout_compatible_data = $layout_data->getCompatibleLayout();
$this->layout_config = $layout_compatible_data['layout_config'];
$this->css_config = $layout_compatible_data['css_config'];
$this->layout_name = $layout_compatible_data['layout_name'];
$this->layout_path = \Drupal::service('extension.list.theme')->getPath($this->layout_config['layout_provider']) . '/layout/' . $this->layout_name;
$this->form_values = $values;
}
/**
* Convert the table row array to settings the rest of the system can use.
*/
public function convertLayoutSettings() {
$settings = [];
foreach ($this->form_values['table_layout_settings'] as $row_key => $row_values) {
foreach ($row_values['layout'] as $setting_layout => $layout) {
$settings[$setting_layout] = $layout;
}
foreach ($row_values['weight'] as $setting_weight => $weight) {
$settings[$setting_weight] = $weight;
}
foreach ($row_values['hide'] as $setting_hide => $hide) {
$settings[$setting_hide] = $hide;
}
}
return $settings;
}
/**
* Build and save the suggestions layout css files.
*/
public function saveLayoutSuggestionsCSS() {
$breakpoints_group = \Drupal::service('breakpoint.manager')->getBreakpointsByGroup($this->form_values['settings_breakpoint_group_layout']);
$generated_files_path = $this->form_values['settings_generated_files_path'];
$css_data = [];
foreach ($this->form_values['settings_suggestions'] as $suggestion_key => $suggestions_name) {
foreach ($breakpoints_group as $breakpoint_id => $breakpoint_value) {
foreach ($this->layout_config['rows'] as $row_key => $row_values) {
// Match the key set in the form, hacking on get label.
$breakpoint_layout_key = strtolower(preg_replace("/\W|_/", "", $breakpoint_value->getLabel()));
$css_data[$suggestion_key][$breakpoint_layout_key]['query'] = $breakpoint_value->getMediaQuery();
// Layout with impossible BC.
if (!empty($this->form_values['settings_' . $suggestion_key . '_' . $breakpoint_layout_key . '_' . $row_key])) {
$css_data[$suggestion_key][$breakpoint_layout_key]['rows'][$row_key] = $this->form_values['settings_' . $suggestion_key . '_' . $breakpoint_layout_key . '_' . $row_key];
}
elseif (!empty($this->form_values['table_layout_settings'][$row_key]['layout']['settings_' . $suggestion_key . '_' . $breakpoint_layout_key . '_' . $row_key])) {
$css_data[$suggestion_key][$breakpoint_layout_key]['rows'][$row_key]['layout'] = $this->form_values['table_layout_settings'][$row_key]['layout']['settings_' . $suggestion_key . '_' . $breakpoint_layout_key . '_' . $row_key];
}
else {
$css_data[$suggestion_key][$breakpoint_layout_key]['rows'][$row_key]['layout'] = 'not_set';
}
// Row order (weight).
if (!empty($this->form_values['table_layout_settings'][$row_key]['weight']['settings_' . $suggestion_key . '_' . $breakpoint_layout_key . '_' . $row_key . '_weight'])) {
$css_data[$suggestion_key][$breakpoint_layout_key]['rows'][$row_key]['weight'] = $this->form_values['table_layout_settings'][$row_key]['weight']['settings_' . $suggestion_key . '_' . $breakpoint_layout_key . '_' . $row_key . '_weight'];
}
else {
$css_data[$suggestion_key][$breakpoint_layout_key]['rows'][$row_key]['weight'] = 1;
}
// Row hidden (hide).
if (!empty($this->form_values['table_layout_settings'][$row_key]['hide']['settings_' . $suggestion_key . '_' . $breakpoint_layout_key . '_' . $row_key . '_hide'])) {
$css_data[$suggestion_key][$breakpoint_layout_key]['rows'][$row_key]['hide'] = $this->form_values['table_layout_settings'][$row_key]['hide']['settings_' . $suggestion_key . '_' . $breakpoint_layout_key . '_' . $row_key . '_hide'];
}
else {
$css_data[$suggestion_key][$breakpoint_layout_key]['rows'][$row_key]['hide'] = 0;
}
}
}
}
// Initialize or set vars.
$output = [];
$css_rows = [];
$css_file = [];
$row_hide_css = [];
$row_weight_css = [];
$path_to_css_files = $this->layout_path . '/' . $this->css_config['css_files_path'];
foreach ($css_data as $suggestion => $breakpoints) {
foreach ($breakpoints as $breakpoint_keys => $breakpoint_values) {
foreach ($breakpoint_values['rows'] as $row_keys => $row_values) {
$row_key = str_replace('_', '-', $row_keys);
if (isset($row_values['hide']) && $row_values['hide'] == 1) {
$row_hide_css[$suggestion][$breakpoint_keys][$row_keys] = '.l-' . $row_key . ' {display: none;}';
}
else {
if (isset($row_values['weight'])) {
$row_weight_css[$suggestion][$breakpoint_keys][$row_keys] = '.l-' . $row_key . ' { -webkit-order: ' . $row_values['weight'] . '; -ms-flex-order: ' . $row_values['weight'] . '; order: ' . $row_values['weight'] . "; }";
}
foreach ($this->css_config['css'] as $css_key => $css_values) {
if (file_exists($path_to_css_files . '/' . $css_key . '/' . $row_values['layout'] . '.css')) {
$css_file[$suggestion][$breakpoint_keys][$row_keys] = file_get_contents($path_to_css_files . '/' . $css_key . '/' . $row_values['layout'] . '.css');
// TODO review fix for underscores in row names.
$replace_class = 'pr-' . $row_key;
if (!empty($css_file[$suggestion][$breakpoint_keys][$row_keys])) {
$file = str_replace($row_values['layout'], $replace_class, $css_file[$suggestion][$breakpoint_keys][$row_keys]);
$css_rows[$suggestion][$breakpoint_keys][$breakpoint_keys . '_' . $row_keys] = $file;
}
}
}
}
}
$output[$suggestion][] = "/* Begin breakpoint: $breakpoint_keys */\n" . '@media ' . $breakpoint_values['query'] . " {\n";
if (!empty($row_hide_css[$suggestion][$breakpoint_keys])) {
$output[$suggestion][] = implode("\n", $row_hide_css[$suggestion][$breakpoint_keys]) . "\n";
}
if (!empty($row_weight_css[$suggestion][$breakpoint_keys])) {
$output[$suggestion][] = implode("\n", $row_weight_css[$suggestion][$breakpoint_keys]) . "\n";
}
if (!empty($css_rows[$suggestion][$breakpoint_keys])) {
$output[$suggestion][] = implode("\n", $css_rows[$suggestion][$breakpoint_keys]);
}
$output[$suggestion][] = "}\n/* End breakpoint */\n";
}
}
// Get the layouts global CSS if any.
$global_css = '';
if ($this->css_config['css_global_layout']) {
$global_css = file_get_contents($path_to_css_files . '/' . $this->css_config['css_global_layout']);
}
// Max widths.
$max_width = [];
if (isset($this->form_values['settings_max_width_enable']) && $this->form_values['settings_max_width_enable'] === 1) {
$max_width_value = Html::escape($this->form_values['settings_max_width_value']);
$max_width['global'] = '.l-rw { max-width: ' . trim($max_width_value) . $this->form_values['settings_max_width_unit'] . '; }';
// Per row.
if (isset($this->form_values['settings_max_width_enable_rows']) && $this->form_values['settings_max_width_enable_rows'] === 1) {
foreach ($this->layout_config['rows'] as $row_key => $row_values) {
if (isset($this->form_values['settings_max_width_value_' . $row_key]) && !empty($this->form_values['settings_max_width_value_' . $row_key])) {
$max_width_rows[$row_key]['value'] = trim($this->form_values['settings_max_width_value_' . $row_key]);
$max_width_rows[$row_key]['unit'] = trim($this->form_values['settings_max_width_unit_' . $row_key]);
$max_width[$row_key] = '.pr-' . str_replace('_', '-', $row_key) . '__rw { max-width: ' . $max_width_rows[$row_key]['value'] . $max_width_rows[$row_key]['unit'] . '; }';
}
}
}
}
// Attribution row order, we need this for BC.
$attribution_order = '.l-attribution { -webkit-order: 100; -ms-flex-order: 100; order: 100 ;}';
// Don't regenerate CSS files to be removed.
foreach ($this->form_values as $values_key => $values_value) {
if (substr($values_key, 0, 18) === 'delete_suggestion_') {
if ($values_value === 1) {
$delete_suggestion_keys[] = mb_substr($values_key, 18);
/*$delete_suggestion_keys[] = Unicode::substr($values_key, 18);*/
}
}
}
if (!empty($delete_suggestion_keys)) {
foreach ($delete_suggestion_keys as $template_to_remove) {
unset($output[$template_to_remove]);
}
}
$saved_css = [];
foreach ($output as $suggestion => $css) {
if (!empty($css)) {
$message = '/* Layout CSS for: ' . str_replace('_', '-', $suggestion) . '.html.twig, generated: ' . date(DATE_RFC822) . ' */';
$file_content = $message . "\n\n" . $global_css . "\n" . implode("\n", $css) . "\n" . implode("\n", $max_width) . "\n" . $attribution_order . "\n";
$file_name = $this->theme_name . '.layout.' . str_replace('_', '-', $suggestion) . '.css';
$filepath = "$generated_files_path/$file_name";
/*file_unmanaged_save_data($file_content, $filepath, FILE_EXISTS_REPLACE);*/
\Drupal::service('file_system')->saveData($file_content, $filepath, FileSystemInterface::EXISTS_REPLACE);
if (file_exists($filepath)) {
$saved_css[] = $file_name;
}
}
}
if (!empty($saved_css)) {
$saved_css_message_list = [
'#theme' => 'item_list',
'#items' => $saved_css,
];
\Drupal::messenger()->addMessage(t('The following <b>CSS</b> files were generated in: <code>@generated_files_path</code> @saved_css', [
'@saved_css' => \Drupal::service('renderer')->renderPlain($saved_css_message_list),
'@generated_files_path' => $generated_files_path . '/'
]
), 'status');
}
}
/**
* Update the themes info file with new regions.
*/
public function saveLayoutRegions() {
$regions = [];
foreach ($this->layout_config['rows'] as $row => $row_values) {
foreach ($row_values['regions'] as $region_key => $region_values) {
if (isset($region_values['label'])) {
$regions[$region_key] = $region_values['label'];
}
// BC.
else {
$regions[$region_key] = $region_values;
}
}
}
$regions['page_top'] = 'Page top';
$regions['page_bottom'] = 'Page bottom';
// Get the paths to this theme and all dependant skin themes.
$theme_info_data = new ThemeInfo($this->theme_name);
$theme_paths[$this->theme_name] = $theme_info_data->getThemeInfo()->getPath();
$sub_themes_info = $theme_info_data->getSubThemesInfo();
if (!empty($sub_themes_info)) {
$sub_theme_paths = $theme_info_data->getSubThemesPaths();
foreach ($sub_themes_info as $machine_name => $sub_theme) {
if (isset($sub_theme['subtheme type']) && $sub_theme['subtheme type'] == 'adaptive_skin') {
$theme_paths = array_merge($theme_paths, $sub_theme_paths);
}
}
}
// Todo - move to method?
// Create a backup.
// if ($this->form_values['settings_enable_backups'] == 1) {
//
// $fileOperations = new FileOperations();
// $directoryOperations = new DirectoryOperations();
//
// $backup_path = $directoryOperations->directoryPrepare($backup_file_path = [$path, 'backup', 'info']);
//
// // Add a date time string to make unique and for easy identification,
// // save as .txt to avoid conflicts.
// $backup_file = $info_file . '.'. date(DATE_ISO8601) . '.txt';
//
// $file_paths = [
// 'copy_source' => $file_path,
// 'copy_dest' => $backup_path . '/' . $info_file,
// 'rename_oldname' => $backup_path . '/' . $info_file,
// 'rename_newname' => $backup_path . '/' . $backup_file,
// ];
// $fileOperations->fileCopyRename($file_paths);
// }
// Parse, format and save info with new regions.
$parser = new Parser();
$buildInfo = new FileOperations();
foreach ($theme_paths as $theme_name => $path) {
$file_path = $path . '/' . $theme_name . '.info.yml';
$theme_info_data = $parser->parse(file_get_contents($file_path));
$theme_info_data['regions'] = $regions;
$rebuilt_info = $buildInfo->fileBuildInfoYml($theme_info_data);
/*file_unmanaged_save_data($rebuilt_info, $file_path, FILE_EXISTS_REPLACE);*/
\Drupal::service('file_system')->saveData($rebuilt_info, $file_path, FileSystemInterface::EXISTS_REPLACE);
}
}
/**
* Build and save twig templates.
* Save each suggestion template, these are saved every time the layout
* settings are saved because the rows and regions might change, so we re-save
* every template, every time the form is submitted.
*/
public function saveLayoutSuggestionsMarkup() {
$template_suggestions = [];
$fileOperations = new FileOperations();
$directoryOperations = new DirectoryOperations();
if (!empty($this->form_values['settings_suggestions'])) {
$template_suggestions = $this->form_values['settings_suggestions'];
}
if (!empty($this->form_values['ts_name'])) {
$template_suggestions['page__' . $this->form_values['ts_name']] = 'page__' . $this->form_values['ts_name'];
}
// Don't regenerate templates to be deleted.
foreach ($this->form_values as $values_key => $values_value) {
if (substr($values_key, 0, 18) === 'delete_suggestion_') {
if ($values_value === 1) {
/*$delete_suggestion_keys[] = Unicode::substr($values_key, 18);*/
$delete_suggestion_keys[] = mb_substr($values_key, 18);
}
}
}
if (!empty($delete_suggestion_keys)) {
foreach ($delete_suggestion_keys as $template_to_remove) {
unset($template_suggestions[$template_to_remove]);
}
}
// Template path.
$template_file = $this->layout_path . '/' . $this->layout_name . '.html.twig';
// Path to target theme where the template will be saved.
$path = \Drupal::service('extension.list.theme')->getPath($this->theme_name);
// Remove if this exists, its now deprecated, this is a BC layer so to speak.
$directoryOperations->directoryRemove($path . '/templates/page');
$template_directory = $path . '/templates/generated';
// Check and create the templates directory if does not exist.
if (!file_exists($path . '/templates')) {
\Drupal::service('file_system')->mkdir($path . '/templates');
}
if (!file_exists($template_directory)) {
\Drupal::service('file_system')->mkdir($template_directory);
}
// Initialize vars.
$row_regions = [];
$templates = [];
$saved_templates = [];
// We have to save every template every time, in case a row has been added to the layout, all template MUST update.
// This could be changed later to only do this IF a row has been added, we're not that flash right now :)
foreach ($template_suggestions as $suggestion_key => $suggestions_name) {
$output = [];
$suggestion_key = Html::escape($suggestion_key);
// Doc block.
$doc = [];
$doc[$suggestion_key][] = '{#';
$doc[$suggestion_key][] = '/**';
$doc[$suggestion_key][] = ' * Layout provider: ' . $this->layout_name;
$doc[$suggestion_key][] = ' * Template suggestion: ' . $suggestion_key;
$doc[$suggestion_key][] = ' * Theme: ' . $this->theme_name;
$doc[$suggestion_key][] = ' * Generated on: ' . date(DATE_RFC822);
$doc[$suggestion_key][] = ' */';
$doc[$suggestion_key][] = '#}' . "\n";
$docblock[$suggestion_key] = implode("\n", $doc[$suggestion_key]);
// Attach the layout library.
$generated_files_path = $this->form_values['settings_generated_files_path'];
$layout_file = $this->theme_name . '.layout.' . str_replace('_', '-', $suggestion_key) . '.css';
if (file_exists($generated_files_path . '/' . $layout_file)) {
$library = $this->theme_name . '/' . $this->theme_name . '.layout.' . $suggestion_key;
}
else {
$library = $this->theme_name . '/' . $this->theme_name . '.layout.page';
}
$attach_layout = "{{ attach_library('$library') }}";
// Get the template file, if not found attempt to generate the template.
if (file_exists($template_file)) {
$template = file_get_contents($template_file);
}
else {
$generated[$suggestion_key][] = '<div{{ attributes }}>' . "\n";
$generated[$suggestion_key][] = ' {{ rows }}' . "\n";
$generated[$suggestion_key][] = " {{ attribution }}" . "\n";
$generated[$suggestion_key][] = '</div>' . "\n";
$template[$suggestion_key] = implode($generated[$suggestion_key]);
}
// Prepend the doc block and attached layout to the template markup.
$template_markup[$suggestion_key] = $docblock[$suggestion_key] . $attach_layout . "\n" . $template[$suggestion_key];
// Set the template file name, either it's page or a page suggestion.
if ($suggestion_key !== 'page') {
$template_file = str_replace('_', '-', $suggestion_key) . '.html.twig';
}
else {
$template_file = 'page.html.twig';
}
// Set the template path.
$template_path = $template_directory . '/' . $template_file;
// Build array of files to be saved.
$templates[$suggestion_key]['markup'] = $template_markup[$suggestion_key];
$templates[$suggestion_key]['template_path'] = $template_path;
$templates[$suggestion_key]['template_name'] = $template_file;
// Create a backup.
if ($this->form_values['settings_enable_backups'] == 1) {
$backup_path = $directoryOperations->directoryPrepare($backup_file_path = [$path, 'backup', 'templates']);
// Add a date time string to make unique and for easy identification,
// save as .txt to avoid conflicts.
$date = new DateTime();
$backup_file = $template_file . '.' . $date->format('Y-m-d\TH:i:sO') . '.txt';
$file_paths = [
'copy_source' => $template_path,
'copy_dest' => $backup_path . '/' . $template_file,
'rename_oldname' => $backup_path . '/' . $template_file,
'rename_newname' => $backup_path . '/' . $backup_file,
];
$fileOperations->fileCopyRename($file_paths);
}
}
foreach ($templates as $suggestion => $template_values) {
if (!file_exists($templates[$suggestion]['template_path'])) {
$new_template = $templates[$suggestion]['template_name'];
$new_template_message = t('It looks like you generated a new template: <b>@new_template</b>. Save the layout settings again so they will take effect.', ['@new_template' => $new_template]);
}
/*file_unmanaged_save_data($templates[$suggestion]['markup'], $templates[$suggestion]['template_path'], FILE_EXISTS_REPLACE);*/
\Drupal::service('file_system')->saveData($templates[$suggestion]['markup'], $templates[$suggestion]['template_path'], FileSystemInterface::EXISTS_REPLACE);
if (file_exists($templates[$suggestion]['template_path'])) {
$saved_templates[] = $templates[$suggestion]['template_name'];
}
}
if (!empty($saved_templates)) {
$saved_templates_message_list = [
'#theme' => 'item_list',
'#items' => $saved_templates,
];
\Drupal::messenger()->addMessage(t('The following <b>templates</b> were generated in: <code>@template_directory</code> @saved_templates', [
'@saved_templates' => \Drupal::service('renderer')->renderPlain($saved_templates_message_list),
'@template_directory' => $template_directory . '/'
]
), 'status');
}
if (isset($new_template_message)) {
\Drupal::messenger()->addMessage($new_template_message, 'status');
}
}
}
