Initial commit - Typo3 11.5.41

This commit is contained in:
Matteo Gallo
2026-07-03 17:53:31 +02:00
commit 5ca4743197
6811 changed files with 568848 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2023 Alexander Bigga <alexander@bigga.de>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
namespace Bobosch\OdsOsm\Backend\FormDataProvider;
/**
* This class is inspired by the FlexFormManipulation::class of the
* news extension by Georg Ringer
*/
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
/**
* Manipulate flexforms of ods_osm_pi to ensure only available
* tables are allowed to select records.
*/
class FlexFormManipulation implements FormDataProviderInterface
{
/**
* Manipulate data of ods_osm_pi plugin.
*
* @param array $result
* @return array
*/
public function addData(array $result): array
{
if ($result['tableName'] === 'tt_content'
&& $result['databaseRow']['CType'] === 'list'
&& $result['databaseRow']['list_type'] === 'ods_osm_pi1'
&& is_array($result['processedTca']['columns']['pi_flexform']['config']['ds'])
) {
// get flexform data structure
$dataStructure = $result['processedTca']['columns']['pi_flexform']['config']['ds'];
// check / manipulate data structure
$dataStructure = $this->fixAllowedTables($dataStructure);
// set checked data structure
$result['processedTca']['columns']['pi_flexform']['config']['ds'] = $dataStructure;
}
return $result;
}
/**
* Check for optionally installed extensions and add the
* corresponding tables to the list of allowed tables for marker.
*
* @param array &$dataStructure flexform structure
* @return array Modified structure
*/
protected function fixAllowedTables(array $dataStructure): array
{
$markerAllowedTables = [ 'fe_users', 'fe_groups', 'pages', 'sys_category', 'tx_odsosm_track', 'tx_odsosm_vector' ];
$markerPopupInitialAllowedTables = [ 'fe_users' ];
$extensions = [
'tt_address' => 'tt_address',
'calendarize' => 'tx_calendarize_domain_model_event'
];
foreach ($extensions as $extension => $table) {
if (ExtensionManagementUtility::isLoaded($extension)) {
$markerAllowedTables[] = $table;
$markerPopupInitialAllowedTables[] = $table;
}
}
// set manipulated value for marker -> config -> allowed
if ($dataStructure['sheets']['sDEF']['ROOT']['el']['marker']['config']['allowed'] ?? false) {
$dataStructure['sheets']['sDEF']['ROOT']['el']['marker']['config']['allowed'] = implode(',', $markerAllowedTables);
}
// set manipulated value for marker_popup_initial -> config -> allowed
if ($dataStructure['sheets']['sDEF']['ROOT']['el']['marker_popup_initial']['config']['allowed'] ?? false) {
$dataStructure['sheets']['sDEF']['ROOT']['el']['marker_popup_initial']['config']['allowed'] = implode(',', $markerPopupInitialAllowedTables);
}
return $dataStructure;
}
}

View File

@@ -0,0 +1,663 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2010 Robert Heel <typo3@bobosch.de>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
namespace Bobosch\OdsOsm\Controller;
use Bobosch\OdsOsm\Div;
use Bobosch\OdsOsm\Provider\BaseProvider;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Plugin\AbstractPlugin;
/**
* Plugin 'OpenStreetMap' for the 'ods_osm' extension.
*
* @author Robert Heel <typo3@bobosch.de>
* @package TYPO3
* @subpackage tx_odsosm
*/
class PluginController extends AbstractPlugin
{
/**
* Same as class name
*
* @var string
*/
public $prefixId = 'tx_odsosm_pi1';
/**
* The extension key.
*
* @var string
*/
public $extKey = 'ods_osm';
public $config;
public $hooks;
public $lats = [];
public $lons = [];
/** @var ConnectionPool */
public $connectionPool = null;
/** @var BaseProvider */
protected $library;
public function init($conf): void
{
$this->pi_setPiVarDefaults();
$this->pi_loadLL();
$this->pi_initPIflexForm(); // Init FlexForm configuration for plugin
$this->hooks = [];
if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ods_osm']['class.tx_odsosm_pi1.php'] ?? null)) {
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ods_osm']['class.tx_odsosm_pi1.php'] as $classRef) {
$this->hooks[] = GeneralUtility::makeInstance($classRef);
}
}
$this->connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
/* --------------------------------------------------
Configuration may be done in different places
- FlexForm ($flex)
- TypoScript ($conf)
- extension settings
-------------------------------------------------- */
$flex = [];
$options = [
'cluster',
'cluster_radius',
'height',
'lat',
'layer',
'leaflet_layer',
'library',
'lon',
'marker',
'marker_popup_initial',
'mouse_position',
'openlayers_layer',
'base_layer',
'overlays',
'overlays_active',
'position',
'show_layerswitcher',
'layerswitcher_activationMode',
'show_scalebar',
'show_fullscreen',
'show_popups',
'staticmap_layer',
'use_coords_only_nomarker',
'width',
'zoom',
'enable_scrollwheelzoom',
'enable_dragging',
];
// fill flex array, if there is a flexform data available
if ($this->cObj->data['pi_flexform'] ?? null) {
foreach ($options as $option) {
$value = $this->pi_getFFvalue($this->cObj->data['pi_flexform'] ?? null, $option, 'sDEF');
switch ($option) {
case 'lat':
case 'lon':
if ($value != 0) {
$flex[$option] = $value;
}
break;
case 'marker':
case 'marker_popup_initial':
$flex[$option] = $this->splitGroup($value, 'tt_address');
break;
default:
$flex[$option] = $value;
break;
}
}
if ($flex['library'] == 'staticmap' && !empty($flex['staticmap_layer'])) {
$flex['layer'] = $flex['staticmap_layer'];
} elseif (!empty($flex['base_layer'])) {
$flex['layer'] = $flex['base_layer'];
}
}
// merge configs together into $this->config
// 1. get extension configuration
$this->config = Div::getConfig();
// 2. get TypoScript settings
ArrayUtility::mergeRecursiveWithOverrule($this->config, $conf);
// 3. merge Flexform settings, but skip empty values.
ArrayUtility::mergeRecursiveWithOverrule($this->config, $flex, true, false);
if (!is_array($this->config['marker'] ?? null)) {
$this->config['marker'] = [];
}
if (is_array($conf['marker.'])) {
foreach ($conf['marker.'] as $name => $value) {
if (is_string($value) && !empty($value)) {
if (!is_array($this->config['marker'][$name] ?? null)) {
$this->config['marker'][$name] = [];
}
$this->config['marker'][$name] = array_merge($this->config['marker'][$name], explode(',', $value));
}
}
}
$this->config['layer'] = explode(',', $this->config['layer'] . (!empty($this->config['overlays']) ? ',' . $this->config['overlays'] : ''));
if (is_numeric($this->config['height'])) {
$this->config['height'] .= 'px';
}
if (is_numeric($this->config['width'])) {
$this->config['width'] .= 'px';
}
if ($this->config['show_layerswitcher'] ?? false) {
$this->config['layers_visible'] = [];
} else {
$this->config['layers_visible'] = $this->config['layer'];
}
if ($this->config['external_control'] ?? false) {
if (GeneralUtility::_GP('lon')) {
$this->config['lon'] = GeneralUtility::_GP('lon');
}
if (GeneralUtility::_GP('lat')) {
$this->config['lat'] = GeneralUtility::_GP('lat');
}
if (GeneralUtility::_GP('zoom')) {
$this->config['zoom'] = GeneralUtility::_GP('zoom');
}
if (GeneralUtility::_GP('layers')) {
$this->config['layers_visible'] = explode(',', GeneralUtility::_GP('layers'));
}
if (GeneralUtility::_GP('records')) {
$this->config['marker'] = $this->splitGroup(GeneralUtility::_GP('records'), 'tt_address');
}
}
// If EXT:calendarize is installed and the single view is called, we try to fetch the right event.
if (ExtensionManagementUtility::isLoaded('calendarize')) {
if (GeneralUtility::_GP('tx_calendarize_calendar')['index'] ?? false) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_calendarize_domain_model_index');
$result = $queryBuilder
->select('foreign_uid')
->from('tx_calendarize_domain_model_index')
->where(
$queryBuilder->expr()->eq(
'tx_calendarize_domain_model_index.uid',
$queryBuilder->createNamedParameter((int) GeneralUtility::_GP('tx_calendarize_calendar')['index'], Connection::PARAM_INT)
)
)
->setMaxResults(1)
->executeQuery();
if ($row = $result->fetchAssociative()) {
$this->config['marker']['tx_calendarize_domain_model_event'][] = $row['foreign_uid'];
}
}
}
$this->config['id'] = 'osm_' . ($this->cObj->data['uid'] ?? uniqid());
$this->config['marker'] = $this->extractGroup($this->config['marker']);
// Show this marker's popup intially
if (is_array($this->config['marker_popup_initial'])) {
foreach ($this->config['marker_popup_initial'] as $table => $records) {
foreach ($records as $uid) {
if (isset($this->config['marker'][$table][$uid])) {
$this->config['marker'][$table][$uid]['initial_popup'] = true;
}
}
}
}
// Library
if (empty($this->config['library'])) {
$this->config['library'] = 'leaflet';
}
$this->library = GeneralUtility::makeInstance('Bobosch\\OdsOsm\\Provider\\' . GeneralUtility::underscoredToUpperCamelCase($this->config['library']));
$this->library->init($this->config);
$this->library->cObj = $this->cObj;
}
/**
* The main method of the PlugIn
*
* @param string $content : The PlugIn content
* @param array $conf : The PlugIn configuration
*
* @return string The content that is displayed on the website
*/
public function main($content, $conf)
{
$this->init($conf);
if ($this->config['marker'] || $this->config['no_marker']) {
$content = $this->getMap();
}
return $this->pi_wrapInBaseClass($content);
}
public function splitGroup($group, $default = '')
{
$groups = explode(',', $group);
foreach ($groups as $group) {
$item = GeneralUtility::revExplode('_', $group, 2);
if (count($item) == 1) {
$record_ids[$default][] = $item[0];
} else {
$record_ids[$item[0]][] = $item[1];
}
}
return ($record_ids);
}
private function extractGroup($record_ids)
{
$tables = Div::getTableConfig();
// if no markers are set, select current page to find records on it
if (count($record_ids) == 0) {
//@extensionScannerIgnoreLine
$record_ids['pages'] = [$GLOBALS['TSFE']->id];
}
// get all marker records on configured page.
if (!empty($record_ids['pages'])) {
foreach (array_keys($tables) as $table) {
if ($table != 'tt_content') {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$result = $queryBuilder
->select($table . '.uid')
->from($table)->where($queryBuilder->expr()->in(
$table . '.pid',
$queryBuilder->createNamedParameter(
$record_ids['pages'],
Connection::PARAM_INT_ARRAY
)
))->executeQuery();
while ($resArray = $result->fetchAssociative()) {
if (!in_array($resArray['uid'], $record_ids[$table] ?? [])) {
$record_ids[$table][] = $resArray['uid'];
}
}
}
}
}
// get marker records from db
$records = [];
foreach ($record_ids as $table => $items) {
$tc = $tables[$table] ?? [];
$connection = $this->connectionPool
->getConnectionForTable($table)
->createSchemaManager()
->tablesExist([$table]);
// table seems not available --> break to avoid exceptions
if ($connection === false) {
break;
}
foreach ($items as $item) {
$item = (int) $item;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
// hint: language overlay e.g. of tt_address records is done automatically
$result = $queryBuilder
->select('*')
->from($table)
->where(
$queryBuilder->expr()->eq(
$table . '.uid',
$queryBuilder->createNamedParameter((int) $item, Connection::PARAM_INT)
)
)->setMaxResults(1)->executeQuery();
if ($row = $result->fetchAssociative()) {
// Group with relation to a field
if (is_array($tc['FIND_IN_SET'] ?? null)) {
foreach ($tc['FIND_IN_SET'] as $t => $f) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$result = $queryBuilder
->select('*')
->from($t)
->where(
$queryBuilder->expr()->inSet(
$f,
$queryBuilder->createNamedParameter(
(int) $item,
Connection::PARAM_INT
)
)
)->executeQuery();
while ($resArray = $result->fetchAssociative()) {
$records[$t][$resArray['uid']] = $resArray;
$records[$t][$resArray['uid']]['group_uid'] = $table . '_' . $row['uid'];
$records[$t][$resArray['uid']]['group_title'] = $row['title'];
$records[$t][$resArray['uid']]['group_description'] = $row['description'];
$records[$t][$resArray['uid']]['tx_odsosm_marker'] = $row['tx_odsosm_marker'];
$records[$t][$resArray['uid']]['longitude'] = $resArray[$tables[$t]['lon']];
$records[$t][$resArray['uid']]['latitude'] = $resArray[$tables[$t]['lat']];
}
}
}
// Group with mm relation
if (is_array($tc['MM'] ?? null)) {
foreach ($tc['MM'] as $t => $f) {
$local = $f['local'];
$mm = $f['mm'];
$foreign = $f['foreign'];
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($foreign);
$constraints = Div::getConstraintsForQueryBuilder($foreign, $this->cObj, $queryBuilder);
// set uid
$constraints[] = $queryBuilder->expr()->eq($local . '.uid', $queryBuilder->createNamedParameter($item, Connection::PARAM_INT));
$rows = $queryBuilder
->select($foreign . '.*')
->from($foreign)
->join(
$foreign,
$mm,
$mm,
$queryBuilder->expr()->eq($foreign . '.uid', $queryBuilder->quoteIdentifier($mm . '.uid_foreign'))
)
->join(
$mm,
$local,
$local,
$queryBuilder->expr()->eq($local . '.uid', $queryBuilder->quoteIdentifier($mm . '.uid_local'))
)
->where(...$constraints)
->executeQuery()
->fetchAllAssociative();
foreach ($rows as $r) {
$records[$t][$r['uid']] = $r;
$records[$t][$r['uid']]['group_uid'] = $table . '_' . $row['uid'];
$records[$t][$r['uid']]['group_title'] = $row['title'];
$records[$t][$r['uid']]['group_description'] = $row['description'];
$records[$t][$r['uid']]['tx_odsosm_marker'] = $row['tx_odsosm_marker'];
$records[$t][$r['uid']]['longitude'] = $r[$tables[$t]['lon']];
$records[$t][$r['uid']]['latitude'] = $r[$tables[$t]['lat']];
}
}
}
// Marker
if (isset($tc['lon'])) {
$records[$table][$item] = $row;
$records[$table][$item]['longitude'] = $row[$tc['lon']];
$records[$table][$item]['latitude'] = $row[$tc['lat']];
}
// Special element
if ($tc === true && $row) {
$records[$table][$item] = $row;
}
}
}
}
// Hook to change records
foreach ($this->hooks as $hook) {
if (method_exists($hook, 'changeRecords')) {
$hook->changeRecords($records, $record_ids, $this);
}
}
// get lon & lat
foreach ($records as $table => $items) {
foreach ($items as $uid => $row) {
switch ($table) {
case 'tx_odsosm_track':
case 'tx_odsosm_vector':
if ($row['min_lon']) {
$this->lons[] = (float) $row['min_lon'];
$this->lats[] = (float) $row['min_lat'];
$this->lons[] = (float) $row['max_lon'];
$this->lats[] = (float) $row['max_lat'];
} else {
unset($records[$table][$uid]);
}
break;
default:
$this->lons[] = (float) $row['longitude'];
$this->lats[] = (float) $row['latitude'];
break;
}
}
}
// No markers
if (count($this->lons) == 0) {
if ($this->config['no_marker'] == 1) {
if (($this->config['lon'] ?? false) && ($this->config['lat'] ?? false)) {
$this->lons[] = $this->config['lon'];
$this->lats[] = $this->config['lat'];
} else {
$this->lons[] = $this->config['default_lon'];
$this->lats[] = $this->config['default_lat'];
}
}
}
return ($records);
}
public function getMap()
{
/* ==================================================
Marker
================================================== */
// Get icon records
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_odsosm_marker');
$result = $queryBuilder
->select('*')
->from('tx_odsosm_marker')->where(1)->executeQuery();
$icons = [];
while ($resArray = $result->fetchAssociative()) {
$icons[$resArray['uid']] = $resArray;
}
// Prepare markers
$markers = $this->config['marker'];
$local_cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
foreach ($markers as $table => &$items) {
foreach ($items as $key => &$item) {
$popup = is_string($this->config['popup.'][$table] ?? null) && is_array($this->config['popup.'][$table . '.'] ?? null) && $this->config['show_popups'];
$icon = is_string($this->config['icon.'][$table] ?? null) && is_array($this->config['icon.'][$table . '.'] ?? null);
if ($popup || $icon) {
$local_cObj->start($item, $table);
}
// Add popup information
if ($popup) {
$item['popup'] = $local_cObj->cObjGetSingle($this->config['popup.'][$table], $this->config['popup.'][$table . '.']);
}
// Add icon information
if ($item['tx_odsosm_marker'] ?? null) {
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$fileObjects = $fileRepository->findByRelation('tx_odsosm_marker', 'icon', $item['tx_odsosm_marker']);
if ($fileObjects) {
$file = $fileObjects[0];
} else {
continue;
}
$item['tx_odsosm_marker'] = $icons[$item['tx_odsosm_marker']];
$item['tx_odsosm_marker']['icon'] = $file;
$item['tx_odsosm_marker']['type'] = 'image';
} elseif ($icon) {
if ($this->config['icon.'][$table] == 'IMAGE') {
$info = $this->cObj->getImgResource(
$this->config['icon.'][$table . '.']['file'] ?? '',
$this->config['icon.'][$table . '.']['file.'] ?? []
);
$item['tx_odsosm_marker'] = [
'icon' => $info['processedFile'],
'type' => 'image',
'size_x' => $info[0],
'size_y' => $info[1],
'offset_x' => -$info[0] / 2,
'offset_y' => -$info[1],
];
} elseif ($this->config['icon.'][$table] == 'TEXT') {
$conf = $this->config['icon.'][$table . '.'];
$html = $local_cObj->cObjGetSingle(
$this->config['icon.'][$table],
$this->config['icon.'][$table . '.']
);
$item['tx_odsosm_marker'] = [
'icon' => $html,
'type' => 'html',
'size_x' => $conf['size_x'],
'size_y' => $conf['size_y'],
'offset_x' => $conf['offset_x'],
'offset_y' => $conf['offset_y'],
];
}
}
}
}
/* ==================================================
Layers
================================================== */
$layers = [];
$baselayers = [];
if (!empty(implode(',', $this->config['layer']))) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_odsosm_layer');
$result = $queryBuilder
->select('*')
->from('tx_odsosm_layer')
->where(
$queryBuilder->expr()->in(
'tx_odsosm_layer.uid',
$queryBuilder->createNamedParameter(
$this->config['layer'],
Connection::PARAM_INT_ARRAY
)
),
)
->add('orderBy', 'FIELD(uid, ' . implode(',', $this->config['layer']) . ')', true)
->executeQuery();
while ($resArray = $result->fetchAssociative()) {
$baselayers[$resArray['uid']] = $resArray;
$baselayers[$resArray['uid']]['visible'] = false;
}
// set visible flag
if (isset($this->config['layers_visible'])) {
foreach ($this->config['layers_visible'] as $key) {
if ($baselayers[$key] ?? false) {
$baselayers[$key]['visible'] = true;
}
}
}
if (isset($this->config['overlays_active'])) {
foreach (explode(',', $this->config['overlays_active']) as $key) {
if ($baselayers[$key] ?? false) {
$baselayers[$key]['visible'] = true;
}
}
}
}
foreach ($baselayers as $uid => $layer) {
if ($layer['overlay'] == 1) {
$layers[1][] = $layer;
} else {
$layers[0][] = $layer;
}
}
// $layers[0] = $baselayers;
// $layers[1] = $overlays;
$layers[2] = []; // markers will be filled in provider classes
/* ==================================================
Map center
================================================== */
if ($this->config['lon'] ?? false || $this->config['use_coords_only_nomarker'] ?? false) {
$lon = array_sum($this->lons) / count($this->lons);
$lat = array_sum($this->lats) / count($this->lats);
} else {
$lon = (float)($this->config['lon'] ?? $this->config['default_lon']);
$lat = (float)($this->config['lat'] ?? $this->config['default_lat']);
}
$zoom = (int)$this->config['zoom'];
/* ==================================================
Map
================================================== */
$content = $this->library->getMap($layers, $markers, $lon, $lat, $zoom);
$script = $this->library->getScript();
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
if ($script) {
switch ($this->config['JSlibrary']) {
case 'jquery':
$pageRenderer->addJsFooterInlineCode(
$this->config['id'],
'$(document).ready(function() {' . $script . '});'
);
break;
default:
$pageRenderer->addJsFooterInlineCode(
$this->config['id'],
'document.addEventListener("DOMContentLoaded", function(){' . $script . '}, false);'
);
break;
}
}
return ($content);
}
}

