From 0dac7fcb0450e43efe64733afab5752199e43fde Mon Sep 17 00:00:00 2001 From: Jim Safley Date: Tue, 28 Apr 2026 22:06:49 -0400 Subject: [PATCH 01/24] Add support for multiple locations per item Items can now have any number of named locations. The admin edit form uses a Leaflet.draw toolbar to add, reposition, and delete markers. Each marker has an optional label editable via popup. The browse map, item show pages, exhibit map blocks, and static site export all reflect multiple locations. --- GeolocationPlugin.php | 229 +++++++++--------- controllers/MapController.php | 2 +- models/Api/Location.php | 31 +-- models/Location.php | 15 +- models/Table/Location.php | 35 +-- plugin.ini | 2 +- views/helpers/GeolocationMapSingle.php | 79 +++--- .../geolocation-map/layout.php | 23 +- .../leaflet-draw/images/spritesheet-2x.png | Bin 0 -> 3581 bytes .../leaflet-draw/images/spritesheet.png | Bin 0 -> 1906 bytes .../leaflet-draw/images/spritesheet.svg | 156 ++++++++++++ .../javascripts/leaflet-draw/leaflet.draw.css | 10 + .../javascripts/leaflet-draw/leaflet.draw.js | 10 + views/shared/javascripts/map.js | 224 +++++++++-------- views/shared/map/browse.kml.php | 23 +- views/shared/map/input-partial.php | 38 +-- 16 files changed, 534 insertions(+), 343 deletions(-) create mode 100644 views/shared/javascripts/leaflet-draw/images/spritesheet-2x.png create mode 100644 views/shared/javascripts/leaflet-draw/images/spritesheet.png create mode 100644 views/shared/javascripts/leaflet-draw/images/spritesheet.svg create mode 100644 views/shared/javascripts/leaflet-draw/leaflet.draw.css create mode 100644 views/shared/javascripts/leaflet-draw/leaflet.draw.js diff --git a/GeolocationPlugin.php b/GeolocationPlugin.php index 3a4a30a..55ca191 100644 --- a/GeolocationPlugin.php +++ b/GeolocationPlugin.php @@ -60,6 +60,7 @@ public function hookInstall() `zoom_level` INT NOT NULL , `map_type` VARCHAR( 255 ) NOT NULL , `address` TEXT NOT NULL , + `label` VARCHAR( 255 ) NOT NULL DEFAULT '' , INDEX (`item_id`)) ENGINE = InnoDB"; $db->query($sql); @@ -163,6 +164,10 @@ public function hookUpgrade($args) set_option('geolocation_basemap', $stamenBasemaps[$currentBasemap]); } } + if (version_compare($args['old_version'], '3.4', '<')) { + $db = get_db(); + $db->query("ALTER TABLE `$db->Location` ADD COLUMN `label` VARCHAR(255) NOT NULL DEFAULT '' AFTER `address`"); + } } /** @@ -258,6 +263,9 @@ public function hookDefineRoutes($args) public function hookAdminHead($args) { $this->_head(); + $version = $this->_getVersion(); + queue_css_file('leaflet-draw/leaflet.draw', null, null, 'javascripts', $version); + queue_js_file('leaflet-draw/leaflet.draw', 'javascripts', [], $version); } public function hookPublicHead($args) @@ -265,11 +273,15 @@ public function hookPublicHead($args) $this->_head(); } - private function _head() + private function _getVersion() { $pluginLoader = Zend_Registry::get('plugin_loader'); - $geolocation = $pluginLoader->getPlugin('Geolocation'); - $version = $geolocation->getIniVersion(); + return $pluginLoader->getPlugin('Geolocation')->getIniVersion(); + } + + private function _head() + { + $version = $this->_getVersion(); queue_css_file('leaflet/leaflet', null, null, 'javascripts', $version); queue_css_file('geolocation-marker', null, null, 'css', $version); queue_js_file(['leaflet/leaflet', 'leaflet/leaflet-providers', 'map'], 'javascripts', [], $version); @@ -287,32 +299,39 @@ public function hookAfterSaveItem($args) } $item = $args['record']; - // If we don't have the geolocation form on the page, don't do anything! - if (!isset($post['geolocation'])) { + // geolocation_form_shown is a sentinel set by input-partial.php. Its + // presence means the map form was rendered, so missing geolocation[] + // inputs mean all markers were deleted, not that the form was absent. + if (!isset($post['geolocation_form_shown'])) { return; } - // Find the location object for the item - $location = $this->_db->getTable('Location')->findLocationByItem($item, true); + $remaining = []; + foreach ($this->_db->getTable('Location')->findBy(['item_id' => $item->id]) as $loc) { + $remaining[$loc->id] = $loc; + } - // If we have filled out info for the geolocation, then submit to the db - $geolocationPost = $post['geolocation']; - if (!empty($geolocationPost) - && $geolocationPost['latitude'] != '' - && $geolocationPost['longitude'] != '' - ) { - if (!$location) { + foreach ($post['geolocation'] ?? [] as $entry) { + if (!is_numeric($entry['latitude'] ?? null) || !is_numeric($entry['longitude'] ?? null)) { + continue; + } + $id = !empty($entry['id']) ? (int) $entry['id'] : null; + if ($id && isset($remaining[$id])) { + $location = $remaining[$id]; + unset($remaining[$id]); + } else { $location = new Location; $location->item_id = $item->id; } - $location->setPostData($geolocationPost); + // Exclude 'id' so a crafted POST cannot cause setPostData to set + // the id on a new record, which would trigger an UPDATE on a row + // belonging to a different item. + $location->setPostData(array_diff_key($entry, ['id' => null])); $location->save(); - } else { - // If the form is empty, then we want to delete whatever location is - // currently stored - if ($location) { - $location->delete(); - } + } + + foreach ($remaining as $loc) { + $loc->delete(); } } @@ -320,9 +339,9 @@ public function hookAdminItemsShowSidebar($args) { $view = $args['view']; $item = $args['item']; - $location = $this->_db->getTable('Location')->findLocationByItem($item, true); + $locations = $this->_db->getTable('Location')->findBy(['item_id' => $item->id]); - if ($location) { + if (!empty($locations)) { $html = '' . '
' . '

