geolocation-8.x-3.x-dev/src/Plugin/views/style/GeolocationStyleBase.php
src/Plugin/views/style/GeolocationStyleBase.php
<?php
namespace Drupal\geolocation\Plugin\views\style;
use Drupal\Component\Render\PlainTextOutput;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\geolocation\DataProviderManager;
use Drupal\image\Entity\ImageStyle;
use Drupal\views\Plugin\views\style\StylePluginBase;
use Drupal\views\ResultRow;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Geolocation Style Base.
*
* @package Drupal\geolocation\Plugin\views\style
*/
abstract class GeolocationStyleBase extends StylePluginBase {
use MessengerTrait;
use LoggerChannelTrait;
/**
* {@inheritdoc}
*/
protected $usesFields = TRUE;
/**
* {@inheritdoc}
*/
protected $usesRowPlugin = TRUE;
/**
* {@inheritdoc}
*/
protected $usesRowClass = FALSE;
/**
* {@inheritdoc}
*/
protected $usesGrouping = FALSE;
/**
* Data provider base.
*
* @var \Drupal\geolocation\DataProviderManager
*/
protected DataProviderManager $dataProviderManager;
/**
* The file url generator service.
*
* @var \Drupal\Core\File\FileUrlGeneratorInterface
*/
protected FileUrlGeneratorInterface $fileUrlGenerator;
/**
* {@inheritdoc}
*
* @phpstan-ignore-next-line
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, $data_provider_manager, FileUrlGeneratorInterface $file_url_generator) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->dataProviderManager = $data_provider_manager;
$this->fileUrlGenerator = $file_url_generator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): GeolocationStyleBase {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.geolocation.dataprovider'),
$container->get('file_url_generator')
);
}
/**
* {@inheritdoc}
*/
public function render(): array {
if (empty($this->options['geolocation_field'])) {
$this->messenger()->addMessage('The geolocation based view ' . $this->view->id() . ' views style was called without a geolocation field defined in the views style settings.', 'error');
return [];
}
if (empty($this->view->field[$this->options['geolocation_field']])) {
$this->messenger()->addMessage('The geolocation based view ' . $this->view->id() . ' views style was called with a non-available geolocation field defined in the views style settings.', 'error');
return [];
}
return parent::render();
}
/**
* Render array from views result row.
*
* @param \Drupal\views\ResultRow $row
* Result row.
*
* @return array
* List of location render elements.
*/
public function getLocationsFromRow(ResultRow $row): array {
$locations = [];
$icon_url = NULL;
if (
!empty($this->options['icon_field'])
&& $this->options['icon_field'] != 'none'
) {
/** @var \Drupal\views\Plugin\views\field\EntityField|null $icon_field_handler */
$icon_field_handler = $this->view->field[$this->options['icon_field']];
if (!empty($icon_field_handler)) {
$image_items = $icon_field_handler->getItems($row);
if (!empty($image_items[0]['rendered']['#item']->entity)) {
$file_uri = $image_items[0]['rendered']['#item']->entity->getFileUri();
$style = NULL;
if (!empty($image_items[0]['rendered']['#image_style'])) {
/** @var \Drupal\image\Entity\ImageStyle $style */
$style = ImageStyle::load($image_items[0]['rendered']['#image_style']);
}
if (!empty($style)) {
$icon_url = $this->fileUrlGenerator->transformRelative($style->buildUrl($file_uri));
}
else {
$icon_url = $this->fileUrlGenerator->generateString($file_uri);
}
}
}
}
elseif (!empty($this->options['marker_icon_path'])) {
$icon_token_uri = $this->viewsTokenReplace($this->options['marker_icon_path'], $this->rowTokens[$row->index]);
$icon_token_uri = $this->globalTokenReplace($icon_token_uri);
$icon_token_uri = preg_replace('/\s+/', '', $icon_token_uri);
$icon_url = $this->fileUrlGenerator->generateString($icon_token_uri);
}
try {
$data_provider = $this->dataProviderManager->createInstance($this->options['data_provider_id'], $this->options['data_provider_settings']);
}
catch (\Exception $e) {
$this->getLogger('geolocation')->critical('View with non-existing data provider called. ' . $e->getMessage());
return [];
}
foreach ($data_provider->getLocationsFromViewsRow($row, $this->view->field[$this->options['geolocation_field']]) as $location) {
if (empty($location)) {
continue;
}
$location = array_merge([
'#row' => $row,
'#title' => $this->getTitleField($row) ?? '',
'#label' => $this->getLabelField($row) ?? '',
'#weight' => $row->index,
], $location);
$location['#attributes'] = array_merge(['data-views-row-index' => $row->index], $location['#attributes'] ?? []);
$location['content'] = $this->view->rowPlugin->render($row);
// @phpstan-ignore-next-line
if ($row->_entity) {
$location['#id'] = Html::getUniqueId($row->_entity->getEntityTypeId() . '-' . $row->_entity->id() . '-' . count($locations));
$location['#attributes']['data-entity-type'] = $row->_entity->getEntityTypeId();
$location['#attributes']['data-entity-id'] = $row->_entity->id();
}
else {
$location['#id'] = $row->index . '-' . count($locations);
}
if (!empty($icon_url)) {
$location['#icon'] = $icon_url;
}
if ($this->options['marker_row_number']) {
$markerOffset = $this->view->pager->getCurrentPage() * $this->view->pager->getItemsPerPage();
$marker_row_number = (int) $markerOffset + $row->index + 1;
if (empty($location['#label'])) {
$location['#label'] = $marker_row_number;
}
else {
$location['#label'] = $location['#label'] . ': ' . $location['#label'];
}
}
$locations[] = $location;
}
foreach ($data_provider->getShapesFromViewsRow($row, $this->view->field[$this->options['geolocation_field']]) as $shape) {
if (empty($shape)) {
continue;
}
$shape = array_merge([
'#row' => $row,
'#title' => $this->getTitleField($row) ?? '',
'#label' => $this->getLabelField($row) ?? '',
'#weight' => $row->index,
], $shape);
$shape['#attributes'] = array_merge(['data-views-row-index' => $row->index], $shape['#attributes'] ?? []);
$shape['content'] = $this->view->rowPlugin->render($row);
// @phpstan-ignore-next-line
if ($row->_entity) {
$shape['#id'] = Html::getUniqueId($row->_entity->getEntityTypeId() . '-' . $row->_entity->id() . '-' . count($locations));
$shape['#attributes']['data-entity-type'] = $row->_entity->getEntityTypeId();
$shape['#attributes']['data-entity-id'] = $row->_entity->id();
}
else {
$shape['#id'] = $row->index . '-' . count($locations);
}
$locations[] = $shape;
}
return $locations;
}
/**
* {@inheritdoc}
*/
protected function defineOptions(): array {
$options = parent::defineOptions();
$options['geolocation_field'] = ['default' => ''];
$options['data_provider_id'] = ['default' => 'geolocation_field_provider'];
$options['data_provider_settings'] = ['default' => []];
$options['title_field'] = ['default' => ''];
$options['label_field'] = ['default' => ''];
$options['icon_field'] = ['default' => ''];
$options['marker_row_number'] = ['default' => FALSE];
$options['marker_icon_path'] = ['default' => ''];
return $options;
}
/**
* {@inheritdoc}
*
* @param array $form
* Form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state): void {
parent::buildOptionsForm($form, $form_state);
$labels = $this->displayHandler->getFieldLabels();
$geo_options = [];
/** @var \Drupal\geolocation\DataProviderInterface[] $data_providers */
$data_providers = [];
$title_options = [];
$label_options = [];
$icon_options = [];
$fields = $this->displayHandler->getHandlers('field');
/** @var \Drupal\views\Plugin\views\field\FieldPluginBase[] $fields */
foreach ($fields as $field_name => $field) {
$data_provider_settings = [];
if (
$this->options['geolocation_field'] == $field_name
&& !empty($this->options['data_provider_settings'])
) {
$data_provider_settings = $this->options['data_provider_settings'];
}
if ($data_provider = $this->dataProviderManager->getDataProviderByViewsField($field, $data_provider_settings)) {
$geo_options[$field_name] = $field->adminLabel() . ' (' . $data_provider->getPluginDefinition()['name'] . ')';
$data_providers[$field_name] = $data_provider;
}
if (!empty($field->options['type']) && $field->options['type'] == 'image') {
$icon_options[$field_name] = $labels[$field_name];
}
if (!empty($field->options['type']) && $field->options['type'] == 'string') {
$title_options[$field_name] = $labels[$field_name];
$label_options[$field_name] = $labels[$field_name];
}
}
$form['geolocation_field'] = [
'#title' => $this->t('Geolocation field'),
'#type' => 'select',
'#default_value' => $this->options['geolocation_field'],
'#description' => $this->t("The source of geodata for each entity."),
'#options' => $geo_options,
'#required' => TRUE,
'#ajax' => [
'callback' => [
get_class($this->dataProviderManager),
'addDataProviderSettingsFormAjax',
],
'wrapper' => 'data-provider-settings',
'effect' => 'fade',
],
];
$data_provider = NULL;
$user_input = $form_state->getUserInput();
$form_state_data_provider_id = NestedArray::getValue(
$user_input,
['style_options', 'geolocation_field']
);
if (
!empty($form_state_data_provider_id)
&& !empty($data_providers[$form_state_data_provider_id])
) {
$data_provider = $data_providers[$form_state_data_provider_id];
}
elseif (!empty($data_providers[$this->options['geolocation_field']])) {
$data_provider = $data_providers[$this->options['geolocation_field']];
}
elseif (!empty($data_providers[reset($geo_options)])) {
$data_provider = $data_providers[reset($geo_options)];
}
else {
return;
}
$form['data_provider_id'] = [
'#type' => 'value',
'#value' => $data_provider->getPluginId(),
];
$form['data_provider_settings'] = $data_provider->getSettingsForm(
$this->options['data_provider_settings'],
[
'style_options',
'map_provider_settings',
]
);
$form['data_provider_settings'] = array_replace($form['data_provider_settings'], [
'#prefix' => '<div id="data-provider-settings">',
'#suffix' => '</div>',
]);
$form['title_field'] = [
'#title' => $this->t('Title field'),
'#type' => 'select',
'#default_value' => $this->options['title_field'],
'#description' => $this->t("The title is displayed on hover of the marker/shape. Field type must be 'string'."),
'#options' => $title_options,
'#empty_value' => 'none',
];
$form['label_field'] = [
'#title' => $this->t('Label field'),
'#type' => 'select',
'#default_value' => $this->options['label_field'],
'#description' => $this->t("The label is permanently displayed above the marker/shape . Field type must be 'string'."),
'#options' => $label_options,
'#empty_value' => 'none',
];
/*
* Advanced settings
*/
$form['advanced_settings'] = [
'#type' => 'fieldset',
'#title' => $this->t('Advanced settings'),
];
if ($icon_options) {
$form['icon_field'] = [
'#group' => 'style_options][advanced_settings',
'#title' => $this->t('Icon source field'),
'#type' => 'select',
'#default_value' => $this->options['icon_field'],
'#description' => $this->t("Optional image (field) to use as icon."),
'#options' => $icon_options,
'#empty_value' => 'none',
'#process' => [
['\Drupal\Core\Render\Element\RenderElementBase', 'processGroup'],
['\Drupal\Core\Render\Element\Select', 'processSelect'],
],
'#pre_render' => [
['\Drupal\Core\Render\Element\RenderElementBase', 'preRenderGroup'],
],
];
}
$form['marker_icon_path'] = [
'#group' => 'style_options][advanced_settings',
'#type' => 'textfield',
'#title' => $this->t('Marker icon path'),
'#description' => $this->t('Set relative or absolute path to custom marker icon. Tokens & Views replacement patterns supported. Empty for default.'),
'#default_value' => $this->options['marker_icon_path'],
];
$form['marker_row_number'] = [
'#group' => 'style_options][advanced_settings',
'#title' => $this->t('Show views result row number in marker'),
'#type' => 'checkbox',
'#default_value' => $this->options['marker_row_number'],
];
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(mixed &$form, FormStateInterface $form_state): void {
parent::validateOptionsForm($form, $form_state);
$triggering_element = $form_state->getTriggeringElement();
// https://www.drupal.org/project/drupal/issues/3137947
if (
$triggering_element['#name'] == 'style_options[map_provider_id]'
&& $triggering_element['#value'] != $triggering_element['#default_value']
) {
$form_state->clearErrors();
}
}
/**
* Get title value if present.
*
* @param \Drupal\views\ResultRow $row
* Result Row.
*
* @return string|null
* Title.
*/
public function getTitleField(ResultRow $row): ?string {
if (
!empty($this->options['title_field'])
&& $this->options['title_field'] != 'none'
) {
$title_field = $this->options['title_field'];
if (!empty($this->rendered_fields[$row->index][$title_field])) {
return trim(PlainTextOutput::renderFromHtml($this->rendered_fields[$row->index][$title_field]));
}
elseif (!empty($this->view->field[$title_field])) {
return trim(PlainTextOutput::renderFromHtml($this->view->field[$title_field]->render($row)));
}
}
return NULL;
}
/**
* Get label value if present.
*
* @param \Drupal\views\ResultRow $row
* Result Row.
*
* @return string|null
* Label.
*/
public function getLabelField(ResultRow $row): ?string {
if (
!empty($this->options['label_field'])
&& $this->options['label_field'] != 'none'
) {
$label_field = $this->options['label_field'];
if (!empty($this->rendered_fields[$row->index][$label_field])) {
return trim(PlainTextOutput::renderFromHtml($this->rendered_fields[$row->index][$label_field]));
}
elseif (!empty($this->view->field[$label_field])) {
return trim(PlainTextOutput::renderFromHtml($this->view->field[$label_field]->render($row)));
}
}
return NULL;
}
}