View File

@@ -0,0 +1,576 @@
<?php
namespace Bobosch\OdsOsm;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Core\Log\Logger;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
use TYPO3\CMS\Core\Http\RequestFactory;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageService;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
class Div
{
const RESOURCE_BASE_PATH = 'EXT:ods_osm/Resources/Public/';
public static function getConstraintsForQueryBuilder(
$table,
ContentObjectRenderer $cObj,
QueryBuilder $queryBuilder
): array {
$constraints = [];
if (is_string($table)) {
$ctrl = $GLOBALS['TCA'][$table]['ctrl'];
// Enable fields
$queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
// Version
$constraints[] =
$queryBuilder->expr()->gte($table . '.pid', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT));
// Translation
if ($ctrl['languageField'] ?? null) {
$orConstraints = [
$queryBuilder->expr()->eq(
$table . '.' . $ctrl['languageField'],
$queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
),
$queryBuilder->expr()->eq(
$table . '.' . $ctrl['languageField'],
$queryBuilder->createNamedParameter(-1, Connection::PARAM_INT)
),
];
$languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
if ($languageAspect->getContentId() && $ctrl['transOrigPointerField']) {
$orConstraints[] = $queryBuilder->expr()->and($queryBuilder->expr()->eq($table . '.' . $ctrl['languageField'],
$queryBuilder->createNamedParameter((int) $languageAspect->getContentId(), Connection::PARAM_INT)), $queryBuilder->expr()->eq($table . '.' . $ctrl['transOrigPointerField'],
$queryBuilder->createNamedParameter(0, Connection::PARAM_INT)));
}
$constraints[] = $queryBuilder->expr()->or(...$orConstraints);
}
}
return $constraints;
}
public static function addJsFiles($scripts, $doc): void
{
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
foreach ($scripts as $script) {
$pageRenderer->addJsFooterFile(
$script['src'],
'text/javascript',
true,
false,
'',
false,
'|',
false,
$script['sri'] ?? '',
false,
'anonymous'
);
}
}
/**
* Update the given address record with geo data from a geocoding service.
*
* Note that the record does not get update in database.
*
* @param array &$address Address record from database
*
* @return boolean True if the address got updated, false if not.
*
* @uses searchAddress()
*/
public static function updateAddress(&$address)
{
$config = self::getConfig(['cache_enabled', 'geo_service']);
self::splitAddressField($address);
// Use cache only when enabled
if ($config['cache_enabled'] == 1) {
$ll = self::searchAddress($address, 0);
}
if (!$ll) {
$search = $address;
$ll = self::searchAddress($address, $config['geo_service']);
// Update cache when enabled or needed for statistic
if ($ll && $config['cache_enabled']) {
self::updateCache($address, $search);
}
}
return $ll;
}
/**
* Search for the given address in one of the geocoding services and update
* its data.
*
* Data lat, lon, zip and city may get updated.
*
* @param array &$address Address record from database
* @param integer $service Geocoding service to use
* - 0: internal caching database table
* - 1: geonames.org
* - 2: nominatim.openstreetmap.org
*
* @return boolean True if the address got updated, false if not.
*/
public static function searchAddress(&$address, $service = 0)
{
$config = self::getConfig(['default_country', 'geo_service_email', 'geo_service_user']);
$ll = false;
$country = strtoupper(strlen($address['country'] ?? false) == 2 ? $address['country'] : $config['default_country']);
$email = GeneralUtility::validEmail($config['geo_service_email']) ? $config['geo_service_email'] : ($_SERVER['SERVER_ADMIN'] ?? 'unkown@example.com');
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug']) {
$service_names = [0 => 'cache', 1 => 'geonames', 2 => 'nominatim'];
self::getLogger()->debug('Search address using ' . $service_names[$service], $address);
}
/** @var ConnectionPool $connectionPool */
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$connection = $connectionPool->getConnectionForTable('tx_odsosm_geocache');
switch ($service) {
case 0: // cache
$where = [];
if ($country) {
$where[] = 'country=' . $connection->quote($country, ParameterType::STRING);
}
if ($address['city'] ?? false) {
$where[] = '(city=' . $connection->quote($address['city'], ParameterType::STRING) . ' OR search_city=' . $connection->quote($address['city'], ParameterType::STRING) . ')';
}
if ($address['zip'] ?? false) {
$where[] = 'zip=' . $connection->quote($address['zip'], ParameterType::STRING);
}
if ($address['street'] ?? false) {
$where[] = 'street=' . $connection->quote($address['street'], ParameterType::STRING);
}
if ($address['housenumber'] ?? false) {
$where[] = 'housenumber=' . $connection->quote($address['housenumber'], ParameterType::STRING);
}
if ($where) {
$where[] = 'deleted=0';
$res = $connection->executeQuery(
'SELECT * FROM tx_odsosm_geocache WHERE ' . implode(' AND ', $where)
);
$row = $res->fetchAssociative();
if ($row) {
$ll = true;
$set = [
'tstamp' => time(),
'cache_hit' => $row['cache_hit'] + 1,
];
$connection->update('tx_odsosm_geocache', $set, ['uid' => (int) $row['uid']]);
$address['lat'] = $row['lat'];
$address['lon'] = $row['lon'];
if ($row['zip'] ?? false) {
$address['zip'] = $row['zip'];
}
if ($row['city'] ?? false) {
$address['city'] = $row['city'];
}
if ($row['state'] ?? false) {
$address['state'] = $row['state'];
}
if (empty($address['country'] ?? false)) {
$address['country'] = $row['country'];
}
}
}
break;
case 1: // http://www.geonames.org/
if ($country) {
$query['country'] = $country;
}
if ($address['city'] ?? false) {
$query['placename'] = $address['city'];
}
if ($address['zip'] ?? false) {
$query['postalcode'] = $address['zip'];
}
if ($query) {
$query['maxRows'] = 1;
$query['username'] = $config['geo_service_user'];
/** @var RequestFactory $requestFactory */
$requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
$configuration = [
'timeout' => 60,
'headers' => [
'Accept' => 'application/json',
'User-Agent' => 'TYPO3 extension ods_osm/' . ExtensionManagementUtility::getExtensionVersion('ods_osm')
],
];
// secure endpoint available, too: https://secure.geonames.org/postalCodeSearchJSON?
$response = $requestFactory->request('http://api.geonames.org/postalCodeSearchJSON?' . http_build_query($query, '', '&'), 'GET', $configuration);
$content = $response->getBody()->getContents();
$result = json_decode($content, true);
if ($result) {
if ($result['status'] ?? false) {
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug']) {
self::getLogger()->debug('GeoNames message', (array)$result['status']['message']);
}
self::flashMessage(
(string)$result['status']['message'],
'GeoNames message',
AbstractMessage::WARNING
);
}
if ($result['postalCodes'][0] ?? false) {
$ll = true;
$address['lat'] = (string)$result['postalCodes'][0]['lat'];
$address['lon'] = (string)$result['postalCodes'][0]['lng'];
if ($result['postalCodes'][0]['postalCode'] ?? false) {
$address['zip'] = (string)$result['postalCodes'][0]['postalCode'];
}
if ($result['postalCodes'][0]['placeName'] ?? false) {
$address['city'] = (string)$result['postalCodes'][0]['placeName'];
}
if (empty($address['country'] ?? false)) {
$address['country'] = (string)$result['postalCodes'][0]['countryCode'];
}
}
} elseif ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] ?? false) {
self::getLogger()->error('No valid response from GeoNames service.');
}
}
break;
case 2: // http://nominatim.openstreetmap.org/
$query['country'] = $country;
$query['email'] = $email;
$query['addressdetails'] = 1;
$query['format'] = 'jsonv2';
if ($address['type'] == 'structured') {
if ($address['city'] ?? false) {
$query['city'] = $address['city'];
}
if ($address['zip'] ?? false) {
$query['postalcode'] = $address['zip'];
}
if ($address['street'] ?? false) {
$query['street'] = $address['street'];
}
if ($address['housenumber'] ?? false) {
$query['street'] = $address['housenumber'] . ' ' . $query['street'];
}
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] ?? false) {
self::getLogger()->debug('Nominatim structured', $query);
}
$ll = self::searchAddressNominatim($query, $address);
if (!$ll && ($query['postalcode'] ?? false)) {
unset($query['postalcode']);
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] ?? false) {
self::getLogger()->debug('Nominatim retrying without zip', $query);
}
$ll = self::searchAddressNominatim($query, $address);
}
}
if ($address['type'] == 'unstructured') {
$query['q'] = $address['address'];
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] ?? false) {
self::getLogger()->debug('Nominatim unstructured', $query);
}
$ll = self::searchAddressNominatim($query, $address);
}
break;
}
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] ?? false) {
if ($ll) {
self::getLogger()->debug('Return address', $address);
} else {
self::getLogger()->debug('No address found.');
}
}
return $ll;
}
/**
* Search for the given address in Nominatim service.
*
* Data lat, lon, zip and city may get updated.
*
* @param array $query The query sent to the nominatim API
* @param array &$address Address record from database
*
* @return boolean True if the address was found and got updated.
*/
protected static function searchAddressNominatim($query, &$address)
{
$ll = false;
/** @var RequestFactory $requestFactory */
$requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
$configuration = [
'timeout' => 60,
'headers' => [
'Accept' => 'application/json',
'User-Agent' => 'TYPO3 extension ods_osm/' . ExtensionManagementUtility::getExtensionVersion('ods_osm')
],
];
$response = $requestFactory->request('https://nominatim.openstreetmap.org/search?' . http_build_query($query, '', '&'), 'GET', $configuration);
$content = $response->getBody()->getContents();
$result = json_decode($content, true);
// Save value in cache
if ($result) {
// take the first result
if ($result[0] ?? false) {
$ll = true;
$address['lat'] = (string)$result[0]['lat'];
$address['lon'] = (string)$result[0]['lon'];
if ($result[0]['address']['road'] ?? false) {
$address['street'] = (string)$result[0]['address']['road'];
}
if ($result[0]['address']['house_number'] ?? false) {
$address['housenumber'] = (string)$result[0]['address']['house_number'];
}
if ($result[0]['address']['postcode'] ?? false) {
$address['zip'] = (string)$result[0]['address']['postcode'];
}
if ($result[0]['address']['city'] ?? false) {
$address['city'] = $result[0]['address']['city'];
} else if ($result[0]['address']['village'] ?? false) {
$address['city'] = (string)$result[0]['address']['village'];
}
if ($result[0]['address']['state'] ?? false) {
$address['state'] = (string)$result[0]['address']['state'];
}
if (($result[0]['address']['country_code'] ?? false) && empty($address['country'] ?? false)) {
$address['country'] = strtoupper((string)$result[0]['address']['country_code']);
}
}
} elseif ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] ?? false) {
self::getLogger()->error('No valid response from Nominatim service.');
}
return $ll;
}
public static function flashMessage($message, $title, $status): void
{
/** @var FlashMessage $flashMessage */
$flashMessage = GeneralUtility::makeInstance(
FlashMessage::class,
$message,
$title,
$status
);
/** @var FlashMessageService $flashMessageService */
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
$flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
$flashMessageQueue->addMessage($flashMessage);
}
public static function updateCache($address, $search = []): void
{
$set = [
'search_city' => $search['city'] ?? '',
'country' => $address['country'] ?? '',
'state' => $address['state'] ?? '',
'city' => $address['city'] ?? '',
'zip' => $address['zip'] ?? '',
'street' => $address['street'] ?? '',
'housenumber' => $address['housenumber'] ?? '',
];
/** @var ConnectionPool $connectionPool */
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$connection = $connectionPool->getConnectionForTable('tx_odsosm_geocache');
$res = $connection->select(
['*'], 'tx_odsosm_geocache', $set
);
$row = $res->fetchAssociative();
if ($row) {
$set = [
'tstamp' => time(),
'service_hit' => $row['service_hit'] + 1,
];
$connection->update('tx_odsosm_geocache', $set, ['uid' => (int) $row['uid']]);
} else {
$set['tstamp'] = time();
$set['crdate'] = time();
$set['service_hit'] = 1;
$set['lat'] = $address['lat'];
$set['lon'] = $address['lon'];
$connection->insert('tx_odsosm_geocache', $set);
}
}
public static function splitAddressField(&$address): void
{
// Address field contains street if country, city or zip is set
if (($address['country'] ?? false) || ($address['city'] ?? false) || ($address['zip'] ?? false)) {
$address['type'] = 'structured';
if ($address['address'] && !($address['street'] ?? false)) {
$address['street'] = $address['address'];
}
if (!($address['housenumber'] ?? false) && ($address['street'] ?? false)) {
// Split street and house number
preg_match('/^(.+)\s(\d+(\s*[^\d\s]+)*)$/', $address['street'], $matches);
if ($matches) {
$address['street'] = $matches[1];
$address['housenumber'] = $matches[2];
}
}
} elseif ($address['address'] ?? false) {
$address['type'] = 'unstructured';
} else {
$address['type'] = 'empty';
}
}
/**
* Get extension configuration, and if not available use
* default configuration. Optional parameter checks if
* single value is available.
*
* @param array $values
*
* @return array
*/
public static function getConfig($values = [])
{
$config = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ods_osm'] ?? [];
$getDefault = [];
if ($config && is_array($values) && count($values)) {
foreach ($values as $value) {
if (!isset($config[$value])) {
$getDefault[] = $value;
}
}
}
if (empty($config) || count($getDefault)) {
$default = parse_ini_file(ExtensionManagementUtility::extPath('ods_osm') . 'ext_conf_template.txt');
if (empty($config)) {
$config = $default;
} else {
foreach ($getDefault as $value) {
$config[$value] = $default[$value];
}
}
}
return $config;
}
public static function getTableConfig($table = false)
{
$tables = [
'fe_groups' => [
'FIND_IN_SET' => [
'fe_users' => 'usergroup',
],
],
'fe_users' => [
'FORMAT' => '%01.6f',
'lon' => 'tx_odsosm_lon',
'lat' => 'tx_odsosm_lat',
'address' => 'address',
'zip' => 'zip',
'city' => 'city',
'country' => 'country',
],
'tt_content' => [
'FORMAT' => '%01.6f',
'lon' => 'lon',
'lat' => 'lat',
],
'tx_odsosm_track' => true,
'tx_odsosm_vector' => true,
'sys_category' => [
'MM' => [
'tt_address' => [
'local' => 'sys_category',
'mm' => 'sys_category_record_mm',
'foreign' => 'tt_address'
]
]
]
];
// load configuration for calendarize only if extension is loaded
if (ExtensionManagementUtility::isLoaded('calendarize')) {
$tables['tx_calendarize_domain_model_event'] = [
'FORMAT' => '%01.6f',
'lon' => 'tx_odsosm_lon',
'lat' => 'tx_odsosm_lat',
'address' => 'location',
];
}
// load configuration for tt_address only if extension is loaded
if (ExtensionManagementUtility::isLoaded('tt_address')) {
$tables['tt_address'] = [
'FORMAT' => '%01.11f',
'lon' => 'longitude',
'lat' => 'latitude',
'address' => 'address',
'zip' => 'zip',
'city' => 'city',
'state' => 'region',
'country' => 'country',
];
}
if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ods_osm']['tables'] ?? null)) {
$tables = array_merge($tables, $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ods_osm']['tables']);
}
return $table ? ($tables[$table] ?? []) : $tables;
}
/**
* @return Logger
*/
protected static function getLogger()
{
/** @var $loggerManager LogManager */
$loggerManager = GeneralUtility::makeInstance(LogManager::class);
return $loggerManager->getLogger(static::class);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Bobosch\OdsOsm\Evaluation;
class LonLat
{
public function returnFieldJS(): string
{
return "return value;";
}
public function evaluateFieldValue($value, $is_in, &$set): string
{
return sprintf('%01.6f', $value);
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Bobosch\OdsOsm\EventListener;
use HDNET\Calendarize\Register;
use TYPO3\CMS\Core\Database\Event\AlterTableDefinitionStatementsEvent;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
class CalendarizeOdsOsmSqlListener
{
public function __invoke(AlterTableDefinitionStatementsEvent $event): void
{
if (ExtensionManagementUtility::isLoaded('calendarize')) {
$event->addSqlData($this->getCalendarizeDatabaseString());
}
}
/**
* Get the calendarize string for the registered tables.
*
* @return string
*/
protected function getCalendarizeDatabaseString()
{
$sql = [];
foreach (Register::getRegister() as $configuration) {
if ($configuration['tableName'] == 'tx_calendarize_domain_model_event') {
$sql[] = "CREATE TABLE " . $configuration['tableName'] . " (
tx_odsosm_lon decimal(9,6) NOT NULL DEFAULT '0.000000',
tx_odsosm_lat decimal(8,6) NOT NULL DEFAULT '0.000000',
);";
}
}
return implode(LF, $sql);
}
}

View File

@@ -0,0 +1,215 @@
<?php
namespace Bobosch\OdsOsm\Provider;
use Bobosch\OdsOsm\Div;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
abstract class BaseProvider
{
/** @var ContentObjectRenderer */
public $cObj; // Must set from instantiating class
protected $config;
protected $script;
/**
*
*/
protected $pageRenderer;
/** @var array keeping all JavaScripts to be included */
protected $scripts = [];
protected $layers = [
0 => [], // Base
1 => [], // Overlay
2 => [], // Marker
];
// Implement these functions
public function getMapCore($backpath = '')
{
}
public function getMapMain()
{
}
public function getMapCenter($lat, $lon, $zoom)
{
}
/**
* @return string
*/
protected function getLayer($layer, $i, $backpath = '')
{
return '';
}
/**
* @return string
*/
protected function getMarker($item, $table)
{
return '';
}
/**
* Get JavaScript code for fulltext button
*
* @return string The JavaScript to add the fullscreen button
*/
protected function getFullScreen()
{
return '';
}
public function init($config): void
{
$this->config = $config;
$this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
}
/**
* @return string
*/
public function getMap($layers, $markers, $lon, $lat, $zoom)
{
$this->getMapCore();
$this->layers = $layers;
$baselayers = $layers[0] ?? null;
$overlays = $layers[1] ?? null;
$this->script = "
" . $this->getMapMain() . "
" . $this->getBaseLayers($baselayers) . "
" . $this->getOverlayLayers($overlays) . "
" . $this->getMapCenter($lat, $lon, $zoom) . "
" . $this->getMarkers($markers);
if (($this->config['show_layerswitcher'] ?? null) && ($this->config['show_layerswitcher'] > 0)) {
$this->script .= $this->getLayerSwitcher() . "\n";
}
if ($this->config['show_fullscreen'] ?? null) {
$this->script .= $this->getFullScreen() . "\n";
}
Div::addJsFiles($this->scripts, null);
return $this->getHtml();
}
/**
* @return string
*/
public function getBaseLayers($layers, $backpath = '')
{
// Main layer
$i = 0;
$jsBaseLayer = [];
if (is_array($layers) && !empty($layers)) {
foreach ($layers as $layer) {
$jsBaseLayer[] = $this->getLayer($layer, $i, $backpath);
$i++;
}
}
return implode("\n", ($jsBaseLayer));
}
/**
* @return string
*/
public function getOverlayLayers($layers, $backpath = '')
{
// Main layer
$i = 0;
$jsOverlayLayer = [];
if (is_array($layers) && !empty($layers)) {
foreach ($layers as $layer) {
$jsOverlayLayer[] = $this->getLayer($layer, $i, $backpath);
$i++;
}
}
return implode("\n", ($jsOverlayLayer));
}
/**
* @return string
*/
public function getScript()
{
return $this->script;
}
/**
* @return string
*/
protected function getMarkers($markers)
{
$jsMarker = '';
foreach ($markers as $table => $items) {
foreach ($items as $item) {
$jsMarker .= $this->getMarker($item, $table);
}
}
return $jsMarker;
}
/**
* @return string
*/
protected function getLayerSwitcher()
{
return '';
}
/**
* @return string
*/
protected function getHtml()
{
$mousePosition = '';
$popupcode = '';
if ($this->config['library'] == 'openlayers') {
if ($this->config['mouse_position']) {
$mousePosition = '<div id="mouse-position-' . $this->config['id'] . '">' . LocalizationUtility::translate('mouse_position', 'OdsOsm') . ':&nbsp;</div>';
}
$popupcode = '
<div id="popup" class="ol-popup">
<a href="#" id="popup-closer" class="ol-popup-closer"></a>
<div id="popup-content"></div>
</div>';
}
return '<div style="width:' . $this->config['width'] . '; height:' . $this->config['height'] . '; " id="' . $this->config['id'] . '"></div>' . $mousePosition . $popupcode;
}
/**
* @return string
*/
protected function getTileUrl($layer)
{
if (strpos($layer['tile_url'], '://') !== false) {
return $layer['tile_url'];
}
// if protocoll is missing, we add http:// or https://
if ($layer['tile_https'] == 1) {
return 'https://' . $layer['tile_url'];
}
return 'http://' . $layer['tile_url'];
}
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
{
$this->cObj = $cObj;
}
}

View File

@@ -0,0 +1,355 @@
<?php
namespace Bobosch\OdsOsm\Provider;
use Bobosch\OdsOsm\Div;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
class Leaflet extends BaseProvider
{
protected $path_res;
protected $path_leaflet;
public function getMapCore($backpath = ''): void
{
$this->path_res = ($backpath ? $backpath :
PathUtility::getAbsoluteWebPath(
GeneralUtility::getFileAbsFileName(Div::RESOURCE_BASE_PATH . 'JavaScript/Leaflet/')
)
);
$this->path_leaflet = ($this->config['local_js'] ? $this->path_res . 'Core/' : 'https://unpkg.com/leaflet@1.9.4/dist/');
$this->pageRenderer->addCssFile($this->path_leaflet . 'leaflet.css');
$this->scripts['leaflet'] = [
'src' => $this->path_leaflet . 'leaflet.js',
'sri' => 'sha384-cxOPjt7s7Iz04uaHJceBmS+qpjv2JkIHNVcuOrM+YHwZOmJGBXI00mdUXEq65HTH'
];
}
public function getMapMain()
{
$controls = [];
if ($this->config['show_scalebar']) {
$controls['scalebar'] = 'new L.control.scale()';
}
$vars = '';
foreach ($controls as $var => $obj) {
$vars .= "\n\t\t\t" . $this->config['id'] . '.addControl(' . $obj . ");";
}
$jsMain =
$this->config['id'] . "=new L.Map('" . $this->config['id'] . "',
{scrollWheelZoom: " .((!isset($this->config['enable_scrollwheelzoom']) || $this->config['enable_scrollwheelzoom'] == '1') ? 'true' : 'false'). ",
dragging: " .((!isset($this->config['enable_dragging']) || $this->config['enable_dragging'] == '1') ? 'true' : 'false'). "});
L.Icon.Default.imagePath='" . $this->path_leaflet . "images/';"
. $vars;
if ($this->config['cluster']) {
$this->pageRenderer->addCssFile($this->path_res . 'leaflet-markercluster/MarkerCluster.css');
$this->pageRenderer->addCssFile($this->path_res . 'leaflet-markercluster/MarkerCluster.Default.css');
$this->scripts['leaflet-markercluster'] = [
'src' => $this->path_res . 'leaflet-markercluster/leaflet.markercluster.js'
];
}
return $jsMain;
}
protected function getLayer($layer, $i, $backpath = '')
{
if ($layer['tile_url']) {
$options = [];
if ($layer['min_zoom']) {
$options['minZoom'] = $layer['min_zoom'];
}
if ($layer['max_zoom']) {
$options['maxZoom'] = $layer['max_zoom'];
}
if ($layer['subdomains']) {
$options['subdomains'] = $layer['subdomains'];
}
if ($layer['attribution']) {
$options['attribution'] = $layer['attribution'];
}
$jsLayer = 'new L.TileLayer(\'' . $this->getTileUrl($layer) . '\',' . json_encode($options) . ');';
}
$jsLayer = "\n\t\t\tvar layer_" . $layer['uid'] . ' = ' . $jsLayer;
// only show first base layer on the map
if (($layer['overlay'] == 1 && $layer['visible']) || ($i == 0 && $layer['overlay'] == 0)) {
$jsLayer .= "\n\t\t\t" . $this->config['id'] . '.addLayer(layer_' . $layer['uid'] . ');';
}
return $jsLayer;
}
protected function getLayerSwitcher()
{
$base = [];
if (is_array($this->layers[0] ?? null) && count($this->layers[0]) > 1) {
foreach ($this->layers[0] as $layer) {
$base[] = '"' . $layer['title'] . '":' . ($layer['table'] ?? 'layer') . '_' . $layer['uid'];
}
}
$overlay = [];
if (is_array($this->layers[1] ?? null)) {
foreach ($this->layers[1] as $layer) {
if (!empty($layer['gid'])) {
$overlay[] = '"' . $layer['title'] . '":' . $layer['gid'];
} else {
$overlay[] = '"' . $layer['title'] . '":' . ($layer['table'] ?? 'layer') . '_' . $layer['uid'];
}
}
}
if (empty($base) && empty($overlay)) {
return '';
}
return 'var layersControl=new L.Control.Layers({' . implode(',', $base) . '},{' . implode(',', $overlay) . '}' . ($this->config['show_layerswitcher'] == 2 ? ',{collapsed:false}' : '') . ');
' . $this->config['id'] . '.addControl(layersControl);';
}
/**
* Get the fullscreen button
*
* @return string The JavaScript to add the fullscreen button
*/
public function getFullScreen()
{
// load leaflet.fullscreen plugin
$this->scripts['leaflet-fullscreen'] = [
'src' => $this->path_res . 'leaflet-fullscreen/Control.FullScreen.js',
'sri' => 'sha384-TqFtkYBnYsrP2JCfIv/oLQxS9L6xpaIV9xnaI2UGMK25cJsTtQXZIU6WGQ7daT0Z'
];
$this->pageRenderer->addCssFile($this->path_res . 'leaflet-fullscreen/Control.FullScreen.css');
return "L.control.fullscreen({
position: 'topleft',
title: 'Full Screen',
titleCancel: 'Exit fullscreen mode',
forceSeparateButton: true,
forcePseudoFullscreen: true, // force use of pseudo full screen even if full screen API is available, default false
fullscreenElement: false // Dom element to render in full screen, false by default, fallback to map._container
}).addTo(" . $this->config['id'] . ");";
}
public function getMapCenter($lat, $lon, $zoom)
{
$return = 'var center = new L.LatLng(' . json_encode($lat) . ',' . json_encode($lon) . ');' . $this->config['id'] . '.setView(center,' . $zoom . ');';
if ($this->config['position']) {
$return .= $this->config['id'] . '.locate();' . $this->config['id'] . '.on("locationfound",function(e){var radius=e.accuracy/2;L.circle(e.latlng,radius).addTo(' . $this->config['id'] . ');});';
}
return $return;
}
protected function getMarkers($markers)
{
$jsMarker = parent::getMarkers($markers);
foreach ($this->layers[2] as $group_uid => $group) {
if ($this->config['cluster']) {
$jsMarker .= 'var ' . $group_uid . ' = new L.MarkerClusterGroup({maxClusterRadius:' . (int)$this->config['cluster_radius'] . '});' . "\n";
foreach ($group as $jsElementVar) {
$jsMarker .= $group_uid . '.addLayer(' . $jsElementVar . ');' . "\n";
}
} else {
$jsMarker .= 'var ' . $group_uid . ' = L.layerGroup([' . implode(',', $group) . ']);' . "\n";
}
$jsMarker .= $this->config['id'] . '.addLayer(' . $group_uid . ');' . "\n";
}
return $jsMarker;
}
protected function getMarker($item, $table)
{
$jsMarker = '';
$jsElementVar = $table . '_' . $item['uid'];
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$jsElementVarsForPopup = [];
switch ($table) {
case 'tx_odsosm_track':
$fileObjects = $fileRepository->findByRelation('tx_odsosm_track', 'file', $item['uid']);
if ($fileObjects) {
$file = $fileObjects[0];
} else {
break;
}
// Add tracks to layerswitcher
$this->layers[1][] = [
'title' => $item['title'],
'table' => $table,
'uid' => $item['uid']
];
switch (strtolower(pathinfo($file->getName(), PATHINFO_EXTENSION))) {
case 'kml':
// include javascript file for KML support
$this->scripts['leaflet-plugins'] = [
'src' => $this->path_res . 'leaflet-plugins/layer/vector/KML.js'
];
$jsMarker .= 'var ' . $jsElementVar . ' = new L.KML(';
$jsMarker .= '"' . $file->getPublicUrl() . '"';
$jsMarker .= ");\n";
$jsMarker .= $this->config['id'] . '.addLayer(' . $jsElementVar . ');' . "\n";
break;
case 'gpx':
// include javascript file for GPX support
$this->scripts['leaflet-gpx'] = [
'src' => $this->path_res . 'leaflet-gpx/gpx.js'
];
$options = [
'clickable' => 'false',
'polyline_options' => [
'color' => $item['color'],
'weight' => $item['width'] ?: 1,
],
'marker_options' => [
'startIconUrl' => $this->path_res . 'leaflet-gpx/pin-icon-start.png',
'endIconUrl' => $this->path_res . 'leaflet-gpx/pin-icon-end.png',
'shadowUrl' => $this->path_res . 'leaflet-gpx/pin-shadow.png',
],
];
$jsMarker .= 'var ' . $jsElementVar . ' = new L.GPX("' . $file->getPublicUrl() . '",';
$jsMarker .= json_encode($options) . ");\n";
$jsMarker .= $this->config['id'] . '.addLayer(' . $jsElementVar . ');' . "\n";
break;
}
$jsElementVarsForPopup[] = $jsElementVar;
break;
case 'tx_odsosm_vector':
// add styles from record if both are set - color and width
if (!empty($item['color']) && !empty($item['width'])) {
$jsMarker .= 'var myStyle = {
"color": "'.$item['color'].'",
"weight": '.$item['width'].',
"opacity": 1
};';
} else {
$jsMarker .= 'var myStyle = {};';
}
$fileObjects = $fileRepository->findByRelation('tx_odsosm_vector', 'file', $item['uid']);
if ($fileObjects) {
$file = $fileObjects[0];
$filename = Environment::getPublicPath() . '/' . $file->getPublicUrl();
$jsMarker .= 'var ' . $jsElementVar . '_file = new L.geoJson(' . file_get_contents($filename) . ',
{
style: myStyle
});' . "\n";
$jsMarker .= $this->config['id'] . '.addLayer(' . $jsElementVar . '_file);' . "\n";
// Add vector file to layerswitcher
$this->layers[1][] = [
'title' => $item['title'] . ' ('. LocalizationUtility::translate('file', 'OdsOsm') .')',
'table' => $table,
'uid' => $item['uid'] . '_file'
];
$jsElementVarsForPopup[] = $jsElementVar . '_file';
}
// add geojson from data field as well
if ($item['data']) {
$jsMarker .= 'var ' . $jsElementVar . '_data = new L.geoJson(' . $item['data'] . ',
{
style: myStyle
});' . "\n";
$jsMarker .= $this->config['id'] . '.addLayer(' . $jsElementVar . '_data);' . "\n";
// Add vector data to layerswitcher
$this->layers[1][] = [
'title' => $item['title'],
'table' => $table,
'uid' => $item['uid'] . '_data'
];
$jsElementVarsForPopup[] = $jsElementVar . '_data';
}
break;
default:
$markerOptions = [];
if ($item['tx_odsosm_marker'] ?? false) {
$marker = $item['tx_odsosm_marker'];
$iconOptions = [
'iconSize' => [(int)$marker['size_x'], (int)$marker['size_y']],
'iconAnchor' => [-(int)$marker['offset_x'], -(int)$marker['offset_y']],
'popupAnchor' => [0, (int)$marker['offset_y']]
];
if ($marker['type'] == 'html') {
$iconOptions['html'] = $marker['icon'];
$markerOptions['icon'] = 'icon: new L.divIcon(' . json_encode($iconOptions) . ')';
} else {
$icon = $marker['icon']->getPublicUrl();
$iconOptions['iconUrl'] = $icon;
$markerOptions['icon'] = 'icon: new L.Icon(' . json_encode($iconOptions) . ')';
}
} else {
$marker = [ 'type' => 'image' ];
$icon = $this->path_leaflet . 'images/marker-icon.png';
}
$jsMarker .= 'var ' . $jsElementVar . ' = new L.Marker([' . $item['latitude'] . ', ' . $item['longitude'] . '], {' . implode(',', $markerOptions) . "});\n";
// Add group to layer switch
if ($item['group_title'] ?? false) {
$this->layers[1][] = [
'title' => ($marker['type'] == 'html' ? $marker['icon'] : "<img class='marker-icon' src='" . $icon . "' />") . ' ' . $item['group_title'],
'gid' => $item['group_uid']
];
$this->layers[2][$item['group_uid']][] = $jsElementVar;
} else {
$this->layers[2][$this->config['id'] . '_g'][] = $jsElementVar;
}
$jsElementVarsForPopup[] = $jsElementVar;
break;
}
foreach ($jsElementVarsForPopup as $jsElementVar) {
// is there a properties attribute from geoJSON? If so, we will show the given properties
$popupJsCode = '';
if ($item['properties'] ?? null) {
$geojsonProperties = json_encode(explode(', ', $item['properties']));
$popupJsCode = "
function (layer) {
var osm_popup = '" . ($item['popup'] ?? '') . "';
var feature = layer.feature,
props = feature.properties,
ll = Object.keys(props),
attribute, value = '';
var osm_filter = " . $geojsonProperties . ";
osm_filter.forEach((osm_prop) => {
if (typeof props[osm_prop] !== 'undefined') {
value += '<dt>' + osm_prop + '</dt> <dd>' + props[osm_prop] + '</dd>';
}
});
return osm_popup + '<dl>' + value + '</dl>';
}
";
} elseif ($item['popup'] ?? null) {
$popupJsCode = json_encode($item['popup'] ?? '');
}
if ($this->config['show_popups'] == 1) {
$jsMarker .= $jsElementVar . '.bindPopup(' . $popupJsCode . '); ' . "\n";
if ($item['initial_popup'] ?? null) {
$jsMarker .= $jsElementVar . ".openPopup();\n";
}
} elseif ($this->config['show_popups'] == 2) {
$jsMarker .= $jsElementVar . '.bindTooltip(' . $popupJsCode . ");\n";
}
}
return $jsMarker;
}
}

View File

@@ -0,0 +1,632 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2022 Alexander Bigga <alexander@bigga.de>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
namespace Bobosch\OdsOsm\Provider;
use Bobosch\OdsOsm\Div;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
class Openlayers extends BaseProvider
{
protected $layers = [
0 => [], // Base
1 => [], // Overlay
2 => [], // Marker
];
public function getMapCore($backpath = ''): void
{
$path = ($backpath ? $backpath :
PathUtility::getAbsoluteWebPath(
GeneralUtility::getFileAbsFileName(Div::RESOURCE_BASE_PATH . 'OpenLayers/')
)
);
$pathOl = ($this->config['local_js'] ? $path : 'https://cdn.jsdelivr.net/npm/ol@v8.1.0/');
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
$pageRenderer->addCssFile($pathOl . 'ol.css');
$this->scripts['OpenLayers'] = [
'src' => $pathOl . 'dist/ol.js',
'sri' => 'sha512-7BxMviUlJVAJOF4l717SzPknm3Y5nLAm3PPtRdrWlCu4GLaW+RhBxYuOJ1MkVNAcPu+lRWn4gtWx0PAxvTzD0g=='
];
// Do we need the layerswitcher? If so, some extra plugin is required.
if ($this->config['show_layerswitcher']) {
$pathContrib = ($this->config['local_js'] ? $path . 'Contrib/ol-layerswitcher/' : 'https://unpkg.com/ol-layerswitcher@4.1.1/dist/');
$pathCustom = $path . 'Custom/';
$pageRenderer->addCssFile($pathContrib . 'ol-layerswitcher.css');
$pageRenderer->addCssFile($pathCustom . 'ol-layerswitcher.css');
$this->scripts['OpenLayersSwitch'] = [
'src' => $pathContrib . 'ol-layerswitcher.js',
'sri' => 'sha512-HhCrrWOoQb5HSpRe1fsk9ugZQEOokbJsLioPuUhfXlr5ccRTZVg3UpnfRsTJzrdKLejmx7uvY62n2fp5qLdYQg=='
];
}
}
public function getMapMain()
{
$controls = [
'new ol.control.Attribution()',
'new ol.control.Zoom()',
'new ol.control.Rotate()'
];
if ($this->config['mouse_position']) {
$controls[] = "new ol.control.MousePosition({
coordinateFormat: ol.coordinate.createStringXY(2),
projection: 'EPSG:4326',
className: 'ods-osm-mouse-position',
target: document.getElementById('mouse-position-" . $this->config['id'] . "')
})";
}
if ($this->config['show_scalebar']) {
$controls[] = "new ol.control.ScaleLine()";
}
return "
view = new ol.View({
center: [0, 0],
zoom: 1
});
baselayergroup = new ol.layer.Group({
name: 'baselayergroup',
title: '" . LocalizationUtility::translate('base_layer', 'OdsOsm') . "',
layers: [
new ol.layer.Tile({
type: 'base',
source: new ol.source.OSM(),
})
]
});
overlaygroup = new ol.layer.Group({
name: 'overlaygroup',
title: '" . LocalizationUtility::translate('overlays', 'OdsOsm') . "',
layers: []
});
const styleCache = {};
clusters = new ol.layer.Vector({
name: 'clusters',
title: '" . LocalizationUtility::translate('openlayers.clusterLayer', 'OdsOsm') . "',
source: new ol.source.Cluster({
distance: " . (int)$this->config['cluster_radius'] . ",
minDistance: 10,
source: new ol.source.Vector({
name: 'source',
features: [],
})
}),
style: function (feature) {
const size = feature.get('features').length;
if (size > 1) {
style = new ol.style.Style({
image: new ol.style.Circle({
radius: 20,
stroke: new ol.style.Stroke({
color: '#fff',
}),
fill: new ol.style.Fill({
color: '#3399CC',
}),
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: '#fff',
}),
}),
});
} else {
style = feature.get('features')[0].values_.style;
}
return style;
},
});
layers = [
baselayergroup,
overlaygroup
];
var " . $this->config['id'] . " = new ol.Map({
target: '" . $this->config['id'] . "',
controls:[" . implode(',', $controls) . "],
layers: layers,
view: view
});
// Popup showing the position the user clicked
var container = document.getElementById('popup');
var closer = document.getElementById('popup-closer');
var content = document.getElementById('popup-content');
var popup = new ol.Overlay({
element: container,
autoPan: true,
autoPanAnimation: {
duration: 100
}
});
" . $this->config['id'] . ".addOverlay(popup);
closer.onclick = function () {
popup.setPosition(undefined);
content.innerHTML = '';
closer.blur();
return false;
};
";
}
/**
* The center and zoom level of the map
*
* @param float $lat: latitude
* @param float $lon: longitude
* @param int $zoom: zoom level
*
* @return string The JavaScript to set the center and zoom level
*/
public function getMapCenter($lat, $lon, $zoom)
{
return '
view.setCenter(ol.proj.transform([' . $lon . ', ' . $lat . '], \'EPSG:4326\', \'EPSG:3857\'));
view.setZoom(' . $zoom . ');
';
}
protected function getLayer($layer, $i, $backpath = '')
{
if (empty($layer['subdomains'])) {
$layer['subdomains'] = 'abc';
}
$layer['subdomains'] = substr($layer['subdomains'], 0, 1) . '-' . substr($layer['subdomains'], -1, 1);
$layer['tile_url'] = strtr($this->getTileUrl($layer), ['{s}' => '{' . $layer['subdomains'] . '}']);
if ($layer['overlay'] == 1) {
return $this->config['id'] . "_" . $i . "_overlayLayer =
new ol.layer.Tile({
visible: " . ($layer['visible'] == true ? 'true' : 'false') . ",
opacity: 0.99,
title: '" . $layer['title'] . "',
source: new ol.source.OSM({
url: '" . $layer['tile_url'] . "',
attributions: [
'" . $layer['attribution']. "'
]
})
});
overlaygroup.getLayers().push(" . $this->config['id'] . "_" . $i . "_overlayLayer);
";
}
return $this->config['id'] . "_" . $i . "_baselayergroup =
new ol.layer.Tile({
type: 'base',
combine: 'true',
visible: " . ($i == 0 ? 'true' : 'false') . ",
title: '" . $layer['title'] . "',
source: new ol.source.OSM({
url: '" . $layer['tile_url'] . "',
attributions: [
'" . $layer['attribution']. "'
]
})
});
baselayergroup.getLayers().push(" . $this->config['id'] . "_" . $i . "_baselayergroup);
";
}
/**
* Get the layer switcher
*
* @return string The JavaScript to add the layerswitcher
*/
protected function getLayerSwitcher()
{
return '
var layerSwitcher = new ol.control.LayerSwitcher({
activationMode: \'' . ($this->config['layerswitcher_activationMode'] == '1' ? 'click' : 'mouseover') . '\',
startActive: ' . ($this->config['show_layerswitcher'] == '2' ? 'true' : 'false') . ',
tipLabel: \'' . LocalizationUtility::translate('openlayers.showLayerList', 'OdsOsm') . '\',
collapseTipLabel: \'' . LocalizationUtility::translate('openlayers.hideLayerList', 'OdsOsm') . '\',
groupSelectStyle: \'children\',
reverse: false
});
' . $this->config['id'] . '.addControl(layerSwitcher);
';
}
/**
* Get the fullscreen button
*
* @return string The JavaScript to add the fullscreen button
*/
protected function getFullScreen()
{
return '
var fullScreen = new ol.control.FullScreen();
' . $this->config['id'] . '.addControl(fullScreen);
';
}
protected function getMarkers($markers)
{
$jsMarker = parent::getMarkers($markers);
// open popup? If yes, with click or hover?
switch ($this->config['show_popups']) {
case 1:
$eventMethod = 'singleclick';
break;
case 2:
$eventMethod = 'pointermove';
break;
default:
$eventMethod = false;
}
if ($eventMethod !== false) {
$jsMarker .= "
" . $this->config['id'] . ".on('" . $eventMethod . "', function (event) {
var feature = " . $this->config['id'] . ".forEachFeatureAtPixel(event.pixel, function (feat, layer) {
return feat;
});
var layer = " . $this->config['id'] . ".forEachFeatureAtPixel(event.pixel, function (feat, layer) {
return layer;
});
if (feature === undefined) {
return;
}
if (feature.get('features') === undefined) {
var coordinate = event.coordinate;
if (feature.get('desc') !== undefined) {
content.innerHTML = feature.get('desc');
} else {
// this might be some geoJSON data with properties set
var osm_popup = layer.get('popup');
var props = feature.values_,
ll = Object.keys(props),
attribute, value = '';
var osm_filter = layer.get('properties').split(',').map(item=>item.trim());
osm_filter.forEach((osm_prop) => {
if (typeof feature.get(osm_prop) !== 'undefined') {
value += '<dt>' + osm_prop + '</dt> <dd>' + feature.get(osm_prop) + '</dd>';
}
});
content.innerHTML = osm_popup + '<dl>' + value + '</dl>';
}
popup.setPosition(coordinate);
} else if (feature.get('features').length === 1) {
var singleFeature = feature.get('features')[0];
if (feature && singleFeature.get('type') == 'Point') {
var coordinate = event.coordinate;
content.innerHTML = singleFeature.get('desc');
popup.setPosition(coordinate);
}
} else if (feature && feature.get('type') == 'Point') {
var coordinate = event.coordinate;
content.innerHTML = feature.get('desc');
popup.setPosition(coordinate);
} else {
if (feature.get('features').length > 0) {
const clusterMembers = feature.get('features');
if (clusterMembers.length > 1) {
// Calculate the extent of the cluster members.
const extent = new ol.extent.createEmpty();
clusterMembers.forEach((feature) =>
ol.extent.extend(extent, feature.getGeometry().getExtent())
);
const view = " . $this->config['id'] . ".getView();
const resolution = " . $this->config['id'] . ".getView().getResolution();
if (
view.getZoom() === view.getMaxZoom() ||
(ol.extent.getWidth(extent) < resolution && ol.extent.getHeight(extent) < resolution)
) {
// Show an expanded view of the cluster members.
clickFeature = features[0];
clickResolution = resolution;
clusterCircles.setStyle(clusterCircleStyle);
} else {
// Zoom to the extent of the cluster members.
view.fit(extent, {duration: 600, padding: [100, 100, 100, 100]});
}
}
}
popup.setPosition(undefined);
}
});
";
}
// grouped marker layer
foreach ($this->layers[2] as $group_uid => $group) {
$jsMarker .= $group['layer'];
$jsMarkerFeatureBatch = [];
foreach ($group['jsMarkerFeatures'] as $id => $jsMarkerFeature) {
$jsMarker .= 'var ' . $group_uid . $id . ' = ' . $jsMarkerFeature;
$jsMarkerFeatureBatch[] = $group_uid . $id;
}
if ($this->config['cluster']) {
$jsMarker .= 'clusters.getSource().getSource().addFeatures([' . implode(',', $jsMarkerFeatureBatch) . ']);' . "\n";
} else {
$jsMarker .= $group_uid . '.getSource().addFeatures([' . implode(',', $jsMarkerFeatureBatch) . ']);' . "\n";
$jsMarker .= 'overlaygroup.getLayers().push(' . $group_uid . ');' . "\n";
}
}
if ($this->config['cluster']) {
// add cluster layer in overlaygroup
$jsMarker .= 'overlaygroup.getLayers().push(clusters);' . "\n";
}
return $jsMarker;
}
protected function getMarker($item, $table)
{
$jsMarker = '';
$jsElementVar = $table . '_' . $item['uid'];
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
// Convert item color hex value to rgba() as Openlayers doesn't have an opacity option.
if (empty($item['color'])) {
// set default blue, if nothing is given
$item['color'] = '#0009ff';
}
if (strlen($item['color']) == 7) {
$hex = [ $item['color'][1] . $item['color'][2], $item['color'][3] . $item['color'][4], $item['color'][5] . $item['color'][6] ];
$rgb = array_map('hexdec', $hex);
$opacity = '0.2';
$item['rgba'] = 'rgba('.implode(",", $rgb).','.$opacity.')';
}
switch ($table) {
case 'tx_odsosm_track':
$fileObjects = $fileRepository->findByRelation('tx_odsosm_track', 'file', $item['uid']);
if ($fileObjects) {
$file = $fileObjects[0];
} else {
break;
}
// Add tracks to layerswitcher
$this->layers[1][] = [
'title' => $item['title'],
'table' => $table,
'uid' => $item['uid']
];
// define style from given color and width
$jsMarker .= 'var ' . $jsElementVar . '_style = new ol.style.Style({
stroke: new ol.style.Stroke({
color: \''.$item['color'].'\',
width: '.($item['width'] ?: 1).'
}),
fill: new ol.style.Fill({
color: \''.$item['rgba'].'\'
}),
});';
switch (strtolower(pathinfo($file->getName(), PATHINFO_EXTENSION))) {
case 'kml':
$jsMarker .= 'var ' . $jsElementVar . '_gpx = new ol.layer.Vector({
title: \'' .$item['title'] . '\',
source: new ol.source.Vector({
projection: \'EPSG:3857\',
url: \'' . $file->getPublicUrl() . '\',
format: new ol.format.KML()
}),
style: ' . $jsElementVar . '_style
});' . "\n";
$jsMarker .= "overlaygroup.getLayers().push(" . $jsElementVar . "_gpx);";
break;
case 'gpx':
$jsMarker .= 'var ' . $jsElementVar . '_gpx = new ol.layer.Vector({
title: \'' .$item['title'] . '\',
source: new ol.source.Vector({
projection: \'EPSG:3857\',
url: \'' . $file->getPublicUrl() . '\',
format: new ol.format.GPX()
}),
style: ' . $jsElementVar . '_style
});' . "\n";
$jsMarker .= "overlaygroup.getLayers().push(" . $jsElementVar . "_gpx);";
break;
}
break;
case 'tx_odsosm_vector':
$fileObjects = $fileRepository->findByRelation('tx_odsosm_vector', 'file', $item['uid']);
// define style from given color and width
$jsMarker .= 'var ' . $jsElementVar . '_style = new ol.style.Style({
stroke: new ol.style.Stroke({
color: \''.$item['color'].'\',
width: '.($item['width'] ?: 1).'
}),
fill: new ol.style.Fill({
color: \''.$item['rgba'].'\'
}),
});' . "\n";
if ($fileObjects) {
$file = $fileObjects[0];
$filename = $file->getPublicUrl();
$properties = [
'popup' => $item['popup'] ?? '',
'properties' => $item['properties'],
];
$jsMarker .= 'var ' . $jsElementVar . '_file_properties = ' . json_encode($properties) . ';';
$jsMarker .= 'var ' . $jsElementVar . '_file = new ol.layer.Vector({
title: \'' .$item['title'] . ' ('. LocalizationUtility::translate('file', 'OdsOsm') .')\',
source: new ol.source.Vector({
projection: \'EPSG:3857\',
url: \'' . $filename . '\',
format: new ol.format.GeoJSON()
}),
style: ' . $jsElementVar . '_style,
properties: ' . $jsElementVar . '_file_properties,
});' . "\n";
$jsMarker .= $jsElementVar . "_file.getSource().setProperties(" . $jsElementVar . "_file_properties);";
$jsMarker .= "overlaygroup.getLayers().push(" . $jsElementVar . "_file);";
}
// add geojson from data field as well
if ($item['data']) {
$properties = [
'popup' => $item['popup'] ? $item['popup'] . '<br />' : '',
'properties' => $item['properties'],
];
$jsMarker .= 'const ' . $jsElementVar . '_geojsonObject = '. $item['data'] . ';';
$jsMarker .= 'var ' . $jsElementVar . '_data_properties = ' . json_encode($properties) . ';';
$jsMarker .= 'var ' . $jsElementVar . '_data = new ol.layer.Vector({
title: \'' .$item['title'] . '\',
source: new ol.source.Vector({
features: new ol.format.GeoJSON({
featureProjection:"EPSG:3857"
}).readFeatures(' . $jsElementVar . '_geojsonObject)
}),
style: ' . $jsElementVar . '_style
});';
$jsMarker .= $jsElementVar . "_data.setProperties(" . $jsElementVar . "_data_properties);";
$jsMarker .= "overlaygroup.getLayers().push(" . $jsElementVar . "_data);";
}
break;
default:
$markerOptions = [];
if ($item['tx_odsosm_marker'] ?? false) {
$marker = $item['tx_odsosm_marker'];
if ($marker['type'] == 'html') {
$markerOptions['icon'] = 'icon: new L.divIcon(' . json_encode($marker['icon']) . ')';
} else {
$icon = $marker['icon']->getPublicUrl();
$markerOptions['icon'] = 'icon: new L.Icon(' . json_encode($icon) . ')';
}
} else {
$icon = '/typo3conf/ext/ods_osm/Resources/Public/Icons/marker-icon.png';
$marker['size_x'] = 25;
$marker['size_y'] = 41;
}
$markerStyle = "const " . $jsElementVar . "_style = new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 46],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
src: '" . $icon ."',
width: " . (int)$marker['size_x'] . ",
height: " . (int)$marker['size_y'] . "
}),
});";
// It's a group of markers
if ($item['group_title'] ?? false) {
if (empty($jsMarkerGroup)) {
$jsMarker .= $markerStyle;
$group_title = ($marker['type'] == 'html' ? $icon : "<img class='marker-icon' src='" . $icon . "' />") . ' ' . $item['group_title'];
$jsMarkerGroup = "
var " . $item['group_uid'] . " = new ol.layer.Vector({
title: \"" . $group_title . "\",
source: new ol.source.Vector({
features: []
}),
style: " . $jsElementVar . "_style
});";
$this->layers[2][$item['group_uid']]['layer'] = $jsMarkerGroup;
}
$popupJsCode = "
function (layer) {
var osm_popup = '" . ($item['popup'] ?? '') . "';
var feature = layer.feature,
props = feature.properties,
ll = Object.keys(props),
attribute, value = '';
for (attribute in props) {
value += '<strong>' + attribute + '</strong>: ' + props[attribute] + '<br />';
}
return osm_popup + value;
}
";
$jsMarkerFeature = "
new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat([" . $item['longitude'] . ", " . $item['latitude'] . "])),
type: 'Point',
desc: " . json_encode($item['popup']) . ",
style: " . $jsElementVar . "_style
});";
$this->layers[2][$item['group_uid']]['jsMarkerFeatures'][] = $jsMarkerFeature;
} else {
$jsMarker .= $markerStyle;
$jsMarker .= "var " . $jsElementVar . " = new ol.layer.Vector({
title: '<img src=\"" .$icon . "\" class=\"marker-icon\" /> " . ($item['group_title'] ?? $item['name']) . "',
source: new ol.source.Vector({
features: [
new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat([" . $item['longitude'] . ", " . $item['latitude'] . "])),
type: 'Point',
desc: " . json_encode($item['popup']) . "
})
]
}),
style: " . $jsElementVar . "_style
});";
$jsMarker .= 'overlaygroup.getLayers().push(' . $jsElementVar . ');' . "\n";
}
break;
}
return $jsMarker;
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Bobosch\OdsOsm\Provider;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Core\Environment;
class Staticmap extends BaseProvider
{
protected $uploadPath = 'fileadmin/tx_odsosm/staticmap';
public function getMap($layers, $markers, $lon, $lat, $zoom)
{
$marker = [];
foreach ($markers as $table => $items) {
foreach ($items as $item) {
switch ($table) {
case 'tx_odsosm_track':
case 'tx_odsosm_vector':
break;
default:
$lon = $item['longitude'];
$lat = $item['latitude'];
if ($item['tx_odsosm_marker'] ?? false) {
$marker = $item['tx_odsosm_marker'];
$icon = $marker['icon'];
} else {
$marker = ['size_x' => 21, 'size_y' => 25, 'offset_x' => -11, 'offset_y' => -25];
$icon = 'EXT:ods_osm/Resources/Public/Icons/marker-icon.png';
}
break 3;
}
}
}
// set reasonable defaults for width and height (100% and vh/vw does not work with staticmap)
if ((int)$this->config['width'] <= 100) {
$this->config['width'] = 640;
}
if ((int)$this->config['height'] <= 100) {
$this->config['height'] = 480;
}
$markerUrl = [
'###lon###' => $lon,
'###lat###' => $lat,
'###zoom###' => $zoom,
'###width###' => (int)$this->config['width'],
'###height###' => (int)$this->config['height'],
];
$layer = array_shift($layers);
$url = strtr($layer[0]['static_url'], $markerUrl);
$this->uploadPath = Environment::getPublicPath() . '/' . $this->uploadPath;
if (!is_dir($this->uploadPath)) {
GeneralUtility::mkdir_deep($this->uploadPath);
}
$filename = $this->uploadPath . '/' . md5($url) . '.png';
// Cache image
$cache = false;
if (file_exists($filename)) {
$cache = filectime($filename) > time() - 7 * 24 * 60 * 60;
}
if (!$cache) {
$referer = $_SERVER['HTTP_REFERER'];
$opts = [
'http'=>[
'header'=>["Referer: $referer\r\n"]
]
];
$context = stream_context_create($opts);
$image = file_get_contents($url, false, $context);
if ($image) {
file_put_contents($filename, $image);
}
}
// Generate image tag
$config = [
'file' => 'GIFBUILDER',
'file.' => [
'format' => 'png',
'XY' => '[10.w],[10.h]',
'10' => 'IMAGE',
'10.' => [
'file' => $filename,
]
],
];
if ($marker['offset_x'] ?? null) {
$config['file.']['20'] = 'IMAGE';
$config['file.']['20.'] = [
'offset' => ((int)$this->config['width'] / 2 + (int)$marker['offset_x']) . ',' . ((int)$this->config['height'] / 2 + (int)$marker['offset_y']),
'file' => $icon,
];
}
$content = $this->cObj->cObjGetSingle('IMAGE', $config);
return ($content);
}
}