' . __('Geolocation') . '

' @@ -341,9 +360,9 @@ public function hookPublicItemsShow($args) $view = $args['view']; $item = $args['item']; - $location = $this->_db->getTable('Location')->findLocationByItem($item, true); + $locations = $this->_db->getTable('Location')->findBy(['item_id' => $item->id]); - if ($location) { + if (!empty($locations)) { $width = $this->_filterCssLength(get_option('geolocation_item_map_width'), '100%'); $height = $this->_filterCssLength(get_option('geolocation_item_map_height'), '300px'); $html = "
"; @@ -579,16 +598,17 @@ public function filterApiResources($apiResources) public function filterApiExtendItems($extend, $args) { $item = $args['record']; - $location = $this->_db->getTable('Location')->findBy(['item_id' => $item->id]); - if (!$location) { + $locations = $this->_db->getTable('Location')->findBy(['item_id' => $item->id]); + if (!$locations) { return $extend; } - $locationId = $location[0]['id']; - $extend['geolocations'] = [ - 'id' => $locationId, - 'url' => Omeka_Record_Api_AbstractRecordAdapter::getResourceUrl("/geolocations/$locationId"), - 'resource' => 'geolocations', - ]; + $extend['geolocations'] = array_map(function ($loc) { + return [ + 'id' => $loc->id, + 'url' => Omeka_Record_Api_AbstractRecordAdapter::getResourceUrl('/geolocations/' . $loc->id), + 'resource' => 'geolocations', + ]; + }, $locations); return $extend; } @@ -602,7 +622,7 @@ public function hookContributionTypeForm($args) if (get_option('geolocation_add_map_to_contribution_form')) { $contributionType = $args['type']; $view = $args['view']; - echo $this->_mapForm(null, __('Find A Geographic Location For The %s:', $contributionType->display_name), false, $view, null); + echo $this->_mapForm(null, __('Find A Geographic Location For The %s:', $contributionType->display_name), $view); } } @@ -697,72 +717,58 @@ public function geolocationShortcode($args) * @param array $post * @return string Html string. */ - protected function _mapForm($item, $label = '', $confirmLocationChange = true, $view = null, $post = null) + protected function _mapForm($item, $label = '', $view = null) { - $html = ''; - if (is_null($view)) { $view = get_view(); } - // Need to be translated. if ($label == '') { $label = __('Find a Location by Address:'); } + $center = $this->_getCenter(); $center['show'] = false; - $location = $this->_db->getTable('Location')->findLocationByItem($item, true); - - if (is_null($post)) { - $post = $_POST; - } - - $usePost = !empty($post) - && !empty($post['geolocation']) - && $post['geolocation']['longitude'] != '' - && $post['geolocation']['latitude'] != ''; - if ($usePost) { - $lng = empty($post['geolocation']['longitude']) ? '' : (float) $post['geolocation']['longitude']; - $lat = empty($post['geolocation']['latitude']) ? '' : (float) $post['geolocation']['latitude']; - $zoom = empty($post['geolocation']['zoom_level']) ? '' : (int) $post['geolocation']['zoom_level']; - $address = html_escape($post['geolocation']['address']); - } else { - if ($location) { - $lng = (float) $location['longitude']; - $lat = (float) $location['latitude']; - $zoom = (int) $location['zoom_level']; - $address = html_escape($location['address']); - } else { - $lng = $lat = $zoom = $address = ''; + // If the form was previously submitted (e.g. save failed validation), + // re-populate from POST so unsaved changes are not lost. + $existingLocations = []; + if (isset($_POST['geolocation_form_shown'])) { + foreach ($_POST['geolocation'] ?? [] as $entry) { + if (!is_numeric($entry['latitude'] ?? null) || !is_numeric($entry['longitude'] ?? null)) { + continue; + } + $existingLocations[] = [ + 'id' => !empty($entry['id']) ? (int) $entry['id'] : null, + 'latitude' => (float) $entry['latitude'], + 'longitude' => (float) $entry['longitude'], + 'zoom_level' => (int) ($entry['zoom_level'] ?? 0), + 'address' => $entry['address'] ?? '', + 'label' => $entry['label'] ?? '', + ]; + } + } elseif ($item && $item->id) { + foreach ($this->_db->getTable('Location')->findBy(['item_id' => $item->id]) as $loc) { + $existingLocations[] = [ + 'id' => $loc->id, + 'latitude' => $loc->latitude, + 'longitude' => $loc->longitude, + 'zoom_level' => $loc->zoom_level, + 'address' => $loc->address, + 'label' => $loc->label, + ]; } } - // Prepare javascript. $options = []; - $options['form'] = [ - 'id' => 'location_form', - 'posted' => $usePost, - ]; - if ($location or $usePost) { - $options['point'] = [ - 'latitude' => $lat, - 'longitude' => $lng, - 'zoomLevel' => $zoom, - ]; - $center = $options['point']; - } - $options['confirmLocationChange'] = $confirmLocationChange; + $options['form'] = ['id' => 'location_form']; $options['cluster'] = false; return $view->partial('map/input-partial.php', [ 'label' => $label, - 'address' => $address, 'center' => $center, 'options' => $options, - 'lng' => $lng, - 'lat' => $lat, - 'zoom' => $zoom, + 'existingLocations' => $existingLocations, ]); } @@ -872,17 +878,7 @@ public function hookStaticSiteExportSiteExportPost($args) $locations = []; foreach ($locationRows as $locationRow) { $item = get_db()->getTable('Item')->find($locationRow->item_id); - $locations[] = [ - 'latitude' => $locationRow->latitude, - 'longitude' => $locationRow->longitude, - 'zoomLevel' => $locationRow->zoom_level, - 'mapType' => $locationRow->map_type, - 'address' => $locationRow->address, - 'itemID' => $item->id, - 'itemTitle' => $item->getDisplayTitle(), - 'fileID' => $item->getFile() ? $item->getFile()->id : null, - 'hasThumbnail' => $item->hasThumbnail(), - ]; + $locations[] = $this->_locationToStaticSiteExportArray($locationRow, $item); } $job->makeFile('content/geolocation/geolocation_locations.json', json_encode($locations)); } @@ -897,8 +893,8 @@ public function hookStaticSiteExportItemBundle($args) $frontMatterPage = $args['front_matter_page']; $blocks = $args['blocks']; - $location = get_db()->getTable('Location')->findLocationByItem($item, true); - if (!$location) { + $itemLocations = get_db()->getTable('Location')->findBy(['item_id' => $item->id]); + if (empty($itemLocations)) { return; } @@ -909,17 +905,10 @@ public function hookStaticSiteExportItemBundle($args) $frontMatterPage['js'][] = 'vendor/omeka-geolocation/geolocation-locations.js'; // Make the locations file. - $locations = [[ - 'latitude' => $location->latitude, - 'longitude' => $location->longitude, - 'zoomLevel' => $location->zoom_level, - 'mapType' => $location->map_type, - 'address' => $location->address, - 'itemID' => $item->id, - 'itemTitle' => $item->getDisplayTitle(), - 'fileID' => $item->getFile() ? $item->getFile()->id : null, - 'hasThumbnail' => $item->hasThumbnail(), - ]]; + $locations = []; + foreach ($itemLocations as $location) { + $locations[] = $this->_locationToStaticSiteExportArray($location, $item); + } $job->makeFile( sprintf('content/items/%s/geolocation_locations.json', $item->id), json_encode($locations) @@ -965,21 +954,10 @@ public function hookExhibitBuilderStaticSiteExportExhibitPageBlock($args) $locations = []; foreach ($attachments as $attachment) { $item = $attachment->getItem(); - $location = get_db()->getTable('Location')->findLocationByItem($item, true); - if (!$location) { - continue; + $itemLocations = get_db()->getTable('Location')->findBy(['item_id' => $item->id]); + foreach ($itemLocations as $location) { + $locations[] = $this->_locationToStaticSiteExportArray($location, $item); } - $locations[] = [ - 'latitude' => $location->latitude, - 'longitude' => $location->longitude, - 'zoomLevel' => $location->zoom_level, - 'mapType' => $location->map_type, - 'address' => $location->address, - 'itemID' => $item->id, - 'itemTitle' => $item->getDisplayTitle(), - 'fileID' => $item->getFile() ? $item->getFile()->id : null, - 'hasThumbnail' => $item->hasThumbnail(), - ]; } $job->makeFile( sprintf('content/exhibits/%s/%s/geolocation_locations.json', $exhibit->slug, $exhibitPage->slug), @@ -992,4 +970,21 @@ public function hookExhibitBuilderStaticSiteExportExhibitPageBlock($args) $exhibitPage->slug ); } + + private function _locationToStaticSiteExportArray(Location $location, Item $item) + { + $file = $item->getFile(); + return [ + 'latitude' => $location->latitude, + 'longitude' => $location->longitude, + 'zoomLevel' => $location->zoom_level, + 'mapType' => $location->map_type, + 'address' => $location->address, + 'label' => $location->label, + 'itemID' => $item->id, + 'itemTitle' => $item->getDisplayTitle(), + 'fileID' => $file ? $file->id : null, + 'hasThumbnail' => $item->hasThumbnail(), + ]; + } } diff --git a/controllers/MapController.php b/controllers/MapController.php index 3560f81..c5d02f0 100644 --- a/controllers/MapController.php +++ b/controllers/MapController.php @@ -22,7 +22,7 @@ public function browseAction() if ($this->_helper->contextSwitch->getCurrentContext() == 'kml') { $items = $table->findBy($params, $limit, $currentPage); $this->view->items = $items; - $this->view->locations = $locationTable->findLocationByItem($items); + $this->view->locations = $locationTable->findLocationsByItem($items); } else { $this->view->totalItems = $table->count($params); $this->view->params = $params; diff --git a/models/Api/Location.php b/models/Api/Location.php index 7063412..daee456 100644 --- a/models/Api/Location.php +++ b/models/Api/Location.php @@ -27,6 +27,7 @@ public function getRepresentation(Omeka_Record_AbstractRecord $record) 'zoom_level' => $record->zoom_level, 'map_type' => $record->map_type, 'address' => $record->address, + 'label' => $record->label, 'item' => [ 'id' => $record->item_id, 'url' => $this->getResourceUrl("/items/{$record->item_id}"), @@ -47,25 +48,7 @@ public function setPostData(Omeka_Record_AbstractRecord $record, $data) if (isset($data->item->id)) { $record->item_id = $data->item->id; } - if (isset($data->latitude)) { - $record->latitude = $data->latitude; - } - if (isset($data->longitude)) { - $record->longitude = $data->longitude; - } - if (isset($data->zoom_level)) { - $record->zoom_level = $data->zoom_level; - } - if (isset($data->map_type)) { - $record->map_type = $data->map_type; - } else { - $record->map_type = ''; - } - if (isset($data->address)) { - $record->address = $data->address; - } else { - $record->address = ''; - } + $this->_applyLocationFields($record, $data); } /** @@ -75,6 +58,11 @@ public function setPostData(Omeka_Record_AbstractRecord $record, $data) * @param mixed $data */ public function setPutData(Omeka_Record_AbstractRecord $record, $data) + { + $this->_applyLocationFields($record, $data); + } + + private function _applyLocationFields(Omeka_Record_AbstractRecord $record, $data) { if (isset($data->latitude)) { $record->latitude = $data->latitude; @@ -95,5 +83,10 @@ public function setPutData(Omeka_Record_AbstractRecord $record, $data) } else { $record->address = ''; } + if (isset($data->label)) { + $record->label = $data->label; + } else { + $record->label = ''; + } } } diff --git a/models/Location.php b/models/Location.php index 32c2c7f..7b68beb 100644 --- a/models/Location.php +++ b/models/Location.php @@ -12,6 +12,7 @@ class Location extends Omeka_Record_AbstractRecord implements Zend_Acl_Resource_ public $zoom_level; public $map_type; public $address; + public $label; /** * Executes before the record is saved. @@ -24,6 +25,9 @@ protected function beforeSave($args) if (is_null($this->address)) { $this->address = ''; } + if (is_null($this->label)) { + $this->label = ''; + } } /** @@ -38,18 +42,13 @@ protected function _validate() if (!$this->getTable('Item')->exists($this->item_id)) { $this->addError('item_id', __('Location requires a valid item ID.')); } - // An item can only have one location. This assumes that updating an - // existing location will never modify the item ID. - if (!$this->exists() && $this->getTable()->findBy(['item_id' => $this->item_id])) { - $this->addError('latitude', __('A location already exists for the provided item.')); - } - if (empty($this->latitude)) { + if (!is_numeric($this->latitude)) { $this->addError('latitude', __('Location requires a latitude.')); } - if (empty($this->longitude)) { + if (!is_numeric($this->longitude)) { $this->addError('longitude', __('Location requires a longitude.')); } - if (empty($this->zoom_level)) { + if (!is_numeric($this->zoom_level)) { $this->addError('zoom_level', __('Location requires a zoom level.')); } } diff --git a/models/Table/Location.php b/models/Table/Location.php index c6d160b..90d94c2 100644 --- a/models/Table/Location.php +++ b/models/Table/Location.php @@ -2,12 +2,12 @@ class Table_Location extends Omeka_Db_Table { /** - * Returns a location (or array of locations) for an item (or array of items) - * @param array|Item|int $item An item or item id, or an array of items or item ids - * @param boolean $findOnlyOne Whether or not to return only one location if it exists for the item - * @return array|Location A location or an array of locations - **/ - public function findLocationByItem($item, $findOnlyOne = false) + * Returns all locations for an item or array of items, grouped by item_id. + * + * @param array|Item|int $item + * @return array item_id => Location[] + */ + public function findLocationsByItem($item) { $db = get_db(); @@ -16,11 +16,10 @@ public function findLocationByItem($item, $findOnlyOne = false) } elseif (is_array($item) && !count($item)) { return []; } + $alias = $this->getTableAlias(); - // Create a SELECT statement for the Location table $select = $db->select()->from([$alias => $db->Location], "$alias.*"); - // Create a WHERE condition that will pull down all the location info if (is_array($item)) { $itemIds = []; foreach ($item as $it) { @@ -32,24 +31,12 @@ public function findLocationByItem($item, $findOnlyOne = false) $select->where("$alias.item_id = ?", $itemId); } - // If only a single location is request, return the first one found. - if ($findOnlyOne) { - $location = $this->fetchObject($select); - return $location; - } - - // Get the locations. $locations = $this->fetchObjects($select); - - // Return an associative array of locations where the key is the item_id of the location - // Note: Since each item can only have one location, this makes sense to associate a single location with a single item_id. - // However, if in the future, an item can have multiple locations, then we cannot just associate a single location with a single item_id; - // Instead, in the future, we would have to associate an array of locations with a single item_id. - $indexedLocations = []; - foreach ($locations as $k => $loc) { - $indexedLocations[$loc['item_id']] = $loc; + $grouped = []; + foreach ($locations as $loc) { + $grouped[$loc->item_id][] = $loc; } - return $indexedLocations; + return $grouped; } /** diff --git a/plugin.ini b/plugin.ini index 28c2979..9ad90d8 100644 --- a/plugin.ini +++ b/plugin.ini @@ -7,4 +7,4 @@ link="https://omeka.org/classic/docs/Plugins/Geolocation/" support_link="https://forum.omeka.org/c/omeka-classic/plugins" omeka_minimum_version="2.5" omeka_target_version="3.0" -version="3.3" +version="3.4" diff --git a/views/helpers/GeolocationMapSingle.php b/views/helpers/GeolocationMapSingle.php index 6c61812..4fae62a 100644 --- a/views/helpers/GeolocationMapSingle.php +++ b/views/helpers/GeolocationMapSingle.php @@ -2,46 +2,55 @@ class Geolocation_View_Helper_GeolocationMapSingle extends Zend_View_Helper_Abstract { - public function geolocationMapSingle($item = null, $width = '200px', $height = '200px', $hasBalloonForMarker = false, $markerHtmlClassName = 'geolocation_balloon') + public function geolocationMapSingle($item = null, $width = '200px', $height = '200px') { $divId = "item-map-{$item->id}"; - $location = get_db()->getTable('Location')->findLocationByItem($item, true); - // Only set the center of the map if this item actually has a location - // associated with it - if ($location) { - $center['latitude'] = $location->latitude; - $center['longitude'] = $location->longitude; - $center['zoomLevel'] = $location->zoom_level; - $center['show'] = true; - if ($hasBalloonForMarker) { - $titleLink = link_to_item(metadata($item, ['Dublin Core', 'Title'], [], $item), [], 'show', $item); - $thumbnailLink = !(item_image('thumbnail')) ? '' : link_to_item(item_image('thumbnail', [], 0, $item), [], 'show', $item); - $description = metadata($item, ['Dublin Core', 'Description'], ['snippet' => 150], $item); - $center['markerHtml'] = '
' - . '
' . $titleLink . '
' - . '
' . $thumbnailLink . '
' - . '

