Issue #3290929: Provide a farmOS map form element

This commit is contained in:
Michael Stenta 2022-06-19 17:47:51 -04:00
commit 62e54951f2
19 changed files with 760 additions and 67 deletions

View File

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- [Issue #3290929: Provide a farmOS map form element](https://www.drupal.org/project/farm/issues/3290929)
### Security
- Update Drupal core to 9.3.16 for [SA-CORE-2022-011](https://www.drupal.org/sa-core-2022-011).

View File

@ -0,0 +1,358 @@
# Maps
farmOS includes features for rendering and manipulating geometry data in
map-based UIs.
It uses [farmOS-map](https://github.com/farmOS/farmOS-map), which is based on
the open-source [OpenLayers](https://openlayers.org/) project. This includes
tools for drawing and editing geometries, adding imagery and vector layers, and
a framework for writing custom behaviors.
farmOS-map is maintained by the farmOS community as a standalone library for
common agricultural mapping needs. It is designed to be reusable in any
application with similar needs. It is not specific to or dependent on farmOS
itself. Rather, farmOS includes it as a dependency, and provides some helpful
wrappers for using it inside modules. This page describes how to use farmOS-map
in farmOS modules.
For more information about the farmOS-map library itself and what it provides,
refer to the farmOS-map documentation on GitHub:
[github.com/farmOS/farmOS-map](https://github.com/farmOS/farmOS-map)
## Render element
Maps can be embedded in pages as a `farm_map` type render element.
```php
$build['mymap'] = [
'#type' => 'farm_map',
'#map_type' => 'default',
'#map_settings' => [
'mysetting' => 'myvalue',
],
'#behaviors' => [
'mybehavior',
],
];
```
**Properties:**
- `#map_type` (optional) - See [Map types](#map-types). Defaults to `default`.
- `#map_settings` (optional) - An array of map settings, which will be passed
into the map instance's client-side JavaScript object, so they are available
in behavior JavaScript.
- `#behaviors` (optional) - See [Behaviors](#behaviors). Defaults to `[]` (but
behaviors may also be added by map types and render events).
## Form element
Editable maps can be embedded in forms with a `farm_map_input` type element.
These maps will have the drawing/editing controls enabled, allowing geometries
to be added/edited/deleted directly in the map. A default value can be used to
pre-populate the map with a geometry. A text field can be optionally displayed
beneath the map to show the raw geometry data (auto-updates during editing).
**Example:**
```php
$form['mymap'] = [
'#type' => 'farm_map_input',
'#title' => t('My Geometry'),
'#map_type' => 'default',
'#map_settings' => [
'mysetting' => 'myvalue',
],
'#behaviors' => [
'mybehavior',
],
'#display_raw_geometry' => TRUE,
'#default_value' => 'POINT(-45.967095060886315 32.77503850904169)',
];
```
**Properties:**
- `#map_type` (same as render element, above)
- `#map_settings` (same as render element, above)
- `#behaviors` (same as render element, above)
- `#display_raw_geometry` (optional) - Whether to show a text field below the
map with the raw geometry value in Well-Known Text (WKT) format. Defaults to
`FALSE`.
- `#default_value` (optional) - The default geometry value to display in the
map initially, in Well-Known Text (WKT) format. This geometry will be
editable in the map unless `#disabled` is `TRUE`.
## Map types
farmOS modules can optionally define "map types", which are then referenced in
the `#map_type` property of the render and form elements.
**This is optional and in most cases the `default` map type is sufficient.**
Map types are used to define reusable map configurations with common
[behaviors](#behaviors). They can be targeted by [render event](#render-events)
subscribers to add/modify behavior in certain contexts.
Map types are represented as Drupal config entities, installed via modules,
just like asset types, log types, flags, etc.
A very simple example of a custom map type definition looks like this:
`my_module/config/install/farm_map.map_type.mymaptype.yml`
```yaml
langcode: en
status: true
dependencies:
enforced:
module:
- my_module
id: mymaptype
label: My Map Type
description: "My module's custom map type."
behaviors: { }
options: { }
```
**Properties**
- `id` - A unique ID for the map type. This will be referenced in `#map_type`.
- `label` - A human-readable label for the map type.
- `description` - A human-readable description for the map type.
- `behaviors` - A list of [behaviors](#behaviors) to attach to maps of this
type by default.
- `options` - Default options that will be merged with `#map_settings` and
passed into `farmOS.map.create()`. See:
[github.com/farmOS/farmOS-map#creating-a-map](https://github.com/farmOS/farmOS-map#creating-a-map)
## Behaviors
The farmOS-map library uses the concept of "behaviors" to encapsulate common
and reusable sets of map behavior logic into JavaScript objects that can be
"attached" to map instances.
Behaviors can be used to add layers to a map, add new buttons/controls, enable
OpenLayers interactions, connect maps with other elements of a page like forms,
etc.
For general information about farmOS-map behaviors, see:
[github.com/farmOS/farmOS-map#adding-behaviors](https://github.com/farmOS/farmOS-map#adding-behaviors)
Some behaviors that farmOS provides include:
- `wkt` - Adds a vector layer to the map based on a Well-Known Text (WKT)
string. Edit controls can be optionally enabled to allow drawing, modifying,
moving, and deleting geometries within the map. This behavior is enabled
automatically in the `farm_map_input` form element, and when `wkt` is
included in `#map_settings.`
- `input` - Listens for changes to geometries in the map and copies them to a
form input (`textfield` or `hidden`) to be saved/manipulated server-side.
This behavior is enabled automatically in the `farm_map_input` form element.
- `popup` - Adds a popup interaction to the map, which appears when a geometry
feature is clicked.
- `asset_type_layers` - Adds asset geometry vector and cluster layers to a
map. This behavior is responsible for adding the "Locations" layers on the
farmOS dashboard map, the "Assets" and "Asset counts" layers to asset maps,
automatically zooming to visible geometries, and adding asset details to
popups when a geometry is clicked (depends on the `popup` behavior).
### Providing behaviors
Modules can provide their own behaviors with a couple of additional files.
The behavior itself is represented as a Drupal config entity, which gets
installed as a YML config file during module installation.
For example (replace `my_module` with the module name, and `mybehavior` with
the behavior name):
`my_module/config/install/farm_map.behavior.mybehavior.yml`
```yaml
langcode: en
status: true
dependencies:
enforced:
module:
- my_module
id: mybehavior
label: My Behavior
description: 'Adds my custom behavior logic.'
library: 'my_module/behavior_mybehavior'
settings: { }
```
The module must declare the behavior JavaScript file as a "library" so that
it can be included in the page(s) that need it.
For example (replace `my_module` with the module name, and `mybehavior` with
the behavior name):
`my_module/my_module.libraries.yml`
```yaml
behavior_mybehavior:
js:
js/farmOS.map.behaviors.mybehavior.js: { }
dependencies:
- farm_map/farm_map
```
Finally, the behavior JavaScript file should have a path and filename that
matches the library definition.
For example (replace `my_module` with the module name, and `mybehavior` with
the behavior name):
`my_module/js/farmOS.map.behaviors.mybehavior.js`
```js
(function () {
farmOS.map.behaviors.mybehavior = {
attach: function (instance) {
// My custom behavior logic.
}
};
}());
```
The `instance` object represents the farmOS-map instance, and includes helper
methods for common needs (eg: `instance.addLayer()`), as well as direct access
to the OpenLayers map object at `instance.map`.
For more information see:
[github.com/farmOS/farmOS-map](https://github.com/farmOS/farmOS-map)
### Attaching behaviors
Behaviors can be "attached" (enabled) in a map in a few different ways:
- [Map types](#map-types) can include a list of default `behaviors`.
- The `#behaviors` property of the `farm_map` [render element](#render-element)
and `farm_map_input` [form element](#form-element) can add specific behaviors
to individual elements.
- A [render event](#render-events) subscriber can use the
`$event->addBehavior()` method.
In all cases the behavior's `id` (as defined it its config entity YML) is used.
### Behavior settings
Some behaviors may require additional settings based on their context. Best
practice is to include these in the map settings so that they are available in
the behavior JavaScript in the following way:
`const settings = instance.farmMapSettings.behaviors.mybehavior;`
This can be accomplished in different ways, depending on how the behavior is
being attached to the map.
[Map types](#map-types) can add behavior settings to their `options` property.
For example:
```yaml
langcode: en
status: true
dependencies:
enforced:
module:
- my_module
id: mymaptype
label: My Map Type
description: "My module's custom map type."
behaviors:
- mybehavior
options:
behaviors:
mybehavior:
mysetting: True
```
Maps added as [render](#render-element) or [form](#form-element) elements can
add behavior settings in their `#map_settings` property. For example:
```php
$build['mymap'] = [
'#type' => 'farm_map',
'#map_settings' => [
'behaviors' => [
'mybehavior' => [
'mysetting' => TRUE,
],
],
],
'#behaviors' => [
'mybehavior',
],
];
```
Behaviors that are added via [render event](#render-events) subscribers can add
settings at the same time:
```php
$event->addBehavior('mybehavior', ['mysetting' => TRUE]);
```
All of the above approaches will make the settings available in the behavior
JavaScript in the same place.
## Render events
farmOS will trigger an event when a map is rendered. Modules can set up an
event subscriber to perform additional logic at that time, such as adding
behaviors.
For example, to add a behavior to all maps in farmOS, add the following two
files (replace `my_module` with the module name, and `mybehavior` with the
behavior name):
`my_module/my_module.services.yml`
```yaml
services:
my_module_map_render_event_subscriber:
class: Drupal\my_module\EventSubscriber\MapRenderEventSubscriber
tags:
- { name: 'event_subscriber' }
```
`my_module/src/EventSubscriber/MapRenderEventSubscriber`
```php
<?php
namespace Drupal\my_module\EventSubscriber;
use Drupal\farm_map\Event\MapRenderEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* An event subscriber for the MapRenderEvent.
*/
class MapRenderEventSubscriber implements EventSubscriberInterface {
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
MapRenderEvent::EVENT_NAME => 'onMapRender',
];
}
/**
* React to the MapRenderEvent.
*
* @param \Drupal\farm_map\Event\MapRenderEvent $event
* The MapRenderEvent.
*/
public function onMapRender(MapRenderEvent $event) {
$event->addBehavior('mybehavior');
}
}
```

View File

@ -37,6 +37,7 @@ nav:
- Quick forms: development/module/quick.md
- Entities: development/module/entities.md
- Fields: development/module/fields.md
- Maps: development/module/maps.md
- OAuth: development/module/oauth.md
- Roles: development/module/roles.md
- Services: development/module/services.md

View File

@ -1,11 +0,0 @@
langcode: en
status: true
dependencies:
enforced:
module:
- farm_map
id: geofield
label: Geofield
description: 'Copies map layer data into a form textarea.'
library: 'farm_map/behavior_geofield'
settings: { }

View File

@ -0,0 +1,11 @@
langcode: en
status: true
dependencies:
enforced:
module:
- farm_map
id: input
label: Input
description: 'Syncs editable map layer data into a form input.'
library: 'farm_map/behavior_input'
settings: { }

View File

@ -1,11 +0,0 @@
langcode: en
status: true
dependencies:
enforced:
module:
- farm_map
id: geofield_widget
label: Geofield widget
description: 'Renders a geofield widget using farmOS-map.'
behaviors: { }
options: { }

View File

@ -30,9 +30,9 @@ behavior_wkt:
dependencies:
- core/drupalSettings
- farm_map/farm_map
behavior_geofield:
behavior_input:
js:
js/farmOS.map.behaviors.geofield.js: { }
js/farmOS.map.behaviors.input.js: { }
dependencies:
- farm_map/farm_map
behavior_enable_side_panel:

View File

@ -0,0 +1,40 @@
<?php
/**
* @file
* Post update hooks for the farm_map module.
*/
use Drupal\farm_map\Entity\MapBehavior;
use Drupal\farm_map\Entity\MapType;
/**
* Generalize geofield map types and behavior.
*/
function farm_map_post_update_generalize_geofield_map_types_behavior(&$sandbox) {
// Create the new input behavior.
$input_behavior = MapBehavior::create([
'id' => 'input',
'label' => 'Input',
'description' => 'Syncs editable map layer data into a form input.',
'library' => 'farm_map/behavior_input',
'settings' => [],
'dependencies' => [
'enforced' => [
'module' => [
'farm_map',
],
],
],
]);
$input_behavior->save();
// Delete the geofield behavior.
$geofield_behavior = MapBehavior::load('geofield');
$geofield_behavior->delete();
// Delete the geofield_widget map type.
$geofield_widget_map_type = MapType::load('geofield_widget');
$geofield_widget_map_type->delete();
}

View File

@ -1,5 +1,5 @@
(function () {
farmOS.map.behaviors.geofield = {
farmOS.map.behaviors.input = {
attach: function (instance) {
instance.editAttached.then(() => {
instance.edit.wktOn('featurechange', function(wkt) {

View File

@ -24,6 +24,8 @@ class FarmMap extends RenderElement {
],
'#theme' => 'farm_map',
'#map_type' => 'default',
'#map_settings' => [],
'#behaviors' => [],
];
}
@ -37,6 +39,8 @@ class FarmMap extends RenderElement {
*
* @return array
* A renderable array representing the map.
*
* @see \Drupal\farm_map\Event\MapRenderEvent
*/
public static function preRenderMap(array $element) {
@ -66,14 +70,20 @@ class FarmMap extends RenderElement {
$element['#attached']['library'][] = 'farm_map/farmOS-map';
$element['#attached']['library'][] = 'farm_map/farm_map';
// Include map settings.
$map_settings = !empty($element['#map_settings']) ? $element['#map_settings'] : [];
// If #behaviors are included, attach each one.
foreach ($element['#behaviors'] as $behavior_name) {
/** @var \Drupal\farm_map\Entity\MapBehaviorInterface $behavior */
$behavior = \Drupal::entityTypeManager()->getStorage('map_behavior')->load($behavior_name);
if (!empty($behavior)) {
$element['#attached']['library'][] = $behavior->getLibrary();
}
}
// Include the map options.
$map_options = $map->getMapOptions();
// Add the instance settings under the map id key.
$instance_settings = array_merge_recursive($map_settings, $map_options);
$instance_settings = array_merge_recursive($element['#map_settings'], $map_options);
$element['#attached']['drupalSettings']['farm_map'][$map_id] = $instance_settings;
// Create and dispatch a MapRenderEvent.

View File

@ -0,0 +1,149 @@
<?php
namespace Drupal\farm_map\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElement;
use Drupal\geofield\GeoPHP\GeoPHPWrapper;
/**
* Form element that returns WKT rendered in a map.
*
* @FormElement("farm_map_input")
*/
class FarmMapInput extends FormElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = static::class;
return [
'#input' => TRUE,
'#process' => [
[$class, 'processElement'],
],
'#pre_render' => [
[$class, 'preRenderGroup'],
],
'#element_validate' => [
[$class, 'elementValidate'],
],
'#theme_wrappers' => ['form_element'],
// Display descriptions above the map by default.
'#description_display' => 'before',
'#map_type' => 'default',
'#map_settings' => [],
'#behaviors' => [],
'#default_value' => '',
'#display_raw_geometry' => FALSE,
'#disabled' => FALSE,
];
}
/**
* Generates the form element.
*
* @param array $element
* An associative array containing the properties and children of the
* element. Note that $element must be taken by reference here, so processed
* child elements are taken over into $form_state.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*/
public static function processElement(array $element, FormStateInterface $form_state, array &$complete_form) {
$element['#tree'] = TRUE;
// Merge provided map behaviors into defaults. Enable wkt and input
// behaviors if #disabled is not TRUE.
$default_behaviors = !$element['#disabled'] ? ['wkt', 'input'] : [];
$behaviors = array_merge($default_behaviors, $element['#behaviors']);
// Recursively merge provided map settings into defaults.
$map_settings = array_merge_recursive([
'behaviors' => [
'wkt' => [
'edit' => !$element['#disabled'],
'zoom' => TRUE,
],
],
], $element['#map_settings']);
// Define the map render array.
$element['map'] = [
'#type' => 'farm_map',
'#map_type' => $element['#map_type'],
'#map_settings' => $map_settings,
'#behaviors' => $behaviors,
];
// Add a textarea for the WKT value.
$display_raw_geometry = $element['#display_raw_geometry'];
$element_title = $element['#title'] ?? t('Geometry');
$element['value'] = [
'#type' => $display_raw_geometry ? 'textarea' : 'hidden',
'#title' => $element_title . ' ' . t('WKT'),
'#title_display' => 'invisible',
'#attributes' => [
'data-map-geometry-field' => TRUE,
],
'#disabled' => $element['#disabled'],
];
// Add default value if provided.
if (!empty($element['#default_value'])) {
$element['map']['#map_settings']['wkt'] = $element['#default_value'];
$element['value']['#default_value'] = $element['#default_value'];
}
// Return the element.
return $element;
}
/**
* Validates the form element.
*/
public static function elementValidate(&$element, FormStateInterface $form_state, &$complete_form) {
// Validate that the geometry data is valid by attempting to load it into
// GeoPHP. This uses the same logic and error message as the geofield
// module's validation constraint.
// @see Drupal\geofield\Plugin\Validation\Constraint\GeoConstraint
// @see Drupal\geofield\Plugin\Validation\Constraint\GeoConstraintValidator
$value = $element['value']['#value'];
if (!empty($value)) {
$geophp = new GeoPHPWrapper();
$valid_geometry = TRUE;
try {
if (!$geophp->load($value)) {
$valid_geometry = FALSE;
}
}
catch (\Exception $e) {
$valid_geometry = FALSE;
}
if (!$valid_geometry) {
$form_state->setError($element, t('"@value" is not a valid geospatial content.', ['@value' => $value]));
}
}
// Save the WKT string value to the overall element.
$form_state->setValueForElement($element, $value);
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if ($input === FALSE) {
return $element['#default_value'] ?: '';
}
return $input;
}
}

View File

@ -72,6 +72,8 @@ class MapRenderEvent extends Event {
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*
* @see \Drupal\farm_map\Element\FarmMap
*/
public function addBehavior(string $behavior_name, array $settings = []) {

View File

@ -61,12 +61,6 @@ class MapRenderEventSubscriber implements EventSubscriberInterface {
$event->addBehavior('wkt');
}
// Add the wkt and geofield behavior to the geofield_widget map.
if (in_array($event->getMapType()->id(), ['geofield_widget'])) {
$event->addBehavior('wkt');
$event->addBehavior('geofield');
}
// Get whether the side panel should be enabled.
$enable_side_panel = $this->configFactory->get('farm_map.settings')->get('enable_side_panel');

View File

@ -133,45 +133,30 @@ class GeofieldWidget extends GeofieldBaseWidget {
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
// Wrap the map in a collapsible details element.
// Use the farm_map_input form element.
$element['#type'] = 'farm_map_input';
// Use the geofield map type.
$element['#map_type'] = 'geofield';
// Wrap in a fieldset.
$element['#theme_wrappers'] = ['fieldset'];
// Wrap the map with a unique id for populating from files.
$field_name = $this->fieldDefinition->getName();
$field_wrapper_id = Html::getUniqueId($field_name . '_wrapper');
$element['#type'] = 'details';
$element['#title'] = $this->t('Geometry');
$element['#open'] = TRUE;
$element['#prefix'] = '<div id="' . $field_wrapper_id . '">';
$element['#suffix'] = '</div>';
// Get the current form state value. Prioritize form state over field value.
$form_value = $form_state->getValue([$field_name, $delta, 'value']);
$form_value = $form_state->getValue([$field_name, $delta]);
$field_value = $items[$delta]->value;
$current_value = $form_value ?? $field_value;
$current_value = $form_value['value'] ?? $field_value;
$element['#default_value'] = $current_value;
// Define the map render array.
$element['map'] = [
'#type' => 'farm_map',
'#map_type' => 'geofield_widget',
'#map_settings' => [
'wkt' => $current_value,
'behaviors' => [
'wkt' => [
'edit' => TRUE,
'zoom' => TRUE,
],
],
],
];
// Add a textarea for the WKT value.
// Configure to display raw geometry.
$display_raw_geometry = $this->getSetting('display_raw_geometry');
$element['value'] = [
'#type' => $display_raw_geometry ? 'textarea' : 'hidden',
'#title' => $this->t('Geometry'),
'#default_value' => $current_value,
'#attributes' => [
'data-map-geometry-field' => TRUE,
],
];
$element['#display_raw_geometry'] = $display_raw_geometry;
// Add an option to populate geometry using files field.
// The "populate_file_field" field setting must be configured and the
@ -192,9 +177,16 @@ class GeofieldWidget extends GeofieldBaseWidget {
':input[name="' . $populate_file_field . '[0][fids]"]' => ['empty' => TRUE],
],
],
'#weight' => 10,
];
}
// Override the element validation to prevent transformation of the value
// from array to string, and because Geofields already perform the same
// geometry validation.
// @see \Drupal\geofield\Plugin\Validation\GeoConstraintValidator.
$element['#element_validate'] = [];
return $element;
}
@ -281,11 +273,11 @@ class GeofieldWidget extends GeofieldBaseWidget {
$field_name = $this->fieldDefinition->getName();
$delta = $element['#delta'];
$user_input = $form_state->getUserInput();
unset($user_input[$field_name][$delta]['value']);
unset($user_input[$field_name][$delta]);
$form_state->setUserInput($user_input);
// Set the new form value.
$form_state->setValue([$field_name, $delta, 'value'], $wkt);
$form_state->setValue([$field_name, $delta], ['value' => $wkt]);
// Rebuild the form so the map widget is rebuilt with the new value.
$form_state->setRebuild(TRUE);

View File

@ -0,0 +1,8 @@
langcode: en
status: true
id: test
label: Test
description: ''
name_pattern: 'Test log [log:id]'
workflow: log_default
new_revision: true

View File

@ -0,0 +1,10 @@
name: farmOS map tests
type: module
description: Support module for farmOS map testing.
package: Testing
core_version_requirement: ^8.8 || ^9
dependencies:
- farm:farm_location
- farm:farm_map
- farm:farm_quick
- log:log

View File

@ -0,0 +1,67 @@
<?php
namespace Drupal\farm_map_test\Plugin\QuickForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\farm_quick\Plugin\QuickForm\QuickFormBase;
use Drupal\farm_quick\Traits\QuickLogTrait;
/**
* Test quick form.
*
* @QuickForm(
* id = "test",
* label = @Translation("Test quick form"),
* description = @Translation("Test quick form description."),
* helpText = @Translation("Test quick form help text."),
* permissions = {
* "create test log",
* }
* )
*/
class Test extends QuickFormBase {
use QuickLogTrait;
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Geometry field with a default value and no raw data textfield.
$form['geometry1'] = [
'#type' => 'farm_map_input',
'#title' => $this->t('Geometry 1'),
'#display_raw_geometry' => FALSE,
'#default_value' => 'POINT(-42.689862437640826 32.621823310499934)',
];
// Geometry field without a default value and a raw data field.
$form['geometry2'] = [
'#type' => 'farm_map_input',
'#title' => $this->t('Geometry 2'),
'#display_raw_geometry' => TRUE,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Create two logs.
$this->createLog([
'type' => 'test',
'name' => 'Test 1',
'geometry' => $form_state->getValue('geometry1'),
]);
$this->createLog([
'type' => 'test',
'name' => 'Test 2',
'geometry' => $form_state->getValue('geometry2'),
]);
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Drupal\Tests\farm_map\Functional;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Tests\farm_test\Functional\FarmBrowserTestBase;
/**
* Tests the farmOS map form element.
*
* @group farm
*/
class MapFormTest extends FarmBrowserTestBase {
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'farm_map_test',
];
/**
* Test the farmOS map form element.
*/
public function testMapForm() {
// Create and login a test user with permission to create test logs.
$user = $this->createUser(['create test log']);
$this->drupalLogin($user);
// Go to the test quick form and confirm that both of the geometry fields
// are visible, and only the second field's WKT text field is visible.
$this->drupalGet('quick/test');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($this->t('Geometry 1'));
$this->assertSession()->pageTextContains($this->t('Geometry 2'));
$this->assertSession()->pageTextNotContains($this->t('Geometry 1 WKT'));
$this->assertSession()->pageTextContains($this->t('Geometry 2 WKT'));
// Submit the form with a value for the second geometry.
$edit = ['geometry2[value]' => 'POINT(-45.967095060886315 32.77503850904169)'];
$this->submitForm($edit, 'Submit');
// Load logs.
$logs = \Drupal::entityTypeManager()->getStorage('log')->loadMultiple();
// Confirm that two logs were created.
$this->assertCount(2, $logs);
// Check that the first log's geometry was populated with the form field's
// default value.
$log = $logs[1];
$this->assertEquals('POINT(-42.689862437640826 32.621823310499934)', $log->get('geometry')->value);
// Check that the second log's geometry field was populated with the value
// entered into the form.
$log = $logs[2];
$this->assertEquals('POINT(-45.967095060886315 32.77503850904169)', $log->get('geometry')->value);
// Test that submitting an invalid geometry throws a form validation error.
$this->drupalGet('quick/test');
$edit = ['geometry2[value]' => 'POLYGON()'];
$this->submitForm($edit, 'Submit');
$this->assertSession()->pageTextContains($this->t('"POLYGON()" is not a valid geospatial content.'));
}
}

View File

@ -65,7 +65,7 @@ class MapRenderEventSubscriber implements EventSubscriberInterface {
$map_id = $event->getmapType()->id();
// Add behaviors/settings to default and geofield maps.
if (in_array($map_id, ['default', 'geofield', 'geofield_widget'])) {
if (in_array($map_id, ['default', 'geofield'])) {
// Add "All locations" layers.
$event->addBehavior('asset_type_layers');