View File

@@ -0,0 +1,365 @@
<?php
namespace Bobosch\OdsOsm;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use \geoPHP\geoPHP;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class TceMain
{
public $lon = [];
public $lat = [];
// ['t3lib/class.t3lib_tcemain.php']['processDatamapClass']
public function processDatamap_preProcessFieldArray(&$incomingFieldArray, $table, $id, $obj)
{
}
/**
* Generate a different preview link *
*
* @param string $status status
* @param string $table table name
* @param int $id id of the record
* @param array $fieldArray fieldArray
* @param DataHandler $parentObject parent Object
*/
public function processDatamap_afterDatabaseOperations(
$status,
$table,
$id,
array $fieldArray,
DataHandler $parentObject
): void {
// guard statement, abort here if no ods_osm table
if (strpos($table, 'tx_odsosm_') !== 0) {
return;
}
/*
* The id may be integer already or the temporary NEW id. This depends, how the record was created
*
* case 1:
* - the user creates a tx_odsosm_track record
* - the file is added to the not yet saved record
* - the record is saved (status="new")
*
* case 2:
* - the user creates a tx_odsosm_track record and saves it
* - the user remains in the dialog and adds the file
* - the user saves again (status="update")
*
* This hook is run for sys_file_reference and for tx_odsosm_track. We only do our work on tx_odsosm_track:
* - in case 1 --> id is not integer yet but the temporary NEW id
* - in case 2 --> id is integer
*/
if ($status == "new") {
$id = $parentObject->substNEWwithIDs[$id] ?? '';
}
if (!is_int($id)) {
return;
}
/*
* If ods_osm is installed via composer, the class geoPHP is already known.
* Otherwise we use the copy in the local folder which is only available in the TER package.
*/
if (!class_exists(geoPHP::class)) {
require_once 'phar://' . ExtensionManagementUtility::extPath('ods_osm', 'Resources/Private/geophp.phar/vendor/autoload.php');
}
switch ($table) {
case 'tx_odsosm_track':
if (is_int($id)) {
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$fileObjects = $fileRepository->findByRelation('tx_odsosm_track', 'file', $id);
}
if ($fileObjects) {
$file = $fileObjects[0];
} else {
break;
}
$filename = Environment::getPublicPath() . '/' . $file->getPublicUrl();
if (file_exists($filename)) {
try {
$polygon = geoPHP::load(file_get_contents($filename), pathinfo($filename, PATHINFO_EXTENSION));
} catch (\Exception $e) {
// silently ignore failure of parsing data
break;
}
$box = $polygon->getBBox();
// unfortunately we cannot pass the new values by reference in this hook, because the database operation is already done.
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$queryBuilder
->update('tx_odsosm_track')
->where(
$queryBuilder->expr()->eq('uid', $id)
)
->set('min_lon', sprintf('%01.6f', $box['minx']))
->set('min_lat', sprintf('%01.6f', $box['miny']))
->set('max_lon', sprintf('%01.6f', $box['maxx']))
->set('max_lat', sprintf('%01.6f', $box['maxy']))
->executeStatement();
}
break;
case 'tx_odsosm_marker':
if (is_int($id)) {
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$fileObjects = $fileRepository->findByRelation('tx_odsosm_marker', 'icon', $id);
}
if ($fileObjects) {
$file = $fileObjects[0];
} else {
break;
}
$filename = Environment::getPublicPath() . '/' . $file->getPublicUrl();
if (file_exists($filename)) {
$size = getimagesize($filename);
if ($size) {
// unfortunately we cannot pass the new values by reference in this hook, because the database operation is already done.
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$queryBuilder
->update('tx_odsosm_marker')
->where(
$queryBuilder->expr()->eq('uid', $id)
)
->set('size_x', $size[0])
->set('size_y', $size[1])
->set('offset_x', -round($size[0] / 2))
->set('offset_y', -$size[1])
->executeStatement();
}
}
break;
case 'tx_odsosm_vector':
if (is_int($id)) {
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$fileObjects = $fileRepository->findByRelation('tx_odsosm_vector', 'file', $id);
}
if ($fileObjects) {
$file = $fileObjects[0];
} else {
break;
}
$filename = Environment::getPublicPath() . '/' . $file->getPublicUrl();
if (file_exists($filename)) {
try {
$polygon = geoPHP::load(file_get_contents($filename), pathinfo($filename, PATHINFO_EXTENSION));
} catch (\Exception $e) {
// silently ignore failure of parsing geojson
break;
}
$box = $polygon->getBBox();
if ($box) {
// unfortunately we cannot pass the new values by reference in this hook, because the database operation is already done.
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
// write the bbox into database
$queryBuilder
->update('tx_odsosm_vector')
->where(
$queryBuilder->expr()->eq('uid', $id)
)
->set('min_lon', sprintf('%01.6f', $box['minx']))
->set('min_lat', sprintf('%01.6f', $box['miny']))
->set('max_lon', sprintf('%01.6f', $box['maxx']))
->set('max_lat', sprintf('%01.6f', $box['maxy']))
->executeStatement();
// handle properties
$properties = (array)$polygon->getData();
if (empty($properties)) {
// seems to contain multiple polygones
$components = $polygon->getComponents();
// take the properties of the first polygon
$properties = (array)$components[0]->getData();
}
if (!empty($properties)) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$result = $queryBuilder
->select('properties', 'properties_from_file')
->from('tx_odsosm_vector')
->where(
$queryBuilder->expr()->eq('uid', $id)
)
->setMaxResults(1)
->executeQuery();
if ($row = $result->fetchAssociative()) {
if ($row['properties_from_file'] && !empty($properties)) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$queryBuilder
->update('tx_odsosm_vector')
->where(
$queryBuilder->expr()->eq('uid', $id)
)
->set('properties', implode(', ', array_keys($properties)))
->set('properties_from_file', 0)
->executeStatement();
}
}
}
}
}
break;
}
}
// ['t3lib/class.t3lib_tcemain.php']['processDatamapClass']
public function processDatamap_postProcessFieldArray($status, $table, $id, &$fieldArray, $obj): void
{
switch ($table) {
case 'tx_odsosm_vector':
if (!empty($fieldArray['data'] ?? false)) {
$this->lon = [];
$this->lat = [];
/*
* If ods_osm is installed via composer, the class geoPHP is already known.
* Otherwise we use the copy in the local folder which is only available in the TER package.
*/
if (!class_exists(geoPHP::class)) {
require_once 'phar://' . ExtensionManagementUtility::extPath('ods_osm', 'Resources/Private/geophp.phar/vendor/autoload.php');
}
try {
$polygon = geoPHP::load(($fieldArray['data']));
} catch (\Exception $e) {
// silently ignore failure of parsing geojson
break;
}
if ($polygon) {
$box = $polygon->getBBox();
$fieldArray['min_lon'] = sprintf('%01.6f', $box['minx']);
$fieldArray['min_lat'] = sprintf('%01.6f', $box['miny']);
$fieldArray['max_lon'] = sprintf('%01.6f', $box['maxx']);
$fieldArray['max_lat'] = sprintf('%01.6f', $box['maxy']);
// handle properties
$properties = [];
$properties = (array)$polygon->getData();
if (empty($properties)) {
// seems to contain multiple polygones
$components = $polygon->getComponents();
// take the properties of the first polygon
$properties = (array)$components[0]->getData();
}
if (!empty($properties)) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$result = $queryBuilder
->select('properties', 'properties_from_file')
->from('tx_odsosm_vector')
->where(
$queryBuilder->expr()->eq('uid', $id)
)
->setMaxResults(1)
->executeQuery();
if ($row = $result->fetchAssociative()) {
if ($row['properties_from_file'] && !empty($properties)) {
$fieldArray['properties'] = implode(', ', array_keys($properties));
$fieldArray['properties_from_file'] = 0;
}
}
}
} else {
$fieldArray['min_lon'] = 0;
$fieldArray['min_lat'] = 0;
$fieldArray['max_lon'] = 0;
$fieldArray['max_lat'] = 0;
}
}
break;
default:
$tc = Div::getTableConfig($table);
if ($tc['lon'] ?? false) {
if (
(isset($tc['address']) && ($fieldArray[$tc['address']] ?? null))
|| (isset($tc['street']) && ($fieldArray[$tc['street']] ?? null))
|| (isset($tc['zip']) && ($fieldArray[$tc['zip']] ?? null))
|| (isset($tc['city']) && ($fieldArray[$tc['city']] ?? null))
) {
$config = Div::getConfig(['autocomplete']);
// Search coordinates
if ($config['autocomplete']) {
// Generate address array with standard keys
$address = [];
foreach ($tc as $def => $field) {
if ($def == strtolower($def)) {
$address[$def] = $obj->datamap[$table][$id][$field];
}
}
if ($config['autocomplete'] == 2 || (float) ($address['longitude'] ?? 0) == 0) {
$ll = Div::updateAddress($address);
if ($ll) {
// Optimize address
$address['lon'] = sprintf($tc['FORMAT'], $address['lon']);
$address['lat'] = sprintf($tc['FORMAT'], $address['lat']);
if (($address['type'] ?? false) == 'structured') {
if (isset($tc['address']) && !isset($tc['street'])) {
if ($address['street'] ?? false) {
$address['address'] = $address['street'];
if ($address['housenumber'] ?? false) {
$address['address'] .= ' ' . $address['housenumber'];
}
}
}
} elseif ($tc['address'] ?? false) {
if ($address['street'] ?? false) {
$address['address'] = $address['street'];
if ($address['housenumber'] ?? false) {
$address['address'] .= ' ' . $address['housenumber'];
}
$address['address'] .= ', ' . $address['zip'] . ' ' . $address['city'];
$address['address'] .= ', ' . $address['country'];
}
}
// Update fieldArray if address is set
foreach ($tc as $def => $field) {
if ($def != strtolower($def)) {
continue;
}
if (!$address[$def]) {
continue;
}
$fieldArray[$field] = $address[$def];
}
}
}
}
}
}
break;
}
}
}

