bootstrap_five_layouts-1.0.x-dev/src/Plugin/Layout/BootstrapFiveLayoutsBase.php

src/Plugin/Layout/BootstrapFiveLayoutsBase.php
<?php

namespace Drupal\bootstrap_five_layouts\Plugin\Layout;

use Drupal\bootstrap_five_layouts\Traits\BytesFormatTrait;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Layout\LayoutDefault;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\bootstrap_five_layouts\BootstrapFiveLayoutsManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;

/**
 * Layout class for all bootstrap layouts.
 */
abstract class BootstrapFiveLayoutsBase extends LayoutDefault implements PluginFormInterface, ContainerFactoryPluginInterface {
  use BytesFormatTrait;
  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The module handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The Bootstrap Layouts manager service.
   *
   * @var \Drupal\bootstrap_five_layouts\BootstrapFiveLayoutsManager
   */
  protected $bootstrapLayoutsManager;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, BootstrapFiveLayoutsManager $bootstrap_five_layouts_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->configFactory = $config_factory;
    $this->moduleHandler = $module_handler;
    $this->bootstrapLayoutsManager = $bootstrap_five_layouts_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('config.factory'),
      $container->get('module_handler'),
      $container->get('plugin.manager.bootstrap_five_layouts')
    );
  }

  /**
   * Provides a default Container definition.
   *
   * @return array
   *   Default region array.
   */
  protected function getContainerDefaults() {
    return [
      'container_type' => 'no-container',
      'classes' => '',
      'container_visibility' => '',
      'container_theme' => '',
      'backgound_image' => NULL,
    ];
  }
  /**
   * Provides a default region definition.
   *
   * @return array
   *   Default region array.
   */
  protected function getRegionDefaults() {
    return [
      'wrapper' => 'div',
      'classes' => [],
      'flex' => [],
      'offset' => [],
      'visibility' => [],
      'flex-shrink' => [],
      'flex-grow' => [],
      'theme' => '',
      'custom' => '',
      'attributes' => '',
      'id' => '',
      'add_region_classes' => TRUE,
      'add_base_col' => TRUE,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    $configuration = parent::defaultConfiguration();
    $configuration += [
      'container' => [
        'container_type' => 'no-container',
        'classes' => '',
        'container_theme' => '',
        'container_visibility' => '',
        'background_image' => 0,
      ],
      'row' => [
        'wrapper' => 'div',
        'columns' => [],
        'theme' => '',
        'custom' => '',
        'classes_margin' => [],
        'classes_margin_x' => [],
        'classes_margin_y' => ['my-2'],
        'classes_margin_top' => [],
        'classes_margin_end' => [],
        'classes_margin_bottom' => [],
        'classes_margin_start' => [],
        'classes_padding' => [],
        'classes_padding_x' => [],
        'classes_padding_y' => ['py-2'],
        'classes_padding_top' => [],
        'classes_padding_end' => [],
        'classes_padding_bottom' => [],
        'classes_padding_start' => [],
        'classes_text_align' => [],
        'alignment' => [],
        'align_self' => [],
        'attributes' => '',
        'id' => '',
        'add_row_classes' => TRUE,
      ],
      'regions' => [],
    ];
    foreach ($this->getPluginDefinition()->getRegions() as $region => $info) {
      $region_configuration = [];
      foreach (['wrapper', 'classes', 'attributes'] as $key) {
        if (isset($info[$key])) {
          $region_configuration[$key] = $info[$key];
        }
      }
      $configuration['regions'][$region] = $region_configuration + $this->getRegionDefaults();
    }
    return $configuration;
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    // Ensure core attribute/label is included.
    $form = parent::buildConfigurationForm($form, $form_state);

    // This can potentially be invoked within a subform instead of a normal
    // form. There is an ongoing discussion around this which could result in
    // the passed form state going back to a full form state. In order to
    // prevent BC breaks, check which type of FormStateInterface has been
    // passed and act accordingly.
    // @see https://www.drupal.org/node/2868254
    // @todo Re-evaluate once https://www.drupal.org/node/2798261 makes it in.
    $complete_form_state = $form_state instanceof SubformStateInterface ? $form_state->getCompleteFormState() : $form_state;
    $configuration = $this->getConfiguration();

    /** @var \Drupal\bootstrap_five_layouts\BootstrapFiveLayoutsManager $manager */
    $manager = $this->bootstrapLayoutsManager;

    $theme_options = $manager->getThemeOptions();
    $has_theme_options = count($theme_options)>1  ? TRUE : FALSE;

    $primaryOptions = $manager->getPrimaryOptions();

    // preflight helpful descriptions
    $id_description = '<ul>
      <li>' . $this->t("Use lower-case, class safe phrase.  You can use dashes (yes).   NO:  underscore, spaces, special characters allowed."). '</li>
      <li>' . $this->t("Ensure ID's entered unique across sections on same layout page."). '</li>
      <li>' . $this->t('Can be used for page hash linking') . '</li>
    </ul>';
    $attr_description = '<ul>
      <li>' . $this->t('E.g. role|navigation,data-bs-something|some value'). '</li>
      <li>' . $this->t('Do not enter Class or ID here.') . '</li>
    </ul>';

    $pills_description = '<ul class="pills-description"><li>'.$this->t('Seperate by spaces.').'</li>'.
    '<li>'.$this->t('Do not start names with period (.) and should start with a letter.').'</li>'.
    '</ul>';

    // Token Integration
    $tokens = FALSE;
    if ($this->moduleHandler->moduleExists('token')) {
      $tokens = [
        '#title' => $this->t('Tokens'),
        '#type' => 'container',
      ];
      $tokens['help'] = [
        '#theme' => 'token_tree_link',
        '#token_types' => 'all',
        '#global_types' => FALSE,
        '#dialog' => TRUE,
      ];
    }

    // Wrapper Options.
    $wrapper_options = [
      'div' => 'Div',
      'span' => 'Span',
      'section' => 'Section',
      'article' => 'Article',
      'header' => 'Header',
      'footer' => 'Footer',
      'aside' => 'Aside',
      'figure' => 'Figure',
    ];

    // Expose classlist map to JS via drupalSettings. (setups cache)
    $classlist_map = $manager->getAllKnownClassnames();
    $form['#attached']['drupalSettings']['bootstrap_five_layouts']['classlist_map'] = $classlist_map;

    $pillbox_classes = $manager->getPillboxClasses();
    $form['#attached']['drupalSettings']['bootstrap_five_layouts']['pillbox_classes'] = $pillbox_classes;

    // use cache info with nesting.
    $ulility_divisions = $manager->getUtilityDivisions();

    // Expose description helper selector to JS via drupalSettings.
    $config = $this->configFactory->get('bootstrap_five_layouts.settings');
    $form['#attached']['drupalSettings']['bootstrap_five_layouts']['description_helper_selector'] = $config->get('description_helper_selector') ?: '.form-item__description';

    // Add related JS/CSS tools.
    $form['#attached']['library'][] = 'bootstrap_five_layouts/layout_admin';
    $form['#attached']['library'][] = 'bootstrap_five_layouts/multiselect';
    $form['#attached']['library'][] = 'bootstrap_five_layouts/descriptionHelper';
    $form['#attached']['library'][] = 'bootstrap_five_layouts/pillbox';
    $form['#attached']['library'][] = 'bootstrap_five_layouts/pillbox_counter';

    // Add a wrapping close open for css.
    $form['open-class'] = [
      '#type' => '#markup',
      '#markup' => '<div class="bootstrap-five-layouts-settings-tray-admin">',
      '#weight' => -50,
    ];

    $form['container'] = [
      '#title' => $this->t('Container Options'),
      '#type' => 'details',
      '#tree' => TRUE,
      '#open' => (bool) $this->configFactory->get('bootstrap_five_layouts.settings')->get('container_appearence'),
    ];
    $form['container']['container_type'] = [
      '#title' => $this->t('Container Type'),
      '#type' => 'select',
      '#tree' => TRUE,
      '#options' => $manager->getContainerOptions(),
      '#default_value' => $complete_form_state->getValue(['container', 'container_type'], $configuration['container']['container_type']),
      '#weight' => -50,
    ];
    $form['container']['classes'] = [
      '#type' => 'textfield',
      '#maxlength' => $this->configFactory->get('bootstrap_five_layouts.settings')->get('custom_maxlength'),
      '#title' => $this->t('Container Custom Classes'),
      '#description' => $pills_description,
      '#default_value' => $complete_form_state->getValue(['container', 'classes'], $configuration['container']['classes']),
       '#attributes' => [
        'data-bsfl-pillbox' => 'true',
        'data-pillbox-counter' => 'true',
      ],
      '#weight' => -49
    ];
    $form['container']['container_theme'] = [
      '#access' => 'false',
      '#type' => 'select',
      '#title' => $this->t('Container Theme'),
      '#access' => $has_theme_options,
      '#options' => $theme_options,
      '#default_value' => $complete_form_state->getValue(['container', 'container_theme'], $configuration['container']['container_theme']),
      '#multiple' => FALSE,
      '#weight' => -48
    ];

     $form['container']['container_visibility'] = [
      '#type' => 'select',
      '#title' => $this->t('Container Visibility'),
      '#options' => $primaryOptions['visibility']['field']['#options'],
      '#default_value' => $complete_form_state->getValue(['container', 'container_visibility'], $configuration['container']['container_visibility']),
      '#description_display' => 'after',
      '#description' => $primaryOptions['visibility']['field']['#description'],
      '#multiple' => TRUE,
      '#size' => 12,
      '#attributes' => [
        'data-multiselect-enhanced' => 'true',
        'data-multiselect-multiple' => 'true',
      ],
    ];

    $enable_background = $this->configFactory->get('bootstrap_five_layouts.settings')->get('enable_background');
    $form['container']['background'] = [
      '#title' => $this->t('Background'),
      '#type' => 'container',
      '#access' => $enable_background
    ];
    // $default = $complete_form_state->getValue(['container', 'background_image'], $configuration['container']['background_image']);
    // $form['container']['background']['image'] = $this->chooseFileType($default, $folder);
    // $form['container']['background']['image']['#access'] = false;

    $col_ulility_divisions = $ulility_divisions['col'];
    $this->addFormZoneInstances($form, $complete_form_state, $col_ulility_divisions, $this->t('Container'), 'container');

    $form['row'] = [
      '#title' => $this->t('Row Options'),
      '#type' => 'details',
      '#tree' => TRUE,
      '#open' => (bool) $this->configFactory->get('bootstrap_five_layouts.settings')->get('row_appearence'),
    ];
    $form['row']['theme'] = [
      '#type' => 'select',
      '#title' => $this->t('Row Theme'),
      '#access' => $has_theme_options,
      '#options' => $theme_options,
      '#default_value' => $complete_form_state->getValue(['row', 'theme'], $configuration['row']['theme']),
      '#multiple' => FALSE,
      '#weight' => -50,
    ];
    $form['row']['wrapper'] = [
      '#type' => 'select',
      '#title' => $this->t('Row Semantic/HTML Tag'),
      '#options' => $wrapper_options,
      '#default_value' => $complete_form_state->getValue(['row', 'wrapper'], $configuration['row']['wrapper']),
       '#weight' => -49,
   ];
    $form['row']['id'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Row ID'),
      '#description' => $id_description,
      '#default_value' => $complete_form_state->getValue(['row', 'id'], $configuration['row']['id']),
      '#weight' => -48,

   ];
   $form['row']['custom'] = [
      '#type' => 'textfield',
      '#maxlength' => $this->configFactory->get('bootstrap_five_layouts.settings')->get('custom_maxlength'),
      '#title' => $this->t('Row Custom Classes'),
      '#default_value' => $complete_form_state->getValue(['row', 'custom'], $configuration['row']['custom']),
      '#description' => $pills_description,
       '#attributes' => [
        'data-bsfl-pillbox' => 'true',
        'data-pillbox-counter' => 'true',
      ],
      '#weight' => -47,
    ];
    // Reminder:  row-cols is handled seperatly from other utility_options items due to it's singular context.
    $base_options = $this->configFactory->get('bootstrap_five_layouts.settings')->get('base_options');
    $row_cols_conf = $base_options['row-cols'] ?? [];
    $row_cols_options =  $manager->processUtilityOption($row_cols_conf);
    $form['row']['columns'] = [
      '#type' => 'select',
      '#title' => $this->t('Row Column Options'),
      '#options' =>$row_cols_options,
      '#default_value' => $complete_form_state->getValue(['row', 'columns'], $configuration['row']['columns']),
      '#multiple' => TRUE,
      '#size' => 12,
      '#attributes' => [
        'data-multiselect-enhanced' => 'true',
        'data-multiselect-single-group' => 'true',
      ],
      '#weight' => -46,
    ];

    $row_ulility_divisions = $ulility_divisions['row'];
    $this->addFormZoneInstances($form, $complete_form_state, $row_ulility_divisions, $this->t('Row'), 'row');

    $form['row']['advanced'] = [
      '#group' => 'advanced',
      '#type' => 'details',
      '#tree' => TRUE,
      '#open' => false,
      '#title' => $this->t('Advanced Row'),
      '#weight' => 20,
    ];
    $form['row']['advanced']['add_row_classes'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Add row layout specific class: <code>@class</code>', ['@class' => Html::cleanCssIdentifier('row-'.$this->getPluginId())]),
      '#default_value' => (int) $complete_form_state->getValue(['row', 'advanced', 'add_row_classes'], $configuration['row']['add_row_classes']),
    ];
    $form['row']['advanced']['attributes'] = [
      '#type' => 'textarea',
      '#rows' => 2,
      '#title' => $this->t('Additional Row Attributes'),
      '#description' => $attr_description,
      '#default_value' => $complete_form_state->getValue(['row', 'advanced', 'attributes'], $configuration['row']['attributes']),
    ];
    if ($tokens) {
      $form['row']['tokens'] = $tokens;
    }

    // Columns begin
    $regions = $this->getPluginDefinition()->getRegions();
    $column_count = is_countable($regions) ? count($regions) : 0;
    $label = $this->t('Columns');
    if ($column_count>0){
      switch ($column_count) {
        case 1:
          $label = $this->t('1 Column – Full Width');
          break;
        case 2:
          $label = $this->t('2 Columns – Split');
          break;
        case 3:
          $label = $this->t('3 Columns – Balanced');
          break;
        case 4:
          $label = $this->t('4 Columns – Grid');
          break;
        case 6:
          $label = $this->t('6 Columns – Equal Width Grid');
          break;
        case 12:
          $label = $this->t('12 Columns – Bootstrap Full Grid');
          break;
        default:
          $label = $this->t('@count Columns – Custom Grid', ['@count' => $column_count]);
          break;
      }
      $form['region-label'] = [
        '#markup' => '<h3>' . $label . '</h3>'
      ];
    }

    // Add each region's settings.
    foreach ($regions as $region => $region_info) {
      $region_label = $region_info['label'];
      $default_values = NestedArray::mergeDeep(
        $this->getRegionDefaults(),
        isset($configuration['regions'][$region]) ? $configuration['regions'][$region] : [],
        $complete_form_state->getValue(['regions', $region], [])
      );
      // Single Column,
      if ($region=='column'){
       $title = $region_label;
      } else {
       $title = $this->t('Column: @region', ['@region' => $region_label]);
      }

      $form[$region] = [
        '#group' => 'additional_settings',
        '#type' => 'details',
        '#tree' => TRUE,
        '#open' => (bool) $this->configFactory->get('bootstrap_five_layouts.settings')->get('column_appearence'),
        '#title' => $title,
        '#weight' => 20,
      ];
      $form[$region]['theme'] = [
        '#type' => 'select',
        '#title' => $this->t('<q>@region</q> Theme', ['@region' => $region_label]),
        '#access' => $has_theme_options,
        '#options' => $theme_options,
        '#default_value' => $default_values['theme'] ?? [],
        '#multiple' => FALSE,
        '#weight' => -50,
      ];
      $form[$region]['wrapper'] = [
        '#type' => 'select',
        '#title' => $this->t('<q>@region</q> Semantic/HTML Tag', ['@region' => $region_label]),
        '#options' => $wrapper_options,
        '#default_value' => $default_values['wrapper'],
        '#weight' => -49,
      ];
      $form[$region]['id'] = [
        '#type' => 'textfield',
        '#title' => $this->t('<q>@region</q> ID', ['@region' => $region_label]),
        '#description' => $id_description,
        '#default_value' => $default_values['id'] ?? '',
        '#weight' => -48,
      ];

   $pills_description = '<ul class="pills-description"><li>'.$this->t('Seperate by spaces.').'</li>'.
   '<li>'.$this->t('Class names should start with a letter.  Also, do not start names with period (.) and should start with a letter.').'</li></ul>';

      $form[$region]['custom'] = [
        '#type' => 'textfield',
        '#maxlength' => $this->configFactory->get('bootstrap_five_layouts.settings')->get('custom_maxlength'),
        '#title' => $this->t('<q>@region</q> Custom Classes', ['@region' => $region_label]),
        '#default_value' => $default_values['custom'],
        '#description' => $pills_description,
        '#attributes' => [
          'data-bsfl-pillbox' => 'true',
          'data-pillbox-counter' => 'true',
        ],
        '#weight' => -47,
      ];
      $form[$region]['add_base_col'] = [
        '#type' => 'checkbox',
        '#title' => $this->t('Add <q>col</q> class for @region Column', ['@region' => $region_label]),
        '#default_value' => (int) $default_values['add_base_col'],
        '#description' => $this->t('Adds the basic Bootstrap column class when checked.'),
         '#weight' => -46,
      ];
      // Reminder:  col handling seperatly from othe rutility_options items due to it's singular context.
      $base_options = $this->configFactory->get('bootstrap_five_layouts.settings')->get('base_options');
      $cols_conf = $base_options['col'] ?? [];
      $col_options =  $manager->processUtilityOption($cols_conf);
      $form[$region]['classes'] = [
        '#type' => 'select',
        '#title' => $this->t('<q>@region</q> Responsive Columns', ['@region' => $region_label]),
        '#options' =>  $col_options,
        '#default_value' => $default_values['classes'],
        '#multiple' => TRUE,
        '#size' => 12,
        '#attributes' => [
          'data-multiselect-enhanced' => 'true',
          'data-multiselect-single-group' => 'true',
           'placeholder' => $this->t('Select responsive column details'),

        ],
        '#weight' => -45,
      ];

      $flexOptions = NestedArray::mergeDeep(
        $primaryOptions['flex-shrink']['field']['#options'],
        $primaryOptions['flex-grow']['field']['#options']
      );
      $desc = '<ul>'.
        '<li>'.$primaryOptions['flex-grow']['field']['#description'].'</li>'.
        '<li>'. $primaryOptions['flex-shrink']['field']['#description'].'</li>'
      .'</ul>';

     $form[$region]['flex'] = [
        '#type' => 'select',
        '#title' => $this->t('<q>@region</q> Column Flex (shrink/grow)', ['@region' => $region_label]),
        '#options' =>  $flexOptions,
        '#default_value' => $default_values['flex'],
        '#description_display' => 'after',
        '#description' => $desc,
        '#multiple' => TRUE,
       '#weight' => -44,
       '#size' => 12,
        '#attributes' => [
          'data-multiselect-enhanced' => 'true',
          'data-multiselect-single-group' => 'true',
        ],
     ];
     $form[$region]['offset'] = [
        '#type' => 'select',
        '#title' => $this->t('<q>@region</q> Offset', ['@region' => $region_label]),
        '#options' =>   $primaryOptions['offset']['field']['#options'],
        '#default_value' => $default_values['offset'],
        '#description_display' => 'after',
        '#description' => $primaryOptions['offset']['field']['#description'],
        '#multiple' => TRUE,
        '#size' => 12,
        '#attributes' => [
          'data-multiselect-enhanced' => 'true',
          'data-multiselect-single-group' => 'true',
        ],
     ];
     $form[$region]['visibility'] = [
        '#type' => 'select',
        '#title' => $this->t('<q>@region</q> Visibility', ['@region' => $region_label]),
        '#options' =>   $primaryOptions['visibility']['field']['#options'],
        '#default_value' => $default_values['visibility'],
        '#description_display' => 'after',
        '#description' => $primaryOptions['offset']['field']['#description'],
        '#multiple' => TRUE,
        '#size' => 12,
        '#attributes' => [
          'data-multiselect-enhanced' => 'true',
          'data-multiselect-multiple' => 'true',
        ],
     ];

    $col_ulility_divisions = $ulility_divisions['col'];
    $this->addFormColumnInstances($form, $default_values, $col_ulility_divisions, $region_label, $region);

      $form[$region]['advanced'] = [
        '#group' => 'advanced',
        '#type' => 'details',
        '#tree' => TRUE,
        '#open' => false,
        '#title' => $this->t('Advanced: @region', ['@region' => $region_label]),
        '#weight' => 20,
      ];
      $form[$region]['advanced']['add_region_classes'] = [
        '#type' => 'checkbox',
        '#title' => $this->t('Add region specific classes for @region:  <code>@layout-layout</code> and <code>@layout-region-@region</code>', [
          '@region' => Html::cleanCssIdentifier($region),
          '@layout' => Html::cleanCssIdentifier(mb_strtolower($this->getPluginId())),
        ]),
        '#default_value' => (int) $default_values['add_region_classes'],
      ];
      $form[$region]['advanced']['attributes'] = [
        '#type' => 'textarea',
        '#rows' => 2,
        '#title' => $this->t('Additional <q>@region</q> Attributes', ['@region' => $region_label]),
        '#description' => $attr_description,
        '#default_value' => $default_values['attributes'],
      ];
      if ($tokens) {
        $form[$region]['tokens'] = $tokens;
      }
    }

    // Add a wrapping close tag for css
    $form['close-class'] = [
      '#type' => '#markup',
      '#markup' => '</div">',
      '#weight' => 50,
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function setConfiguration(array $configuration) {
    // Don't use NestedArray::mergeDeep here since this will merge both the
    // default classes and the classes stored in config.
    $default = $this->defaultConfiguration();

    // Ensure top level properties exist.
    $configuration += $default;

    // Ensure specific top level sub-properties exists.
    $configuration['container'] += $default['container'];
    $configuration['row'] += $default['row'];

    // For regions, merge properly to preserve preset classes from plugin definition.
    $regions = $this->getPluginDefinition()->getRegions();
    foreach (array_keys($configuration['regions']) as $region) {
      if (isset($regions[$region])) {
        // Merge existing region config with defaults to preserve preset classes.
        $configuration['regions'][$region] = array_merge($default['regions'][$region], $configuration['regions'][$region]);
      }
    }
    // Add any missing regions from defaults.
    $configuration['regions'] += $default['regions'];

    // Remove any region configuration that doesn't apply to current layout.
    foreach (array_keys($configuration['regions']) as $region) {
      if (!isset($regions[$region])) {
        unset($configuration['regions'][$region]);
      }
    }
    $this->configuration = $configuration;
  }


  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    // Get form values for validation
    $container_values = $form_state->getValue('container', []);
    $layout_values = $form_state->getValue('row', []);
    // Validate container settings
    $this->validateContainerSettings($container_values, $form_state);

    // Validate layout settings
    $this->validateRowSettings($layout_values, $form_state);

    // Validate region settings
    foreach ($this->getPluginDefinition()->getRegionNames() as $region_name) {
      $region_values = $form_state->getValue($region_name, []);
      $this->validateRegionSettings($region_name, $region_values, $form_state);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
   parent::submitConfigurationForm($form, $form_state);
   $defaults = $this->getRegionDefaults();

    if ($container_raw = $form_state->getValue('container', $defaults)) {
      // Filter out detail-group keys (form organizational structure) from configuration
      $container_valid_keys = ['container_type', 'classes', 'container_theme', 'container_visibility'];
      $container = [];
      foreach ($container_raw as $key => $value) {
        // Keep valid config keys or utility class keys, skip detail-group structure
        if (in_array($key, $container_valid_keys) || strpos($key, 'classes_') === 0) {
          $container[$key] = $value;
        }
      }

      // Extract utility class values from detail-group structure if present
      foreach ($container_raw as $type => $type_values) {
        if (in_array($type, $container_valid_keys)) {
          continue;
        }
        if (!empty($type_values) && is_array($type_values)) {
          foreach ($type_values as $item => $item_values) {
            if (!empty($item_values) && is_array($item_values)) {
              foreach ($item_values as $instance_name => $value) {
                if (strpos($instance_name, 'classes_') === 0) {
                  $container[$instance_name] = $value;
                }
              }
            }
          }
        }
      }

      $container['container_type'] = $form_state->getValue(['container', 'container_type'], []);
      $container['build_classes'] = Html::cleanCssIdentifier($form_state->getValue(['container', 'build_classes'], ''));

      // Apply Xss::filter to attributes.
      $container['classes'] = Html::cleanCssIdentifier($form_state->getValue(['container', 'classes'], ''));
      $container['container_theme'] = $form_state->getValue(['container', 'container_theme'], []);
      $container['container_visibility'] = $form_state->getValue(['container', 'container_visibility'], []);
//start containter_divisions

//end divisions
////////////////
    $this->configuration['container'] = $container;
    }
    if ($row_raw = $form_state->getValue('row', $defaults)) {
      // Filter out detail-group keys (form organizational structure) from configuration
      $row_valid_keys = ['add_row_classes', 'attributes', 'id', 'custom', 'offset', 'theme', 'visibility', 'advanced', 'gutter'];
      $row = [];
      foreach ($row_raw as $key => $value) {
        // Keep valid config keys or utility class keys, skip detail-group structure
        if (in_array($key, $row_valid_keys) || strpos($key, 'classes_') === 0) {
          $row[$key] = $value;
        }
      }

      // Extract utility class values from detail-group structure if present
      foreach ($row_raw as $type => $type_values) {
        if (in_array($type, $row_valid_keys)) {
          continue;
        }
        if (!empty($type_values) && is_array($type_values)) {
          foreach ($type_values as $item => $item_values) {
            if (!empty($item_values) && is_array($item_values)) {
              foreach ($item_values as $instance_name => $value) {
                if (strpos($instance_name, 'classes_') === 0) {
                  $row[$instance_name] = $value;
                }
              }
            }
          }
        }
      }

      $row['add_row_classes'] = $form_state->getValue(['row', 'advanced', 'add_row_classes'], 1);
      // Apply Xss::filter to attributes.
      $row_classes_value = $form_state->getValue(['row', 'classes'], '');
      $row['columns'] = $form_state->getValue(['row', 'columns'], '');
      $row_attributes_value = $form_state->getValue(['row', 'advanced', 'attributes'], '');
      $row['attributes'] = Xss::filter(is_string($row_attributes_value) ? $row_attributes_value : '');
      $row['id'] = Html::cleanCssIdentifier($form_state->getValue(['row', 'id'], ''));
      $row['custom'] = Html::cleanCssIdentifier($form_state->getValue(['row', 'custom'], ''));
      $row['offset'] = $form_state->getValue(['row', 'offset'], []);
      $row['theme'] = $form_state->getValue(['row', 'theme'], []);
      $row['visibility'] = $form_state->getValue(['row', 'visibility'], []);////////////////
//start rows_divisions

//end divisions
////////////////
dpm($row);
      $this->configuration['row'] = $row;
    }

    $regions = [];
    foreach ($this->getPluginDefinition()->getRegionNames() as $name) {
      // Start with the existing configuration to preserve preset values from YAML.
      $existing_config = $this->configuration['regions'][$name] ?? [];
      $region_raw = $form_state->getValue($name, []);

      // Filter out detail-group keys (form organizational structure) from configuration.
      // These are machine names of detail-groups like 'content_layout', 'spacing', 'default', etc.
      // We only want to keep actual configuration keys.
      $valid_config_keys = ['theme', 'id', 'custom', 'offset', 'visibility', 'align', 'flex', 'attributes', 'add_region_classes', 'advanced'];
      $region = [];
      foreach ($region_raw as $key => $value) {
        // Keep valid top-level config keys, skip detail-group structure
        if (in_array($key, $valid_config_keys) || strpos($key, 'classes_') === 0) {
          $region[$key] = $value;
        }
      }

      // Merge form values with existing configuration to preserve presets.
      $region = array_merge($existing_config, $region);

      if (!empty($region)) {
        $region['theme'] = $form_state->getValue([$name, 'theme'], $existing_config['theme']);
        $region['id'] = Html::cleanCssIdentifier($form_state->getValue([$name, 'id'], $existing_config['id']));
        $region['custom'] = Html::cleanCssIdentifier($form_state->getValue([$name, 'custom'], $existing_config['custom']));
        // Ensure these arrays are properly set from form values or existing config.
        $region['offset'] = $form_state->getValue([$name, 'offset'], $existing_config['offset']);
        $region['visibility'] = $form_state->getValue([$name, 'visibility'], $existing_config['visibility']);
        $region['align'] = $form_state->getValue([$name, 'align'], $existing_config['align']);
        $region['flex'] = $form_state->getValue([$name, 'flex'], $existing_config['flex']);

        // Process dynamic form items from addFormColumnInstances
        // Extract utility class values from detail-group structure
        $region_values = $form_state->getValue($name, []);
        if (!empty($region_values)) {
          foreach ($region_values as $type => $type_values) {
            // Skip if this is a valid config key (already processed above)
            if (in_array($type, $valid_config_keys)) {
              continue;
            }
            // This is a detail-group structure, extract the actual field values
            if (!empty($type_values) && is_array($type_values)) {
              foreach ($type_values as $item => $item_values) {
                if (!empty($item_values) && is_array($item_values)) {
                  foreach ($item_values as $instance_name => $value) {
// dpm($instance_name);
                    // Use form value if provided, otherwise fall back to existing config
                    $region[$instance_name] = !empty($value) ? $value :
                      ($existing_config[$instance_name] ?? []);
                  }
                }
              }
            }
          }
        }

        $region['add_region_classes'] = $form_state->getValue([$name, 'advanced', 'add_region_classes'], $existing_config['add_region_classes'] ?? 0);
        // Apply Xss::filter to attributes.
        $region_attributes_value = $form_state->getValue([$name, 'advanced', 'attributes'], $existing_config['attributes'] ?? '');
        $region['attributes'] = Xss::filter(is_string($region_attributes_value) ? $region_attributes_value : '');
       $regions[$name] = $region;
      }
    }
    $this->configuration['regions'] = $regions;
  }

  /**
   * {@inheritdoc}
   */
  public function build(array $regions) {
    $build = parent::build($regions);

    $configuration = $this->getConfiguration();
    // Setup column_classes + column_container + column_theme

    // --- Container ---
    $build_classes = [];
    // Handle container_type as a class (if needed by template)
    if (!empty($configuration['container']['container_type'])) {
      $build_classes[] = $configuration['container']['container_type'];
    }
    // Handle container_theme as a class
    if (!empty($configuration['container']['container_theme'])) {
      $configuration['container']['theme'] = $configuration['container']['container_theme'];
    }
    // Handle container_theme as a class
    if (!empty($configuration['container']['container_visibility'])) {
      $build_classes += $configuration['container']['container_visibility'];
    }

    // Process dynamic utility classes from addFormZoneInstances
    foreach ($configuration['container'] as $config_key => $config_value) {
      if (strpos($config_key, 'classes_') === 0 && !empty($config_value)) {
        // This is a dynamic utility class configuration
        // Ensure $config_value is an array before merging
        $value_to_merge = is_array($config_value) ? $config_value : [$config_value];
        $build_classes = array_merge($build_classes, $value_to_merge);
      }
    }

    // Handle build_classes (string, split by spaces)
    if (!empty($configuration['container']['classes'])) {
      if (is_array($configuration['container']['classes'])) {
        $build_classes = array_merge($build_classes, $configuration['container']['classes']);
      } else {
        $build_classes = array_merge($build_classes, preg_split('/\s+/', $configuration['container']['classes']));
      }
    }

//start col_divisions
//end divisions
////////////////
    // Remove empty values

    $build_classes = array_filter($build_classes, function($item) { return $item !== ''; });
    // Add to configuration for use in templates
    $configuration['container']['build_classes'] = implode(' ', $build_classes);
// dpm($configuration['container']['build_classes']);


$build_classes_row = [];

    if (!empty($configuration['row']['theme'])) {
      // theme is a string.
       $build_classes_row[]= $configuration['row']['theme'];

      $configuration['row']['classes'] = array_merge(
        $configuration['row']['classes'] ?? [],
        [$configuration['row']['theme']=>$configuration['row']['theme']],
      );
    }

    if (!empty($configuration['row']['offset'])) {
          $build_classes_row[]= $configuration['row']['offset'];
     $configuration['row']['classes'] = array_merge(
        $configuration['row']['classes'] ?? [],
        $configuration['row']['offset'],
      );
    }

    if (!empty($configuration['row']['alignment'])) {
          $build_classes_row[]= $configuration['row']['theme'];
     $configuration['row']['classes'] = array_merge(
        $configuration['row']['classes'] ?? [],
        $configuration['row']['alignment']
      );
    }
//start rows_divisions

//end divisions
////////////////

    if (!empty($configuration['row']['custom'])) {
      // Split the string by spaces into an array.
      $customOpts = explode(' ', $configuration['row']['custom']);
      // Trim periods from each item and filter out empty items.
      $customOpts = array_filter(array_map(function($item) {
        return trim($item, '.');
      }, $customOpts), function($item) {
        return $item !== '';
      });
      $configuration['row']['classes'] = array_merge(
        $configuration['row']['classes'] ?? [],
        array_combine($customOpts, $customOpts)
      );
    }
//dpm($build_classes_row);
//dpm($configuration['row']);

   // Setup region/column classes.
   if (!empty($configuration['regions'])) {
      foreach ($configuration['regions'] as $region_name => &$region_config) {

        // If add_base_col is enabled, add the 'col' class at the beginning.
        if (!empty($region_config['add_base_col'])) {
          $region_config['classes'] = array_merge(
            ['col' => 'col'],
            $region_config['classes'] ?? []
          );
        }
        // If no classes are defined at all (neither preset nor user-selected), ensure a basic column is supplied.
        elseif (empty($region_config['classes'])) {
          $region_config['classes'] = ['col' => 'col'];
        }


        // merge in all other sets.
        if (!empty($region_config['theme'])) {
          $region_config['classes'] = array_merge(
          $region_config['classes'] ?? [],
          [$region_config['theme'] => $region_config['theme']],
          );
        }
       if (!empty($region_config['align_self'])) {
          $region_config['classes'] = array_merge(
            $region_config['classes'] ?? [],
            $region_config['align_self'],
          );
        }

        if (!empty($region_config['alignment'])) {
          $region_config['classes'] = array_merge(
            $region_config['classes'] ?? [],
            $region_config['alignment']
          );
        }

        // Process dynamic utility classes from addFormColumnInstances
        foreach ($region_config as $config_key => $config_value) {
          if (strpos($config_key, 'classes_') === 0 && !empty($config_value)) {
            // This is a dynamic utility class configuration
            // Ensure $config_value is an array before merging
            $value_to_merge = is_array($config_value) ? $config_value : [$config_value];
            $region_config['classes'] = array_merge(
              $region_config['classes'] ?? [],
              $value_to_merge
            );
          }
        }

        if (!empty($region_config['custom'])) {
          // Split the string by spaces into an array.
          $customOpts = explode(' ', $region_config['custom']);
          $region_config['classes'] = array_merge(
            $region_config['classes'] ?? [],
            array_combine($customOpts, $customOpts)
          );
        }

      }
    }
    $this->configuration = $configuration;

//dpm($build);
    return $build;
  }

  private function addFormZoneInstances(&$form, $complete_form_state, $ulility_division, $zone_label, $zone){
    $configuration = $this->getConfiguration();
    foreach($ulility_division as $type => $fields){
      // Convert detail-group to machine name for form structure
      $type_machine = ($type == 'default') ? 'default' : $this->makeMachineName($type);
      $form[$zone][$type_machine]['#type'] = 'details';
      if ($type=='default'){
        $form[$zone][$type_machine]['#open'] = TRUE;
        $form[$zone][$type_machine]['#title'] = $this->t('Base Utilities');
      } else {
        $form[$zone][$type_machine]['#open'] = FALSE;
        $form[$zone][$type_machine]['#title'] = $this->t('@type Utilities', [
          '@type' => $type,
        ]);
      }
     if ($type===(string) $this->t('Spacing')){
         $form[$zone][$type_machine]['#weight'] = -20;
     }
      foreach($fields as $field){

        $item = $this->makeMachineName($field['item']);
        $instance_name = "classes_$item";
        $form[$zone][$type_machine][$item][$instance_name] = [
          '#title' => $this->t('</q>@zone</q> @title', [
            '@title' => $field['field-label'],
            '@zone' => $zone_label,
          ])
        ] + $field['field'];
        // Check for values using both machine name and original type for backward compatibility
        $default_value = $complete_form_state->getValue([$zone, $type_machine, $item, $instance_name]);
        if ($default_value === NULL) {
          $default_value = $complete_form_state->getValue([$zone, $type, $item, $instance_name]);
        }
        if ($default_value === NULL) {
          $default_value = $configuration['row']['wrapper'] ?? [];
        }
        $form[$zone][$type_machine][$item][$instance_name]['#default_value'] = $default_value;
      }
    }
  return $form;
  }

  private function addFormColumnInstances(&$form, $default_values, $ulility_division, $region_label, $region){
    $configuration = $this->getConfiguration();
    foreach($ulility_division as $type => $fields){
      // Convert detail-group to machine name for form structure
      $type_machine = ($type == 'default') ? 'default' : $this->makeMachineName($type);
      $form[$region][$type_machine]['#type'] = 'details';
      if ($type=='default'){
        $form[$region][$type_machine]['#open'] = TRUE;
        $form[$region][$type_machine]['#title'] = $this->t('Base Utilities');
      } else {
        $form[$region][$type_machine]['#open'] = FALSE;
        $form[$region][$type_machine]['#title'] = $this->t('@type Utilities', [
          '@type' => $type,
        ]);
      }
     if ($type==$this->t('Spacing')){
         $form[$region][$type_machine]['#weight'] = -20;
     }
      foreach($fields as $field){
        $item = $this->makeMachineName($field['item']);
        $instance_name = "classes_$item";
        $form[$region][$type_machine][$item][$instance_name] = [
          '#title' => $this->t('<q>@region</q> @title', [
            '@title' => $field['field-label'],
            '@region' => $region_label,
          ])
        ] + $field['field'];
        // Check for values using both machine name and original type for backward compatibility
        $default_value = $default_values[$instance_name] ?? NULL;
        if ($default_value === NULL && isset($configuration['regions'][$region][$type_machine])) {
          // Try to get from machine name key
          $type_config = $configuration['regions'][$region][$type_machine];
          if (isset($type_config[$item][$instance_name])) {
            $default_value = $type_config[$item][$instance_name];
          }
        }
        if ($default_value === NULL && isset($configuration['regions'][$region][$type])) {
          // Try to get from original type key for backward compatibility
          $type_config = $configuration['regions'][$region][$type];
          if (isset($type_config[$item][$instance_name])) {
            $default_value = $type_config[$item][$instance_name];
          }
        }
        $form[$region][$type_machine][$item][$instance_name]['#default_value'] = $default_value ?? [];
      }
    }
  return $form;
  }

  private function chooseFileType($default = NULL, $folder = NULL) {
    if(isset($folder)){
      $folder = $folder ? $folder .'/' : '';
    }
    $config = $this->configFactory->get('bootstrap_five_layouts.settings');
    // get config for this var.  *not on admin settngs form at this time.
    $max_file_size = $config->get('max_file_size');
    $formatted_max_file_size = $this->formatBytes($max_file_size);

    $type = [];
    $file_type = 'file';
    // Determine which file type to use.
    if ($this->moduleHandler->moduleExists('media_library_form_element')) {
      $file_type = 'media';
    }
    elseif ($this->moduleHandler->moduleExists('image')) {
      $file_type = 'image';
    }
    $formats = $config->get('allowed_file_types');
    switch($file_type) {
      case 'media':
        $type = [
          '#type' => 'media_library',
          '#allowed_bundles' => $config->get('allowed_media_types'),
          '#title' => t('Background Image'),
          '#default_value' => isset($default) ?? NULL,
        ];
        break;
      case 'image':
        $type = [
          '#type' => 'managed_file',
          '#title' => t('Background Image'),
          '#multiple' => FALSE,
          '#upload_location' => 'public://bootstrap-layouts-decor/'. $folder,
          '#default_value' => isset($default) ?? NULL,
          '#description' => '<ul>'.
            '<li>' . t('Allowed extensions: <output>@formats</output>', ['@formats' =>  $formats]) . '</li>' .
            '<li>' . t('Max file size: @formatted_max_file_size', ['@formatted_max_file_size' => $formatted_max_file_size ]) . '</li>' .
          '</ul>',
          '#upload_validators' => [
            'file_validate_extensions' => [$formats],
            'file_validate_size' => [$max_file_size],
          ],
        ];
        break;
      case 'file':
      default:
      //BytesFormatTrait todo  show formated allowed size..
        $type = [
          '#type' => 'file',
          '#multiple' => FALSE,
          '#title' => t('Background Image'),
          '#upload_location' => 'public://bootstrap-layouts-decor/'. $folder,
          '#description' => '<ul>'.
            '<li>' . t('Allowed extensions: <output>@formats</output>', ['@formats' =>  $formats]) . '</li>' .
            '<li>' . t('Max file size: @formatted_max_file_size', ['@formatted_max_file_size' => $formatted_max_file_size ]) . '</li>' .
          '</ul>',
          '#upload_validators' => [
            'file_validate_extensions' => [$formats],
            'file_validate_size' => [$max_file_size],
          ],
        ];
        break;
    }

    return $type;
  }

  /**
   * Validates container settings.
   *
   * @param array $container_values
   *   The container form values.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  private function validateContainerSettings(array $container_values, FormStateInterface $form_state) {
    // Validate container classes
    if (!empty($container_values['build_classes'])) {
      $classes = is_array($container_values['build_classes'])
        ? $container_values['build_classes']
        : preg_split('/\s+/', $container_values['build_classes']);

      foreach ($classes as $class) {
        $class = trim($class);
        if (!empty($class) && !$this->isValidCssClass($class)) {
          $form_state->setErrorByName('container][build_classes',
            $this->t('Invalid CSS class <q><code>@class</code></q> in container classes.', ['@class' => $class]));
        }
      }
    }
  }

  /**
   * Validates layout settings.
   *
   * @param array $layout_values
   *   The layout form values.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  private function validateRowSettings(array $layout_values, FormStateInterface $form_state) {
    // Validate layout ID
    if (!empty($layout_values['id'])) {
      if (!$this->isValidHtmlId($layout_values['id'])) {
        $form_state->setErrorByName('row][id',
          $this->t('Invalid ID <q><code>@id</code></q>.', ['@id' => $layout_values['id']]));
      }
    }

    // Validate custom classes
    if (!empty($layout_values['custom'])) {

      $classes = preg_split('/\s+/', $layout_va);
      $classes = preg_split('/\s+/', $layout_values['custom']);
      foreach ($classes as $class) {
        $class = trim($class);
        if (!empty($class) && !$this->isValidCssClass($class)) {
          $form_state->setErrorByName('row][custom',
            $this->t('Invalid CSS class <q><code>@class</code></q> in custom classes.', ['@class' => $class]));
        }
      }
    }

    // Validate attributes (now in advanced section)
    if (!empty($layout_values['advanced']['attributes'])) {
      if (!$this->isValidHtmlAttributes($layout_values['advanced']['attributes'])) {
        $form_state->setErrorByName('row][advanced][attributes',
          $this->t('Invalid attributes format.<br><br>Use format: attribute|value or attribute|<q>value with spaces</q>'));
      }
    }
  }

  /**
   * Validates region settings.
   *
   * @param string $region_name
   *   The region name.
   * @param array $region_values
   *   The region form values.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  private function validateRegionSettings($region_name, array $region_values, FormStateInterface $form_state) {
    // Validate region ID
    if (!empty($region_values['id'])) {
      if (!$this->isValidHtmlId($region_values['id'])) {
        $form_state->setErrorByName($region_name . '][id',
          $this->t('Invalid ID <q><code>@id</code></q> for region "@region".', [
            '@id' => $region_values['id'],
            '@region' => $region_name
          ]));
      }
    }

    // Validate custom classes
    if (!empty($region_values['custom'])) {
      $classes = preg_split('/\s+/', $region_values['custom']);
      foreach ($classes as $class) {
        $class = trim($class);
        if (!empty($class) && !$this->isValidCssClass($class)) {
          $form_state->setErrorByName($region_name . '][custom',
            $this->t('Invalid CSS class <q><code>@class</code></q> in custom classes for region "@region".', [
              '@class' => $class,
              '@region' => $region_name
            ]));
        }
      }
    }

    // Validate attributes (now in advanced section)
    if (!empty($region_values['advanced']['attributes'])) {
      if (!$this->isValidHtmlAttributes($region_values['advanced']['attributes'])) {
        $form_state->setErrorByName($region_name . '][advanced][attributes',
          $this->t('Invalid attributes format in advanced settings for region "@region".<br><br>Use format: attribute|value or attribute|"value with spaces"', ['@region' => $region_name]));
      }
    }
  }

  /**
   * Validates that CSS class names are properly formatted.
   *
   * @param string $class
   *   The CSS class name to validate.
   * @return bool
   *   TRUE if valid, FALSE otherwise.
   */
  protected function isValidCssClass($class) {
    // Allow letters, numbers, hyphens, and underscores
    // Must start with a letter or underscore
    return preg_match('/^[a-zA-Z_-][a-zA-Z0-9_-]*$/', $class) === 1;
  }

  /**
   * Validates that HTML IDs are properly formatted.
   *
   * @param string $id
   *   The HTML ID to validate.
   * @return bool
   *   TRUE if valid, FALSE otherwise.
   */
  protected function isValidHtmlId($id) {
    // Allow letters, numbers, hyphens, underscores, and colons
    // Must start with a letter
    return preg_match('/^[a-zA-Z][a-zA-Z0-9_-]*$/', $id) === 1;
  }

  /**
   * Validates HTML attributes format.
   *
   * @param string $attributes
   *   The attributes string to validate.
   * @return bool
   *   TRUE if valid, FALSE otherwise.
   */
  protected function isValidHtmlAttributes($attributes) {
    // Check for proper key|value format
    // Allow spaces in quoted values
    $lines = explode("\n", $attributes);
    foreach ($lines as $line) {
      $line = trim($line);
      if (empty($line)) {
        continue;
      }

      // Match patterns like: attribute|value or attribute|"value with spaces"
      if (!preg_match('/^[a-zA-Z_-]+\|(?:"[^"]*"|[^\s\|]+)$/', $line)) {
        return FALSE;
      }
    }
    return TRUE;
  }


  /**
   * Gets the default container type from configuration.
   *
   * @return string
   *   The default container type, defaults to 'container' if config is not available.
   */
  protected function getDefaultContainerType() {
    // Check if configFactory is available (it might be null during early initialization).
    $config = $this->configFactory->get('bootstrap_five_layouts.settings');
    $default_container = $config->get('default_container');
    if ($default_container) {
      return $default_container;
    }

    // Fallback to 'container' if config is not available or not set.
    return 'no-container';
  }

  protected function makeMachineName($string) {
    $machine = mb_strtolower($string);
    $machine = preg_replace('/[^a-z0-9_]+/', '_', $machine);
    $machine = trim($machine, '_');
    return $machine;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc