View source
<?php
namespace Drupal\leaflet_views\Plugin\views\style;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\Form\FormStateInterface;
use Drupal\leaflet_views\Controller\LeafletAjaxPopupController;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\Entity\Index;
use Drupal\Core\Url;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\style\StylePluginBase;
use Drupal\views\ViewExecutable;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Leaflet\LeafletService;
use Drupal\Component\Utility\Html;
use Drupal\Core\Utility\LinkGeneratorInterface;
use Drupal\leaflet\LeafletSettingsElementsTrait;
use Drupal\views\Plugin\views\PluginBase;
use Drupal\views\Views;
class LeafletMap extends StylePluginBase implements ContainerFactoryPluginInterface {
use LeafletSettingsElementsTrait;
protected $defaultSettings;
protected $entitySource;
protected $entityType;
protected $entityInfo;
protected $usesFields = TRUE;
protected $usesRowPlugin = TRUE;
protected $entityManager;
protected $entityFieldManager;
protected $entityDisplay;
protected $currentUser;
protected $messenger;
protected $renderer;
protected $moduleHandler;
protected $leafletService;
protected $link;
protected $viewFields = [];
protected $fieldTypeManager;
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_manager, EntityFieldManagerInterface $entity_field_manager, EntityDisplayRepositoryInterface $entity_display, AccountInterface $current_user, MessengerInterface $messenger, RendererInterface $renderer, ModuleHandlerInterface $module_handler, LeafletService $leaflet_service, LinkGeneratorInterface $link_generator, FieldTypePluginManagerInterface $field_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->defaultSettings = self::getDefaultSettings();
$this->entityManager = $entity_manager;
$this->entityFieldManager = $entity_field_manager;
$this->entityDisplay = $entity_display;
$this->currentUser = $current_user;
$this->messenger = $messenger;
$this->renderer = $renderer;
$this->moduleHandler = $module_handler;
$this->leafletService = $leaflet_service;
$this->link = $link_generator;
$this->fieldTypeManager = $field_type_manager;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('entity_type.manager'), $container
->get('entity_field.manager'), $container
->get('entity_display.repository'), $container
->get('current_user'), $container
->get('messenger'), $container
->get('renderer'), $container
->get('module_handler'), $container
->get('leaflet.service'), $container
->get('link_generator'), $container
->get('plugin.manager.field.field_type'));
}
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
if (!empty($options['entity_source']) && $options['entity_source'] != '__base_table') {
$handler = $this->displayHandler
->getHandler('relationship', $options['entity_source']);
$this->entitySource = $options['entity_source'];
$data = Views::viewsData();
if (($table = $data
->get($handler->definition['base'])) && !empty($table['table']['entity type'])) {
try {
$this->entityInfo = $this->entityManager
->getDefinition($table['table']['entity type']);
$this->entityType = $this->entityInfo
->id();
} catch (\Exception $e) {
watchdog_exception('geofield_map', $e);
}
}
}
else {
$this->entitySource = '__base_table';
$base_tables = array_keys($view
->getBaseTables());
$base_table = reset($base_tables);
if ($this->entityInfo = $view
->getBaseEntityType()) {
$this->entityType = $this->entityInfo
->id();
return;
}
if (!isset($this->entityType)) {
$index_id = substr($base_table, 17);
$index = Index::load($index_id);
foreach ($index
->getDatasources() as $datasource) {
if ($datasource instanceof DatasourceInterface) {
$this->entityType = $datasource
->getEntityTypeId();
try {
$this->entityInfo = $this->entityManager
->getDefinition($this->entityType);
} catch (\Exception $e) {
watchdog_exception('leaflet', $e);
}
}
}
}
}
}
public function getFieldValue($index, $field) {
$this->view->row_index = $index;
$value = isset($this->view->field[$field]) ? $this->view->field[$field]
->getValue($this->view->result[$index]) : NULL;
unset($this->view->row_index);
return $value;
}
protected function getAvailableDataSources() {
$fields_geo_data = [];
foreach ($this->displayHandler
->getHandlers('field') as $field_id => $handler) {
$label = $handler
->adminLabel() ?: $field_id;
$this->viewFields[$field_id] = $label;
if (is_a($handler, '\\Drupal\\views\\Plugin\\views\\field\\EntityField')) {
try {
$entity_type = $handler
->getEntityType();
} catch (\Exception $e) {
$entity_type = NULL;
}
$field_storage_definitions = $this->entityFieldManager
->getFieldStorageDefinitions($entity_type);
$field_storage_definition = $field_storage_definitions[$handler->definition['field_name']];
$type = $field_storage_definition
->getType();
try {
$definition = $this->fieldTypeManager
->getDefinition($type);
if (is_a($definition['class'], '\\Drupal\\geofield\\Plugin\\Field\\FieldType\\GeofieldItem', TRUE)) {
$fields_geo_data[$field_id] = $label;
}
} catch (\Exception $e) {
watchdog_exception("Leaflet Map - Get Available data sources", $e);
}
}
}
return $fields_geo_data;
}
protected function getAvailableEntitySources() {
if ($base_entity_type = $this->view
->getBaseEntityType()) {
$label = $base_entity_type
->getLabel();
}
else {
$base_tables = array_keys($this->view
->getBaseTables());
$label = $base_tables[0] ?? $this
->t('Unknown');
}
$options = [
'__base_table' => new TranslatableMarkup('View Base Entity (@entity_type)', [
'@entity_type' => $label,
]),
];
$data = Views::viewsData();
foreach ($this->displayHandler
->getHandlers('relationship') as $relationship_id => $handler) {
if (($table = $data
->get($handler->definition['base'])) && !empty($table['table']['entity type'])) {
try {
$entity_type = $this->entityManager
->getDefinition($table['table']['entity type']);
} catch (\Exception $e) {
$entity_type = NULL;
}
$options[$relationship_id] = new TranslatableMarkup('@relationship (@entity_type)', [
'@relationship' => $handler
->adminLabel(),
'@entity_type' => $entity_type
->getLabel(),
]);
}
}
return $options;
}
protected function getEntitySourceEntityInfo($source) {
if (!empty($source) && $source != '__base_table') {
$handler = $this->displayHandler
->getHandler('relationship', $source);
$data = Views::viewsData();
if (($table = $data
->get($handler->definition['base'])) && !empty($table['table']['entity type'])) {
try {
return $this->entityManager
->getDefinition($table['table']['entity type']);
} catch (\Exception $e) {
$entity_type = NULL;
}
}
}
return $this->view
->getBaseEntityType();
}
public function evenEmpty() {
return TRUE;
}
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
if ($form_state
->get('entity_source')) {
$this->options['entity_source'] = $form_state
->get('entity_source');
$this->entityInfo = $this
->getEntitySourceEntityInfo($this->options['entity_source']);
$this->entityType = $this->entityInfo
->id();
$this->entitySource = $this->options['entity_source'];
}
parent::buildOptionsForm($form, $form_state);
$form['#tree'] = TRUE;
$form['#attached'] = [
'library' => [
'leaflet/general',
],
];
$fields_geo_data = $this
->getAvailableDataSources();
if (!count($fields_geo_data)) {
$form['error'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => $this
->t('Please add at least one Geofield to the View and come back here to set it as Data Source.'),
'#attributes' => [
'class' => [
'leaflet-warning',
],
],
];
return;
}
$wrapper_id = 'leaflet-map-views-style-options-form-wrapper';
$form['#prefix'] = '<div id="' . $wrapper_id . '">';
$form['#suffix'] = '</div>';
$form['data_source'] = [
'#type' => 'select',
'#title' => $this
->t('Data Source'),
'#description' => $this
->t('Which field contains geodata?'),
'#options' => $fields_geo_data,
'#default_value' => $this->options['data_source'],
'#required' => TRUE,
];
$entity_sources = $this
->getAvailableEntitySources();
if (count($entity_sources) == 1) {
$form['entity_source'] = [
'#type' => 'value',
'#value' => key($entity_sources),
];
}
else {
$form['entity_source'] = [
'#type' => 'select',
'#title' => new TranslatableMarkup('Entity Source'),
'#description' => new TranslatableMarkup('Select which Entity should be used as Leaflet Mapping base Entity.<br><u>Leave as "View Base Entity" to rely on default Views behaviour, and don\'t specifically needed otherwise</u>.'),
'#options' => $entity_sources,
'#default_value' => !empty($this->options['entity_source']) ? $this->options['entity_source'] : '__base_table',
'#ajax' => [
'wrapper' => $wrapper_id,
'callback' => [
static::class,
'optionsFormEntitySourceSubmitAjax',
],
'trigger_as' => [
'name' => 'entity_source_submit',
],
],
];
$form['entity_source_submit'] = [
'#type' => 'submit',
'#value' => new TranslatableMarkup('Update Entity Source'),
'#name' => 'entity_source_submit',
'#submit' => [
[
static::class,
'optionsFormEntitySourceSubmit',
],
],
'#validate' => [],
'#limit_validation_errors' => [
[
'style_options',
'entity_source',
],
],
'#attributes' => [
'class' => [
'js-hide',
],
],
'#ajax' => [
'wrapper' => $wrapper_id,
'callback' => [
static::class,
'optionsFormEntitySourceSubmitAjax',
],
],
];
}
$form['name_field'] = [
'#type' => 'select',
'#title' => $this
->t('Title Field'),
'#description' => $this
->t('Choose the field which will appear as a title on tooltips.'),
'#options' => array_merge([
'' => ' - None - ',
], $this->viewFields),
'#default_value' => $this->options['name_field'],
];
$desc_options = array_merge([
'' => ' - None - ',
], $this->viewFields);
if ($this->entityType) {
$desc_options += [
'#rendered_entity' => $this
->t('< @entity entity >', [
'@entity' => $this->entityType,
]),
'#rendered_entity_ajax' => $this
->t('< @entity entity via ajax >', [
'@entity' => $this->entityType,
]),
'#rendered_view_fields' => $this
->t('# Rendered View Fields (with field label, format, classes, etc)'),
];
}
$form['description_field'] = [
'#type' => 'select',
'#title' => $this
->t('Description Field'),
'#description' => $this
->t('Choose the field or rendering method which will appear as a description on tooltips or popups.'),
'#required' => FALSE,
'#options' => $desc_options,
'#default_value' => $this->options['description_field'],
];
if ($this->entityType) {
$view_mode_options = [];
foreach ($this->entityDisplay
->getViewModes($this->entityType) as $key => $view_mode) {
$view_mode_options[$key] = $view_mode['label'];
}
$form['view_mode'] = [
'#type' => 'select',
'#title' => $this
->t('View mode'),
'#description' => $this
->t('View mode the entity will be displayed in the Infowindow.'),
'#options' => $view_mode_options,
'#default_value' => $this->options['view_mode'],
'#states' => [
'visible' => [
':input[name="style_options[description_field]"]' => [
[
'value' => '#rendered_entity',
],
[
'value' => '#rendered_entity_ajax',
],
],
],
],
];
}
$this
->generateMapGeneralSettings($form, $this->options);
$this
->setResetMapControl($form, $this->options);
$map_position_options = $this->options['map_position'];
$form['map_position'] = $this
->generateMapPositionElement($map_position_options);
$form['weight'] = $this
->generateWeightElement($this->options['weight']);
$icon_options = $this->options['icon'];
$form['icon'] = $this
->generateIconFormElement($icon_options);
$this
->setMapMarkerclusterElement($form, $this->options);
$this
->setMapPathOptionsElement($form, $this->options);
$this
->setGeocoderMapControl($form, $this->options);
}
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
parent::validateOptionsForm($form, $form_state);
$style_options = $form_state
->getValue('style_options');
if (!empty($style_options['height']) && (!is_numeric($style_options['height']) || $style_options['height'] <= 0)) {
$form_state
->setError($form['height'], $this
->t('Map height needs to be a positive number.'));
}
}
public static function optionsFormEntitySourceSubmit(array $form, FormStateInterface $form_state) {
$parents = $form_state
->getTriggeringElement()['#parents'];
array_pop($parents);
array_push($parents, 'entity_source');
$form_state
->set('entity_source', $form_state
->getValue($parents));
$form_state
->setRebuild(TRUE);
}
public static function optionsFormEntitySourceSubmitAjax(array $form, FormStateInterface $form_state) {
$triggering_element = $form_state
->getTriggeringElement();
$array_parents = $triggering_element['#array_parents'];
array_pop($array_parents);
return NestedArray::getValue($form, $array_parents);
}
public function render() {
$data = [];
$build_for_bubbleable_metadata = [];
$leaflet_map_style = !isset($this->options['leaflet_map']) ? $this->options['map'] : $this->options['leaflet_map'];
$map = leaflet_map_get_info($leaflet_map_style);
$this
->setAdditionalMapOptions($map, $this->options);
$map['id'] = Html::getUniqueId("leaflet_map_view_" . $this->view
->id() . '_' . $this->view->current_display);
if ($geofield_name = $this->options['data_source']) {
$this
->renderFields($this->view->result);
foreach ($this->view->result as $id => $result) {
$geofield_value = (array) $this
->getFieldValue($result->index, $geofield_name);
if (!empty($geofield_value)) {
$features = $this->leafletService
->leafletProcessGeofield($geofield_value);
if (!empty($result->_entity)) {
$entity = $result->_entity;
}
elseif (isset($result->_object)) {
$entity_adapter = $result->_object;
if ($entity_adapter instanceof EntityAdapter) {
$entity = $entity_adapter
->getValue();
}
}
if (isset($entity)) {
if (!isset($map['geofield_cardinality'])) {
try {
$geofield_entity = $entity
->get($geofield_name);
$map['geofield_cardinality'] = $geofield_entity
->getFieldDefinition()
->getFieldStorageDefinition()
->getCardinality();
} catch (\Exception $e) {
$map['geofield_cardinality'] = -1;
}
}
$entity_type = $entity
->getEntityTypeId();
$entity_type_langcode_attribute = $entity_type . '_field_data_langcode';
$view = $this->view;
$rendering_language = $view->display_handler
->getOption('rendering_language');
$dynamic_renderers = [
'***LANGUAGE_entity_translation***' => 'TranslationLanguageRenderer',
'***LANGUAGE_entity_default***' => 'DefaultLanguageRenderer',
];
if (isset($dynamic_renderers[$rendering_language])) {
$langcode = isset($result->{$entity_type_langcode_attribute}) ? $result->{$entity_type_langcode_attribute} : $entity
->language()
->getId();
}
else {
if (strpos($rendering_language, '***LANGUAGE_') !== FALSE) {
$langcode = PluginBase::queryLanguageSubstitutions()[$rendering_language];
}
else {
$langcode = $rendering_language;
}
}
switch ($this->options['description_field']) {
case '#rendered_entity':
$build = $this->entityManager
->getViewBuilder($entity
->getEntityTypeId())
->view($entity, $this->options['view_mode'], $langcode);
$render_context = new RenderContext();
$description = $this->renderer
->executeInRenderContext($render_context, function () use (&$build) {
return $this->renderer
->render($build, TRUE);
});
if (!$render_context
->isEmpty()) {
$render_context
->update($build_for_bubbleable_metadata);
}
break;
case '#rendered_entity_ajax':
$parameters = [
'entity_type' => $entity_type,
'entity' => $entity
->id(),
'view_mode' => $this->options['view_mode'],
'langcode' => $langcode,
];
$url = Url::fromRoute('leaflet_views.ajax_popup', $parameters);
$description = sprintf('<div class="leaflet-ajax-popup" data-leaflet-ajax-popup="%s" %s></div>', $url
->toString(), LeafletAjaxPopupController::getPopupIdentifierAttribute($entity_type, $entity
->id(), $this->options['view_mode'], $langcode));
$map['settings']['ajaxPoup'] = TRUE;
break;
case '#rendered_view_fields':
$render_row = [
"markup" => $this->view->rowPlugin
->render($result),
];
$description = !empty($this->options['description_field']) ? $this->renderer
->renderPlain($render_row) : '';
break;
default:
$description = !empty($this->options['description_field']) ? $this->rendered_fields[$result->index][$this->options['description_field']] : '';
}
if (!empty($map['icon'])) {
$this->options['icon'] = $this->options['icon'] ?: [];
foreach ($this->options['icon'] as $k => $icon_option) {
if (empty($icon_option) || is_array($icon_option) && $this->leafletService
->multipleEmpty($icon_option)) {
unset($this->options['icon'][$k]);
}
}
$this->options['icon'] = array_replace($map['icon'], $this->options['icon']);
}
$tokens = [];
foreach ($this->rendered_fields[$result->index] as $field_name => $field_value) {
$tokens[$field_name] = $field_value;
$tokens["{{ {$field_name} }}"] = $field_value;
}
$icon_type = isset($this->options['icon']['iconType']) ? $this->options['icon']['iconType'] : 'marker';
foreach ($features as &$feature) {
$feature['entity_id'] = $entity
->id();
$feature['weight'] = !empty($this->options['weight']) ? intval(str_replace([
"\n",
"\r",
], "", $this
->viewsTokenReplace($this->options['weight'], $tokens))) : $id;
if (isset($description)) {
$feature['popup'] = $description;
}
if ($this->options['name_field']) {
$feature['label'] = !empty($this->options['name_field']) ? Html::decodeEntities($this->rendered_fields[$result->index][$this->options['name_field']]) : '';
}
if ($feature['type'] === 'point' && isset($this->options['icon'])) {
$feature['icon'] = $this->options['icon'];
if (!empty($this->options["icon"]["iconSize"]["x"])) {
$feature['icon']["iconSize"]["x"] = $this
->viewsTokenReplace($this->options["icon"]["iconSize"]["x"], $tokens);
}
if (!empty($this->options["icon"]["iconSize"]["y"])) {
$feature['icon']["iconSize"]["y"] = $this
->viewsTokenReplace($this->options["icon"]["iconSize"]["y"], $tokens);
}
if (!empty($this->options["icon"]["shadowSize"]["x"])) {
$feature['icon']["shadowSize"]["x"] = $this
->viewsTokenReplace($this->options["icon"]["shadowSize"]["x"], $tokens);
}
if (!empty($this->options["icon"]["shadowSize"]["y"])) {
$feature['icon']["shadowSize"]["y"] = $this
->viewsTokenReplace($this->options["icon"]["shadowSize"]["y"], $tokens);
}
switch ($icon_type) {
case 'html':
$feature['icon']['html'] = str_replace([
"\n",
"\r",
], "", $this
->viewsTokenReplace($this->options['icon']['html'], $tokens));
$feature['icon']['html_class'] = $this->options['icon']['html_class'];
break;
case 'circle_marker':
$feature['icon']['options'] = str_replace([
"\n",
"\r",
], "", $this
->viewsTokenReplace($this->options['icon']['circle_marker_options'], $tokens));
break;
default:
if (!empty($this->options['icon']['iconUrl'])) {
$feature['icon']['iconUrl'] = str_replace([
"\n",
"\r",
], "", $this
->viewsTokenReplace($this->options['icon']['iconUrl'], $tokens));
if (!empty($feature['icon']['iconUrl'])) {
$feature['icon']['iconUrl'] = $this->leafletService
->pathToAbsolute($feature['icon']['iconUrl']);
}
}
if (!empty($this->options['icon']['shadowUrl'])) {
$feature['icon']['shadowUrl'] = str_replace([
"\n",
"\r",
], "", $this
->viewsTokenReplace($this->options['icon']['shadowUrl'], $tokens));
if (!empty($feature['icon']['shadowUrl'])) {
$feature['icon']['shadowUrl'] = $this->leafletService
->pathToAbsolute($feature['icon']['shadowUrl']);
}
}
$this->leafletService
->setFeatureIconSizesIfEmptyOrInvalid($feature);
break;
}
}
if ($feature['type'] !== 'point') {
$feature['path'] = str_replace([
"\n",
"\r",
], "", $this
->viewsTokenReplace($this->options['path'], $tokens));
}
$feature['icon']['className'] = !empty($this->options['icon']['className']) ? str_replace([
"\n",
"\r",
], "", $this
->viewsTokenReplace($this->options['icon']['className'], $tokens)) : '';
$this->moduleHandler
->alter('leaflet_views_feature', $feature, $result, $this->view->rowPlugin);
}
$data = array_merge($data, $features);
}
}
}
}
if (empty($data) && !empty($this->options['hide_empty_map'])) {
return [];
}
uasort($data, [
'Drupal\\Component\\Utility\\SortArray',
'sortByWeightElement',
]);
$js_settings = [
'map' => $map,
'features' => $data,
];
$this->moduleHandler
->alter('leaflet_map_view_style', $js_settings, $this);
$map_height = !empty($this->options['height']) ? $this->options['height'] . $this->options['height_unit'] : '';
$element = $this->leafletService
->leafletRenderMap($js_settings['map'], $js_settings['features'], $map_height);
if (isset($map['settings']['ajaxPoup']) && $map['settings']['ajaxPoup'] == TRUE) {
$build_for_bubbleable_metadata['#attached']['library'][] = 'core/drupal.ajax';
}
BubbleableMetadata::createFromRenderArray($element)
->merge(BubbleableMetadata::createFromRenderArray($build_for_bubbleable_metadata))
->applyTo($element);
return $element;
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['data_source'] = [
'default' => '',
];
$options['entity_source'] = [
'default' => '__base_table',
];
$options['name_field'] = [
'default' => '',
];
$options['description_field'] = [
'default' => '',
];
$options['view_mode'] = [
'default' => 'full',
];
$leaflet_map_default_settings = [];
foreach (self::getDefaultSettings() as $k => $setting) {
$leaflet_map_default_settings[$k] = [
'default' => $setting,
];
}
return $options + $leaflet_map_default_settings;
}
}