View File

@@ -0,0 +1,374 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2020 Alexander Bigga <alexander@bigga.de>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
namespace Bobosch\OdsOsm\Updates;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
use TYPO3\CMS\Install\Updates\ChattyInterface;
use Doctrine\DBAL\DBALException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Console\Output\OutputInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\ResourceStorage;
use TYPO3\CMS\Core\Resource\StorageRepository;
/**
* Migrate location of marker and track files to new, FAL-based location
*/
class FileLocationUpdater implements UpgradeWizardInterface, ChattyInterface, LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* @var OutputInterface
*/
protected $output;
/**
* @var ResourceStorage
*/
protected $storage;
/**
* Array with table and fields to migrate
*
* @var string
*/
protected $fieldsToMigrate = [
'tx_odsosm_marker' => 'icon',
'tx_odsosm_track' => 'file'
];
/**
* the source file resides here
*
* @var string
*/
protected $sourcePath = 'uploads/tx_odsosm/';
/**
* target folder after migration
* Relative to fileadmin
*
* @var string
*/
protected $targetPath = '_migrated/tx_odsosm/';
/**
* Return the identifier for this wizard
* This must be the same string as used in the ext_localconf class registration
*
* @return string
*/
public function getIdentifier(): string
{
return 'odsOsmFileLocationUpdater';
}
/**
* Return the speaking name of this wizard
*
* @return string
*/
public function getTitle(): string
{
return 'EXT:ods_osm: Migrate used files to FAL';
}
/**
* Get description
*
* @return string Longer description of this updater
*/
public function getDescription(): string
{
return 'Move marker images and track files of EXT:ods_osm to fileadmin/_migrated/tx_odsosm/ and convert reference in records.';
}
/**
* Is an update necessary?
*
* Is used to determine whether a wizard needs to be run.
* Check if data for migration exists.
*
* @return bool
*/
public function updateNecessary(): bool
{
$numRecords = $this->falGetRecordsFromTable(true);
if ($numRecords > 0) {
return true;
}
return false;
}
/**
* @return string[] All new fields and tables must exist
*/
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class
];
}
/**
* @param OutputInterface $output
*/
public function setOutput(OutputInterface $output): void
{
$this->output = $output;
}
/**
* Execute the update
*
* Called when a wizard reports that an update is necessary
*
* @return bool
*/
public function executeUpdate(): bool
{
$result = true;
try {
$numRecords = $this->falGetRecordsFromTable(true);
if ($numRecords > 0) {
$this->falPerformUpdate();
}
} catch (\Exception $e) {
// If something goes wrong, migrateField() logs an error
$result = false;
}
return $result;
}
/**
* Get records from table where the field to migrate is not empty (NOT NULL and != '')
* and also not numeric (which means that it is migrated)
*
* Work based on BackendLayoutIconUpdateWizard::class
*
* @return array|int
* @throws \RuntimeException
*/
protected function falGetRecordsFromTable($countOnly = false)
{
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$allResults = [];
$numResults = 0;
foreach(array_keys($this->fieldsToMigrate) as $table) {
$queryBuilder = $connectionPool->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()->removeAll();
try {
$result = $queryBuilder
->select('uid', 'pid', $this->fieldsToMigrate[$table])
->from($table)
->where(
$queryBuilder->expr()->isNotNull($this->fieldsToMigrate[$table]),
$queryBuilder->expr()->neq(
$this->fieldsToMigrate[$table],
$queryBuilder->createNamedParameter('', Connection::PARAM_STR)
),
$queryBuilder->expr()->comparison(
'CAST(CAST(' . $queryBuilder->quoteIdentifier($this->fieldsToMigrate[$table]) . ' AS DECIMAL) AS CHAR)',
ExpressionBuilder::NEQ,
'CAST(' . $queryBuilder->quoteIdentifier($this->fieldsToMigrate[$table]) . ' AS CHAR)'
)
)
->orderBy('uid')
->executeQuery()
->fetchAllAssociative();
if ($countOnly === true) {
$numResults += count($result);
} else {
$allResults[$table] = $result;
}
} catch (DBALException $e) {
throw new \RuntimeException(
'Database query failed. Error was: ' . $e->getPrevious()->getMessage(),
1511950673
);
}
}
if ($countOnly === true) {
return $numResults;
}
return $allResults;
}
/**
* Performs the database update.
*
* @return bool TRUE on success, FALSE on error
*/
protected function falPerformUpdate(): bool
{
$result = true;
try {
$storages = GeneralUtility::makeInstance(StorageRepository::class)->findAll();
$this->storage = $storages[0];
$records = $this->falGetRecordsFromTable();
foreach ($records as $table => $recordsInTable) {
foreach ($recordsInTable as $record) {
$this->migrateField($table, $record);
}
}
} catch (\Exception $e) {
$result = false;
}
return $result;
}
/**
* Migrates a single field.
*
* @param string $table
* @param array $row
* @throws \Exception
*/
protected function migrateField($table, $row)
{
$fieldItem = trim($row[$this->fieldsToMigrate[$table]]);
if (empty($fieldItem) || is_numeric($fieldItem)) {
return;
}
$fileadminDirectory = rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/';
$i = 0;
$storageUid = (int)$this->storage->getUid();
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$fileUid = null;
$sourcePath = Environment::getPublicPath() . '/' . $this->sourcePath . $fieldItem;
$targetDirectory = Environment::getPublicPath() . '/' . $fileadminDirectory . $this->targetPath;
$targetPath = $targetDirectory . basename($fieldItem);
// maybe the file was already moved, so check if the original file still exists
if (file_exists($sourcePath)) {
if (!is_dir($targetDirectory)) {
GeneralUtility::mkdir_deep($targetDirectory);
}
// see if the file already exists in the storage
$fileSha1 = sha1_file($sourcePath);
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file');
$existingFileRecord = $queryBuilder->select('uid')->from('sys_file')->where($queryBuilder->expr()->eq(
'missing',
$queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
), $queryBuilder->expr()->eq(
'sha1',
$queryBuilder->createNamedParameter($fileSha1, Connection::PARAM_STR)
), $queryBuilder->expr()->eq(
'storage',
$queryBuilder->createNamedParameter($storageUid, Connection::PARAM_INT)
))->executeQuery()->fetchAssociative();
// the file exists, the file does not have to be moved again
if (is_array($existingFileRecord)) {
$fileUid = $existingFileRecord['uid'];
} else {
// just move the file (no duplicate)
rename($sourcePath, $targetPath);
}
}
if ($fileUid === null) {
// get the File object if it hasn't been fetched before
try {
// if the source file does not exist, we should just continue, but leave a message in the docs;
// ideally, the user would be informed after the update as well.
/** @var File $file */
$file = $this->storage->getFile($this->targetPath . $fieldItem);
$fileUid = $file->getUid();
} catch (\InvalidArgumentException $e) {
// no file found, no reference can be set
$this->logger->notice(
'File ' . $this->sourcePath . $fieldItem . ' does not exist. Reference was not migrated.',
[
'table' => $table,
'record' => $row,
'field' => $fieldItem,
]
);
$format = 'File \'%s\' does not exist. Referencing field: %s.%d.%s. The reference was not migrated.';
$this->output->writeln(sprintf(
$format,
$this->sourcePath . $fieldItem,
$table,
$row['uid'],
$fieldItem
));
return;
}
}
if ($fileUid > 0) {
$fields = [
'fieldname' => $this->fieldsToMigrate[$table],
'table_local' => 'sys_file',
'pid' => ($table === 'pages' ? $row['uid'] : $row['pid']),
'uid_foreign' => $row['uid'],
'uid_local' => $fileUid,
'tablenames' => $table,
'crdate' => time(),
'tstamp' => time(),
'sorting_foreign' => $i,
];
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference');
$queryBuilder->insert('sys_file_reference')->values($fields)->executeStatement();
++$i;
}
// Update referencing table's original field to now contain the count of references,
// but only if all new references could be set
if ($i === 1) {
$queryBuilder = $connectionPool->getQueryBuilderForTable($table);
$queryBuilder->update($table)->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($row['uid'], Connection::PARAM_INT)
)
)->set($this->fieldsToMigrate[$table], $i)->executeStatement();
}
}
}

