openlayers-8.x-4.x-dev/src/Form/OpenlayersMapFormBase.php
src/Form/OpenlayersMapFormBase.php
<?php
namespace Drupal\openlayers\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Link;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Url;
use Drupal\openlayers\OpenlayersPluginManager;
//use Drupal\openlayers\LayerPluginManager;
//use Drupal\openlayers\StylePluginManager;
//use Drupal\openlayers\ControlPluginManager;
//use Drupal\openlayers\InteractionPluginManager;
use Drupal\openlayers\OpenlayersPluginCollection;
use Drupal\openlayers\OpenlayersConfigurablePluginInterface;
/**
* Base form for Openlayers Maps add and edit configuration forms.
*/
class OpenlayersMapFormBase extends EntityForm {
/**
* The entity being used by this form.
*
* @var \Drupal\image\ImageStyleInterface
*/
protected $entity;
/**
* The image style entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $entityStorage;
/**
* The image effect manager service.
*
* @var \Drupal\image\ImageEffectManager
*/
protected $openlayersPluginManager;
// protected $layerPluginManager;
// protected $stylePluginManager;
// protected $controlPluginManager;
// protected $interactionPluginManager;
/**
* Constructs a base class for image style add and edit forms.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $image_style_storage
* The image style entity storage.
*/
public function __construct(
EntityStorageInterface $entity_storage,
OpenlayersPluginManager $openlayers_plugin_manager,
// LayerPluginManager $layer_plugin_manager,
// StylePluginManager $style_plugin_manager,
// ControlPluginManager $control_plugin_manager,
// InteractionPluginManager $interaction_plugin_manager
) {
$this->entityStorage = $entity_storage;
$this->openlayersPluginManager = $openlayers_plugin_manager;
// $this->layerPluginManager = $layer_plugin_manager;
// $this->stylePluginManager = $style_plugin_manager;
// $this->controlPluginManager = $control_plugin_manager;
// $this->interactionPluginManager = $interaction_plugin_manager;
}
public static function create(ContainerInterface $container) {
$form = new static(
$container->get('entity_type.manager')->getStorage('openlayers_map'),
$container->get('plugin.manager.openlayers'),
// $container->get('plugin.manager.openlayers.layer'),
// $container->get('plugin.manager.openlayers.style'),
// $container->get('plugin.manager.openlayers.control'),
// $container->get('plugin.manager.openlayers.interaction')
);
$form->setMessenger($container->get('messenger'));
return $form;
}
public function buildForm(array $form, FormStateInterface $form_state) {
// Get anything we need from the base class.
$form = parent::buildForm($form, $form_state);
$user_input = $form_state->getUserInput();
$form['#title'] = $this->t('Edit %name map', ['%name' => $this->entity->label()]);
$form['#tree'] = TRUE;
$form['#attached']['library'][] = 'openlayers/admin';
// Drupal provides the entity to us as a class variable. If this is an
// existing entity, it will be populated with existing values as class
// variables. If this is a new entity, it will be a new object with the
// class of our entity. Drupal knows which class to call from the
// annotation on our Control class.
$openlayers_map = $this->entity;
// Build the form.
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#maxlength' => 255,
'#default_value' => $openlayers_map->label(),
'#required' => TRUE,
];
$form['id'] = [
'#type' => 'machine_name',
'#title' => $this->t('Machine name'),
'#default_value' => $openlayers_map->id(),
'#machine_name' => [
'exists' => [$this, 'exists'],
'replace_pattern' => '([^a-z0-9_]+)|(^custom$)',
'error' => 'The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores. Additionally, it can not be the reserved word "custom".',
],
'#disabled' => !$openlayers_map->isNew(),
];
$form['service'] = [
'#type' => 'textfield',
'#title' => $this->t('Factory Service'),
'#default_value' => 'openlayers.Map:OLMap',
'#disabled' => TRUE,
];
$form['pluginId'] = [
'#type' => 'value',
'#value' => 'OLMap',
];
// Start of layers / styles / controls subforms
foreach (['layer', 'control', 'style', 'interaction', 'component'] as $type) {
$types = $type . 's';
// Build the list of existing objects for this map.
$form[$types] = [
'#type' => 'table',
'#header' => [
$this->t(ucfirst($types)),
$this->t('Weight'),
$this->t('Operations'),
],
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'ol-' . $type . '-order-weight',
],
],
'#attributes' => [
'id' => 'ol-map-' . $types,
],
'#empty' => t('There are currently no ' . $types . ' in this map. Add one by selecting an option below.'),
'#weight' => 5,
];
// Build the list of new objects that can be added to the map (excluding those that are already attached to the map?).
$all_objects = $this->entity->getAllObjects($type);
$map_objects = $this->entity->getObjects($type);
foreach ($map_objects->getConfigurations() as $key => $object_info) {
$object = \Drupal::entityTypeManager()
->getStorage('openlayers_' . $type)
->load($object_info['id'])
;
if ($object !== null) {
$label = $object->label();
$id = $object->id();
$is_configurable = $object->is_configurable;
} else {
$label = 'Unknown';
$id = 'Unknown';
$is_configurable = false;
}
$form[$types][$key]['#attributes']['class'][] = 'draggable';
$form[$types][$key][$type] = [
'#tree' => FALSE,
'data' => [
'label' => [
'#plain_text' => $label . ' (' . $id . ')',
],
],
];
$form[$types][$key]['weight'] = [
'#type' => 'weight',
'#title' => $this->t('Weight for @title', ['@title' => 'xxxxxx']),
'#title_display' => 'invisible',
'#default_value' => isset($object_info['weight']) ? $object_info['weight'] : 0,
'#attributes' => [
'class' => ['ol-' . $type . '-order-weight'],
],
];
$links = [];
if ($is_configurable) {
$links['edit'] = [
'title' => $this->t('Edit'),
'url' => Url::fromRoute('openlayers.map.' . $type . '_edit_form', [
'map' => $this->entity->id(),
$type => $key,
]),
];
}
$links['delete'] = [
'title' => $this->t('Delete'),
'url' => Url::fromRoute('openlayers.map.' . $type . '_delete', [
'map' => $this->entity->id(),
$type => $key,
]),
];
$form[$types][$key]['operations'] = [
'#type' => 'operations',
'#links' => $links,
];
$form[$types][$key]['uuid'] = [
'#type' => 'value',
'#value' => $object_info['uuid'],
];
$form[$types][$key]['id'] = [
'#type' => 'value',
'#value' => $object_info['id'],
];
$form[$types][$key]['data'] = [
'#type' => 'value',
'#value' => isset($object_info['data']) ? $object_info['data'] : [],
];
}
// Build the new object addition form and add it to the object list.
asort($all_objects);
$form[$types]['new'] = [
'#tree' => FALSE,
'#weight' => isset($user_input['weight']) ? $user_input['weight'] : NULL, // TODO - ???
'#attributes' => ['class' => ['draggable']],
];
$form[$types]['new'][$type] = [
'data' => [
'new_' . $type => [
'#type' => 'select',
'#title' => $this->t($type),
'#title_display' => 'invisible',
'#options' => $all_objects,
'#empty_option' => $this->t('Select a new ' . $type),
],
[
'add' => [
'#type' => 'submit',
'#value' => $this->t('Add ' . $type),
'#name' => 'add_' . $type,
'#validate' => ['::ObjectValidate'],
'#submit' => ['::submitForm', '::ObjectSave'],
],
],
],
'#prefix' => '<div class="map-' . $type . '-new">',
'#suffix' => '</div>',
];
$form[$types]['new']['weight'] = [
'#type' => 'weight',
'#title' => $this->t('Weight for new ' . $type),
'#title_display' => 'invisible',
'#default_value' => 0,
'#attributes' => ['class' => ['ol-' . $type . '-order-weight']],
];
$form[$types]['new']['operations'] = [
'data' => [],
];
}
// End of layers / styles / controls / interactions / components subforms
// Finally set the options for the Openlayers Map View.
$form['map_view'] = [
'#type' => 'fieldset',
'#title' => $this->t('Map View'),
'#weight' => 50,
];
$form['map_view']['center'] = [
'#type' => 'fieldset',
'#title' => $this->t('Center'),
];
$form['map_view']['center']['lat'] = [
'#type' => 'textfield',
'#title' => $this->t('Latitude'),
'#size' => 20,
'#default_value' => isset($openlayers_map->map_view['center']['lat']) ? (int)$openlayers_map->map_view['center']['lat'] : 0,
];
$form['map_view']['center']['lon'] = [
'#type' => 'textfield',
'#title' => $this->t('Longitude'),
'#size' => 20,
'#description' => $this->t('Latitude / longitude should be expressed in degrees.'),
'#default_value' => isset($openlayers_map->map_view['center']['lon']) ? (int)$openlayers_map->map_view['center']['lon'] : 0,
];
$zoom_options = [1 => '1', 2 => '2', 3 => '3', 4 => '4', 5 => '5', 6 => '6']; // TODO
$form['map_view']['zoom'] = [
'#type' => 'select',
'#title' => $this->t('Zoom'),
'#options' => $zoom_options,
'#empty_option' => $this->t('Select an initial zoom'),
'#default_value' => isset($openlayers_map->map_view['zoom']) ? $openlayers_map->map_view['zoom'] : 0,
];
$projection_options = ['EPSG:3857' => 'EPSG:3857', 'bb' => 'BB', 'cc' => 'CC']; // TODO
$form['map_view']['projection'] = [
'#type' => 'select',
'#title' => $this->t('Projection'),
'#options' => $projection_options,
'#empty_option' => $this->t('Select a projection'),
'#default_value' => isset($openlayers_map->map_view['projection']) ? $openlayers_map->map_view['projection'] : 'EPSG:3857',
];
$form['map_view']['rotation'] = [
'#type' => 'textfield',
'#title' => $this->t('Rotation'),
'#default_value' => isset($openlayers_map->map_view['rotation']) ? $openlayers_map->map_view['rotation'] : 0,
];
$form['map_view']['force'] = [
'#type' => 'checkbox',
'#title' => $this->t('Force'),
'#description' => $this->t('Forces the initial center / zoom, rather than let the map calculate this automatically based on the features.'),
'#default_value' => isset($openlayers_map->map_view['force']) ? $openlayers_map->map_view['force'] : false,
];
// Return the form.
return $form;
}
/**
* Checks for an existing openlayers_map.
*
* @param string|int $entity_id
* The entity ID.
* @param array $element
* The form element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return bool
* TRUE if this format already exists, FALSE otherwise.
*/
public function exists($entity_id, array $element, FormStateInterface $form_state) {
// Use the query factory to build a new openlayers_map entity query.
$query = $this->entityStorage->getQuery();
// Query the entity ID to see if its in use.
$result = $query->condition('id', $element['#field_prefix'] . $entity_id)
->execute();
// We don't need to return the ID, only if it exists or not.
return (bool) $result;
}
/**
* Overrides Drupal\Core\Entity\EntityFormController::actions().
*
* To set the submit button text, we need to override actions().
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* An associative array containing the current state of the form.
*
* @return array
* An array of supported actions for the current entity form.
*/
protected function actions(array $form, FormStateInterface $form_state) {
// Get the basic actins from the base class.
$actions = parent::actions($form, $form_state);
// Change the submit button text.
$actions['submit']['#value'] = $this->t('Save');
// Return the result.
return $actions;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
// Add code here to validate your config entity's form elements.
// Nothing to do here.
}
public function submit(array &$form, FormStateInterface $form_state) {
parent::submit($form, $form_state);
}
/**
* Overrides Drupal\Core\Entity\EntityFormController::save().
*
* Saves the entity. This is called after submit() has built the entity from
* the form values. Do not override submit() as save() is the preferred
* method for entity form controllers.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* An associative array containing the current state of the form.
*/
public function save(array $form, FormStateInterface $form_state) {
// EntityForm provides us with the entity we're working on.
$openlayers_map = $this->getEntity();
// Drupal already populated the form values in the entity object. Each
// form field was saved as a public variable in the entity class. PHP
// allows Drupal to do this even if the method is not defined ahead of
// time.
$status = $openlayers_map->save();
// Grab the URL of the new entity. We'll use it in the message.
$url = $openlayers_map->toUrl();
// Create an edit link.
$edit_link = Link::fromTextAndUrl($this->t('Edit'), $url)->toString();
if ($status == SAVED_UPDATED) {
// If we edited an existing entity...
$this->messenger()->addMessage($this->t('Map %label has been updated.', ['%label' => $openlayers_map->label()]));
$this->logger('contact')->notice('Map %label has been updated.', ['%label' => $openlayers_map->label(), 'link' => $edit_link]);
}
else {
// If we created a new entity...
$this->messenger()->addMessage($this->t('Map %label has been added.', ['%label' => $openlayers_map->label()]));
$this->logger('contact')->notice('Map %label has been added.', ['%label' => $openlayers_map->label(), 'link' => $edit_link]);
}
// Redirect the user back to the listing route after the save operation
$form_state->setRedirect('entity.openlayers_map.list');
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Update image effect weights.
if (!$form_state->isValueEmpty('layers')) {
$this->updateLayerWeights($form_state->getValue('layers'));
}
}
/**
* Validate handler for image effect.
*/
public function objectValidate($form, FormStateInterface $form_state) {
$type = str_replace('add_', '', $form_state->getTriggeringElement()['#name']);
if (!$form_state->getValue('new_' . $type)) {
$form_state->setErrorByName('new_' . $type, $this->t('Select a ' . $type . ' to add.'));
}
}
/**
* Submit handler for new/amended object being added to the map edit form.
*/
public function objectSave($form, FormStateInterface $form_state) {
$type = str_replace('add_', '', $form_state->getTriggeringElement()['#name']);
// Create empty Objects array if no layers added to map yet
if ($this->entity->{$type . 's'} == '') {
$this->entity->{$type . 's'} = [];
}
$this->save($form, $form_state);
// Check if this field has any configuration options.
// TODO: potentially convert the config options to a plugin (see Image module in core with Styles/Effects as an example)
// TODO: there must be a shorthand for this eg. Node::load.
$mapObject = \Drupal::entityTypeManager()
->getStorage('openlayers_' . $type)
->load($form_state->getValue('new_' . $type));
// Load the configuration form for this option.
if ($mapObject->get('is_configurable')) {
$form_state->setRedirect(
'openlayers.map.' . $type . '_add_form',
[
'map' => $this->entity->id(),
$type => $form_state->getValue('new_' . $type),
'weight' => $form_state->getValue('weight'),
],
['query' => ['weight' => $form_state->getValue('weight')]]
);
}
// If there are no configuration options/form, add the new object to the map immediately.
else {
$configuration = [
'id' => $mapObject->id(),
'weight' => 0,
'data' => ['entityId' => $mapObject->id()],
];
$form_state->setRedirect(
'entity.openlayers_map.edit_form',
[
'openlayers_map' => $this->entity->id(),
],
);
$object_id = $this->entity->addMapObject($configuration, $type);
$this->entity->save();
if (!empty($object_id)) {
$this->messenger()->addStatus($this->t('The ' . $type . ' was successfully added to the map.'));
}
}
}
/**
* Validate handler for image effect.
*/
public function layerValidate($form, FormStateInterface $form_state) {
if (!$form_state->getValue('new_layer')) {
$form_state->setErrorByName('new_layer', $this->t('Select a layer to add.'));
}
}
/**
* Submit handler for new/amended layer being added to the map edit form.
*/
public function layerSave($form, FormStateInterface $form_state) {
// Create empty Layers array if no layers added to map yet
if ($this->entity->layers == '') {
$this->entity->layers = [];
}
$this->save($form, $form_state);
// Check if this field has any configuration options.
// TODO: potentially convert the layer config options to a plugin (see Image module in core with Styles/Effects as an example)
// TODO: there must be a shorthand for this eg. Node::load.
$layer = \Drupal::entityTypeManager()
->getStorage('openlayers_layer')
->load($form_state->getValue('new_layer'));
// Load the configuration form for this option.
if ($layer->get('is_configurable')) {
$form_state->setRedirect(
'openlayers.map.layer_add_form',
[
'map' => $this->entity->id(),
'layer' => $form_state->getValue('new_layer'),
'weight' => $form_state->getValue('weight'),
],
['query' => ['weight' => $form_state->getValue('weight')]]
);
}
// If there are no configuration options/form, add the layer to the map immediately.
else {
$configuration = [
'id' => $layer->getType(),
'weight' => 0,
'data' => ['entityId' => $layer->id()],
];
$form_state->setRedirect(
'entity.openlayers_map.edit_form',
[
'openlayers_map' => $this->entity->id(),
],
);
$object_id = $this->entity->addMapObject($configuration, $type);
$this->entity->save();
if (!empty($layer_id)) {
$this->messenger()->addStatus($this->t('The ' . $type . ' was successfully added to the map.'));
}
}
}
/**
* Validate handler for map style.
*/
public function styleValidate($form, FormStateInterface $form_state) {
if (!$form_state->getValue('new_style')) {
$form_state->setErrorByName('new_style', $this->t('Select a style to add.'));
}
}
/**
* Submit handler for new/amended style being added to the map edit form.
*/
public function styleSave($form, FormStateInterface $form_state) {
$this->save($form, $form_state);
// Check if this field has any configuration options.
$style = $this->openlayersPluginManager->getDefinition($form_state->getValue('new_style'));
// Load the configuration form for this option.
if (is_subclass_of($style['class'], '\Drupal\openlayers\OpenlayersConfigurablePluginInterface')) {
$form_state->setRedirect(
'openlayers.style.plugin_add_form',
[
'map' => $this->entity->id(),
'plugin_type' => 'style',
'plugin' => $form_state->getValue('new_style'),
],
);
}
// If there are no configuration options/form, add the style to the map immediately.
else {
$style = [
'id' => $style['id'],
'data' => [],
'weight' => 0,
'data' => ['entityId' => $style->id()],
];
$style_id = $this->entity->addMapStyle($style);
$this->entity->save();
if (!empty($style_id)) {
$this->messenger()->addStatus($this->t('The style was successfully added to the map.'));
}
$form_state->setRedirect(
'entity.openlayers_map.edit_form',
[
'openlayers_map' => $this->entity->id(),
],
);
}
}
/**
* Validate handler for new/amended control.
*/
public function controlValidate($form, FormStateInterface $form_state) {
if (!$form_state->getValue('new_control')) {
$form_state->setErrorByName('new_control', $this->t('Select a control to add.'));
}
}
/**
* Submit handler for new/amended layer being added to the map edit form.
*/
public function controlSave($form, FormStateInterface $form_state) {
$this->save($form, $form_state);
// Check if this field has any configuration options.
$control = $this->openlayersPluginManager->getDefinition($form_state->getValue('new_control'));
// Load the configuration form for this option.
if (is_subclass_of($control['class'], '\Drupal\openlayers\OpenlayersConfigurablePluginInterface')) {
$form_state->setRedirect(
'openlayers.control.plugin_add_form',
[
'map' => $this->entity->id(),
'plugin_type' => 'control',
'plugin' => $form_state->getValue('new_control'),
],
);
}
// If there are no configuration options/form, add the control to the map immediately.
else {
$control = [
'id' => $control['id'],
'data' => [],
'weight' => 0,
'data' => ['entityId' => $control['id']],
];
$control_id = $this->entity->addMapControl($control);
$this->entity->save();
if (!empty($control_id)) {
$this->messenger()->addStatus($this->t('The control was successfully added to the map.'));
}
$form_state->setRedirect(
'entity.openlayers_map.edit_form',
[
'openlayers_map' => $this->entity->id(),
],
);
}
}
/**
* Validate handler for image effect.
*/
public function interactionValidate($form, FormStateInterface $form_state) {
if (!$form_state->getValue('new_interaction')) {
$form_state->setErrorByName('new_interaction', $this->t('Select a interaction to add.'));
}
}
/**
* Submit handler for new/amended interaction being added to the map edit form.
*/
public function interactionSave($form, FormStateInterface $form_state) {
$this->save($form, $form_state);
// Check if this field has any configuration options.
$interaction = $this->openlayersPluginManager->getDefinition($form_state->getValue('new_interaction'));
// Load the configuration form for this option.
if (is_subclass_of($interaction['class'], '\Drupal\openlayers\OpenlayersConfigurablePluginInterface')) {
$form_state->setRedirect(
'openlayers.interaction.plugin_add_form',
[
'map' => $this->entity->id(),
'plugin_type' => 'interaction',
'plugin' => $form_state->getValue('new_interaction'),
],
);
}
// If there are no configuration options/form, add the interaction to the map immediately.
else {
$interaction = [
'id' => $interaction['id'],
'data' => [],
'weight' => 0,
'data' => ['entityId' => $interaction->id()],
];
$interaction_id = $this->entity->addMapInteraction($interaction);
$this->entity->save();
if (!empty($interaction_id)) {
$this->messenger()->addStatus($this->t('The interaction was successfully added to the map.'));
}
$form_state->setRedirect(
'entity.openlayers_map.edit_form',
[
'openlayers_map' => $this->entity->id(),
],
);
}
}
/**
* Updates layer weights on map.
*
* @param array $layers
* Associative array with layers having layer uuid as keys and array
* with effect data as values.
*/
protected function updateLayerWeights(array $layers) {
/*
foreach ($layers as $uuid => $layer_data) {
if (isset($this->entity->getLayers()[$uuid])) {
$this->entity->layers[$uuid]['weight'] = $layer_data['weight'];
}
}
*/
}
}