' . $description . '

'; - } + $locations = get_db()->getTable('Location')->findBy(['item_id' => $item->id]); - $options = []; - $options['basemap'] = get_option('geolocation_basemap'); - $options = $this->view->geolocationMapOptions($options); - $center = js_escape($center); - $varDivId = Inflector::variablize($divId); - - $style = "width:$width;height:$height"; - $divAttrs = [ - 'id' => $divId, - 'class' => 'map geolocation-map', - 'style' => $style, - ]; + if (empty($locations)) { + return '

' . __('This item has no location info associated with it.') . '

'; + } - $html = '
'; - $js = "var $varDivId" . "OmekaMapSingle = new OmekaMapSingle(" . js_escape($divId) . ", $center, $options); "; - $html .= ""; - } else { - $html = '

'.__('This item has no location info associated with it.').'

'; + $center = [ + 'latitude' => $locations[0]->latitude, + 'longitude' => $locations[0]->longitude, + 'zoomLevel' => $locations[0]->zoom_level, + 'show' => false, + ]; + + $points = []; + foreach ($locations as $loc) { + $point = [ + 'latitude' => $loc->latitude, + 'longitude' => $loc->longitude, + 'zoomLevel' => $loc->zoom_level, + ]; + if ($loc->label !== '') { + $point['markerHtml'] = '
' + . '
' . html_escape($loc->label) . '
' + . '
'; + } + $points[] = $point; } + + $options = []; + $options['basemap'] = get_option('geolocation_basemap'); + $options['points'] = $points; + $options = $this->view->geolocationMapOptions($options); + $center = js_escape($center); + $varDivId = Inflector::variablize($divId); + + $style = "width:$width;height:$height"; + $divAttrs = [ + 'id' => $divId, + 'class' => 'map geolocation-map', + 'style' => $style, + ]; + + $html = '
'; + $js = "var $varDivId" . "OmekaMapSingle = new OmekaMapSingle(" . js_escape($divId) . ", $center, $options); "; + $html .= ""; + return $html; } } diff --git a/views/shared/exhibit_layouts/geolocation-map/layout.php b/views/shared/exhibit_layouts/geolocation-map/layout.php index 1d041c7..85a0a7d 100644 --- a/views/shared/exhibit_layouts/geolocation-map/layout.php +++ b/views/shared/exhibit_layouts/geolocation-map/layout.php @@ -10,20 +10,19 @@ foreach ($attachments as $attachment): $item = $attachment->getItem(); $file = $attachment->getFile(); - $location = $locationTable->findLocationByItem($item, true); - if ($location): - $titleLink = exhibit_builder_link_to_exhibit_item(null, [], $item); + $titleLink = exhibit_builder_link_to_exhibit_item(null, [], $item); - // Manually print just the caption as body when there's no file to avoid - // double-printing the title link. - if ($file): - $body = $this->exhibitAttachment($attachment, [], [], true); - else: - $body = $this->exhibitAttachmentCaption($attachment); - endif; + if ($file): + $body = $this->exhibitAttachment($attachment, [], [], true); + else: + $body = $this->exhibitAttachmentCaption($attachment); + endif; + $itemLocations = $locationTable->findBy(['item_id' => $item->id]); + foreach ($itemLocations as $location): + $title = $titleLink . ($location->label ? ' — ' . html_escape($location->label) : ''); $html = '
' - . '
' . $titleLink . '
' + . '
' . $title . '
' . $body . '
'; $locations[] = [ @@ -31,7 +30,7 @@ 'lng' => $location->longitude, 'html' => $html, ]; - endif; + endforeach; endforeach; ?>