View File

@@ -0,0 +1,235 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2022 Alexander Bigga <alexander@bigga.de>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
namespace Bobosch\OdsOsm\Updates;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
/**
* Migrate flexform settings to keep existing configuration valid.
*/
class MigrateSettings implements UpgradeWizardInterface
{
/**
* Return the identifier for this wizard
* This must be the same string as used in the ext_localconf class registration
*
* @return string
*/
public function getIdentifier(): string
{
return 'odsOsmMigrateSettings';
}
/**
* Return the speaking name of this wizard
*
* @return string
*/
public function getTitle(): string
{
return 'EXT:ods_osm: Migrate plugin flexform settings';
}
/**
* Return the description for this wizard
*
* @return string
*/
public function getDescription(): string
{
return 'This wizard migrates some flexform settings which has changed in ods_osm' .
' extension. This makes the full reconfiguration of all used plugins obsolete.';
}
/**
* Execute the update
*
* Called when a wizard reports that an update is necessary
*
* @return bool
*/
public function executeUpdate(): bool
{
// Get all tt_content data of ods_osm and update their flexforms settings
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
/** @var QueryBuilder $queryBuilder */
$queryBuilder = $connection->createQueryBuilder();
$statement = $queryBuilder->select('uid')
->addSelect('pi_flexform')
->from('tt_content')->where($queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('list')), $queryBuilder->expr()->like('list_type', $queryBuilder->createNamedParameter('ods_osm_%')))->executeQuery();
// Update the found record sets
while ($record = $statement->fetchAssociative()) {
// Robust error handling in case pi_flexform is NULL or empty
if (!($record['pi_flexform'] ?? false)) {
continue;
}
$oldXml = $record['pi_flexform'];
$newXml = $this->migrateFlexformSettings($record['pi_flexform']);
if ($oldXml === $newXml) {
// robust error handling:
// if no change is necessary, this record was probably already converted and we skip the SQL UPDATE
continue;
}
$queryBuilder = $connection->createQueryBuilder();
$updateResult = $queryBuilder->update('tt_content')
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($record['uid'], Connection::PARAM_INT)
)
)->set('pi_flexform', $newXml)->executeStatement();
// exit if at least one update statement is not successful
if (!((bool) $updateResult)) {
return false;
}
}
return true;
}
/**
* Is an update necessary?
*
* Looks for ods_osm plugins in tt_content table to be migrated
*
* @return bool
*/
public function updateNecessary(): bool
{
$oldSettingsFound = false;
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
/** @var QueryBuilder $queryBuilder */
$queryBuilder = $connection->createQueryBuilder();
$statement = $queryBuilder->select('uid')
->addSelect('pi_flexform')
->from('tt_content')->where($queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('list')), $queryBuilder->expr()->like('list_type', $queryBuilder->createNamedParameter('ods_osm_%')))->executeQuery();
// Update the found record sets
while ($record = $statement->fetchAssociative()) {
if (!($record['pi_flexform'] ?? false)) {
continue;
}
$oldSettingsFound = $this->checkForOldSettings($record['pi_flexform']);
if ($oldSettingsFound) {
// We found at least one field to be updated --> break here
break;
}
}
return $oldSettingsFound;
}
/**
* Returns an array of class names of Prerequisite classes
*
* This way a wizard can define dependencies like "database up-to-date" or
* "reference index updated"
*
* @return string[]
*/
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class
];
}
/**
* @param string $oldValue
* @return string|bool
*/
protected function migrateFlexformSettings(string $oldValue): ?string
{
$xml = simplexml_load_string($oldValue);
// if something went wrong, return.
if ($xml === false) {
return false;
}
// get all field elements
$library = $xml->xpath("//field[@index='library'][1]");
// get all field elements
$fields = $xml->xpath("//field");
foreach ($fields as $field) {
if ($library[0]->value != 'staticmap' && $field['index'] == $library[0]->value . '_layer') {
// rename base layer field to base_layer
$field['index'] = 'base_layer';
// Copy all layers into new 'overlays' field. This is easier here, doesn't hurt the
// frontend and will be filtered to only real 'overlays' on next saving the plugin flexform.
$overlays = $xml->data->sheet->language->addChild('field');
$overlays->addAttribute('index', 'overlays');
$overlays->addChild('value', $field->value)->addAttribute('index', 'vDEF');
} elseif ($field['index'] != $library[0]->value . '_layer' && ($field['index'] == 'layer' ||
$field['index'] == 'openlayers_layer' ||
$field['index'] == 'openlayers3_layer' || $field['index'] == 'leaflet_layer')) {
// remove all other, unused layer fields from flexform xml
unset($field[0]);
}
}
return $xml->asXML();
}
/**
* @param string $flexFormXml
* @return bool
*/
protected function checkForOldSettings(string $flexFormXml): bool
{
$xml = simplexml_load_string($flexFormXml);
// if something went wrong, return.
if ($xml === false) {
return false;
}
// check for existing values of attribute "index"
// * openlayers_layer
// * leaflet_layer
// * layer
$fields = $xml->xpath("//field[@index='openlayers_layer'] | //field[@index='leaflet_layer'] | //field[@index='layer']");
return (bool) $fields;
}
}

View File

@@ -0,0 +1,117 @@
<?php
declare(strict_types = 1);
namespace Bobosch\OdsOsm\Wizard;
/**
* This file is part of the "ods_osm" Extension for TYPO3 CMS.
* It's based on LocationMapWizard of "tt_address".
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use Bobosch\OdsOsm\Div;
use TYPO3\CMS\Backend\Form\AbstractNode;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\StringUtility;
/**
* Adds a wizard for location selection via map
*/
class CoordinatepickerWizard extends AbstractNode
{
/**
* @return array
*/
public function render(): array
{
$row = $this->data['databaseRow'];
$paramArray = $this->data['parameterArray'];
$resultArray = $this->initializeResultArray();
$extConfig = Div::getConfig();
$nameLongitude = $paramArray['itemFormElName'];
if (strpos($nameLongitude, '[pi_flexform]') > 0) {
// it's a call inside a flexform
$lon = $row["pi_flexform"]["data"]["sDEF"]["lDEF"]["lon"]["vDEF"] != '' ? htmlspecialchars($row["pi_flexform"]["data"]["sDEF"]["lDEF"]["lon"]["vDEF"]) : '';
$lat = $row["pi_flexform"]["data"]["sDEF"]["lDEF"]["lat"]["vDEF"] != '' ? htmlspecialchars($row["pi_flexform"]["data"]["sDEF"]["lDEF"]["lat"]["vDEF"]) : '';
} else {
$lat = $row['tx_odsosm_lat'] != '' ? htmlspecialchars($row['tx_odsosm_lat']) : '';
$lon = $row['tx_odsosm_lon'] != '' ? htmlspecialchars($row['tx_odsosm_lon']) : '';
}
$nameLatitude = str_replace('lon', 'lat', $nameLongitude);
$nameLatitudeActive = str_replace('data', 'control[active]', $nameLatitude);
$geoCodeUrl = '';
$geoCodeUrlShort = '';
if (empty((float)$lat) || empty((float)$lon)) {
// remove all after first slash in address (top, floor ...)
$address = preg_replace('/^([^\/]*).*$/', '$1', $row['address'] ?? '') . ' ';
$address .= $row['city'] ?? '';
// if we have at least some address part (saves geocoding calls)
if (trim($address)) {
// base url
$geoCodeUrlBase = 'https://nominatim.openstreetmap.org/search/';
$geoCodeUrlAddress = $address;
$geoCodeUrlCityOnly = ($row['city'] ?? '');
// urlparams for nominatim which are fixed.
$geoCodeUrlQuery = '?format=json&addressdetails=1&limit=1&polygon_svg=1';
// replace newlines with spaces; remove multiple spaces
$geoCodeUrl = trim(preg_replace('/\s\s+/', ' ', $geoCodeUrlBase . $geoCodeUrlAddress . $geoCodeUrlQuery));
$geoCodeUrlShort = trim(preg_replace('/\s\s+/', ' ', $geoCodeUrlBase . $geoCodeUrlCityOnly . $geoCodeUrlQuery));
}
}
$resultArray['iconIdentifier'] = 'coordinate-picker-wizard';
$resultArray['title'] = $this->getLanguageService()->sL('LLL:EXT:ods_osm/Resources/Private/Language/locallang_db.xlf:coordinatepickerWizard');
$resultArray['linkAttributes']['class'] = 'coordinatepickerWizard ';
$resultArray['linkAttributes']['data-label-title'] = $this->getLanguageService()->sL('LLL:EXT:ods_osm/Resources/Private/Language/locallang_db.xlf:coordinatepickerWizard.title');
$resultArray['linkAttributes']['data-label-close'] = $this->getLanguageService()->sL('LLL:EXT:ods_osm/Resources/Private/Language/locallang_db.xlf:coordinatepickerWizard.close');
$resultArray['linkAttributes']['data-label-import'] = $this->getLanguageService()->sL('LLL:EXT:ods_osm/Resources/Private/Language/locallang_db.xlf:coordinatepickerWizard.import');
$resultArray['linkAttributes']['data-lat'] = $lat;
$resultArray['linkAttributes']['data-lon'] = $lon;
$resultArray['linkAttributes']['data-default-lat'] = $extConfig['default_lat'];
$resultArray['linkAttributes']['data-default-lon'] = $extConfig['default_lon'];
$resultArray['linkAttributes']['data-default-zoom'] = $extConfig['default_zoom'];
$resultArray['linkAttributes']['data-geocodeurl'] = $geoCodeUrl;
$resultArray['linkAttributes']['data-geocodeurlshort'] = $geoCodeUrlShort;
$resultArray['linkAttributes']['data-namelat'] = htmlspecialchars($nameLatitude);
$resultArray['linkAttributes']['data-namelon'] = htmlspecialchars($nameLongitude);
$resultArray['linkAttributes']['data-namelat-active'] = htmlspecialchars($nameLatitudeActive);
$resultArray['linkAttributes']['data-tiles'] = htmlspecialchars('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
$resultArray['linkAttributes']['data-copy'] = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
$resultArray['stylesheetFiles'][] = 'EXT:ods_osm/Resources/Public/JavaScript/Leaflet/Core/leaflet.css';
$resultArray['stylesheetFiles'][] = 'EXT:ods_osm/Resources/Public/Css/Backend/leafletBackend.css';
$versionInformation = GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion();
if ($versionInformation > 11) {
$id = StringUtility::getUniqueId('t3js-formengine-fieldcontrol-');
$resultArray['requireJsModules'][] = JavaScriptModuleInstruction::forRequireJS(
'TYPO3/CMS/OdsOsm/Leaflet/Core/leaflet'
)->instance($id);
$resultArray['requireJsModules'][] = JavaScriptModuleInstruction::forRequireJS(
'TYPO3/CMS/OdsOsm/Backend/LeafletBackend'
)->instance($id);
} else {
$resultArray['requireJsModules'][] = 'TYPO3/CMS/OdsOsm/Leaflet/Core/leaflet';
$resultArray['requireJsModules'][] = 'TYPO3/CMS/OdsOsm/Backend/LeafletBackend';
}
return $resultArray;
}
/**
* @return LanguageService
*/
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types = 1);
namespace Bobosch\OdsOsm\Wizard;
/**
* This file is part of the "ods_osm" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use Bobosch\OdsOsm\Div;
use TYPO3\CMS\Backend\Form\AbstractNode;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\StringUtility;
/**
* Adds a wizard for drawing vectors on a map
*/
class VectordrawWizard extends AbstractNode
{
/**
* @return array
*/
public function render(): array
{
$row = $this->data['databaseRow'];
$paramArray = $this->data['parameterArray'];
$resultArray = $this->initializeResultArray();
$extConfig = Div::getConfig();
$nameDataField = $paramArray['itemFormElName'];
// calculate center point or use Kopenhagen as fallback
if (!empty((float)$row['max_lon']) && !empty((float)$row['min_lon'])) {
$lon = ($row['max_lon'] + $row['min_lon']) / 2;
} else {
$lon = $extConfig['default_lon'];
}
if (!empty((float)$row['max_lat']) && !empty((float)$row['min_lat'])) {
$lat = ($row['max_lat'] + $row['min_lat']) / 2;
} else {
$lat = $extConfig['default_lat'];
}
$resultArray['iconIdentifier'] = 'vectordraw-wizard';
$resultArray['title'] = $this->getLanguageService()->sL('LLL:EXT:ods_osm/Resources/Private/Language/locallang_db.xlf:vectordrawWizard');
$resultArray['linkAttributes']['class'] = 'vectordrawWizard ';
$resultArray['linkAttributes']['data-label-title'] = $this->getLanguageService()->sL('LLL:EXT:ods_osm/Resources/Private/Language/locallang_db.xlf:vectordrawWizard.title');
$resultArray['linkAttributes']['data-label-close'] = $this->getLanguageService()->sL('LLL:EXT:ods_osm/Resources/Private/Language/locallang_db.xlf:vectordrawWizard.close');
$resultArray['linkAttributes']['data-label-import'] = $this->getLanguageService()->sL('LLL:EXT:ods_osm/Resources/Private/Language/locallang_db.xlf:vectordrawWizard.import');
$resultArray['linkAttributes']['data-minlat'] = empty((float)$row['min_lat']) ? null : $row['min_lat'];
$resultArray['linkAttributes']['data-maxlat'] = empty((float)$row['max_lat']) ? null : $row['max_lat'];
$resultArray['linkAttributes']['data-minlon'] = empty((float)$row['min_lon']) ? null : $row['min_lon'];
$resultArray['linkAttributes']['data-maxlon'] = empty((float)$row['max_lon']) ? null : $row['max_lon'];
$resultArray['linkAttributes']['data-lat'] = $lat;
$resultArray['linkAttributes']['data-lon'] = $lon;
$resultArray['linkAttributes']['data-fieldName'] = htmlspecialchars($nameDataField);
$resultArray['linkAttributes']['data-fieldValue'] = $row['data'];
$resultArray['linkAttributes']['data-tiles'] = htmlspecialchars('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
$resultArray['linkAttributes']['data-copy'] = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
$resultArray['stylesheetFiles'][] = 'EXT:ods_osm/Resources/Public/JavaScript/Leaflet/leaflet-draw/leaflet.draw.css';
$resultArray['stylesheetFiles'][] = 'EXT:ods_osm/Resources/Public/JavaScript/Leaflet/Core/leaflet.css';
$resultArray['stylesheetFiles'][] = 'EXT:ods_osm/Resources/Public/Css/Backend/drawvectorWizard.css';
$versionInformation = GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion();
if ($versionInformation > 11) {
$id = StringUtility::getUniqueId('t3js-formengine-fieldcontrol-');
$resultArray['requireJsModules'][] = JavaScriptModuleInstruction::forRequireJS(
'TYPO3/CMS/OdsOsm/Leaflet/Core/leaflet'
)->instance($id);
$resultArray['requireJsModules'][] = JavaScriptModuleInstruction::forRequireJS(
'TYPO3/CMS/OdsOsm/Backend/Vectordraw'
)->instance($id);
} else {
$resultArray['requireJsModules'][] = 'TYPO3/CMS/OdsOsm/Leaflet/Core/leaflet';
$resultArray['requireJsModules'][] = 'TYPO3/CMS/OdsOsm/Backend/Vectordraw';
}
return $resultArray;
}
/**
* @return LanguageService
*/
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}