diff --git a/GeolocationPlugin.php b/GeolocationPlugin.php index 3a4a30a..649440f 100644 --- a/GeolocationPlugin.php +++ b/GeolocationPlugin.php @@ -34,8 +34,6 @@ class GeolocationPlugin extends Omeka_Plugin_AbstractPlugin protected $_filters = [ 'admin_navigation_main', 'public_navigation_main', - 'response_contexts', - 'action_contexts', 'admin_items_form_tabs', 'public_navigation_items', 'api_resources', @@ -58,8 +56,8 @@ public function hookInstall() `latitude` DOUBLE NOT NULL , `longitude` DOUBLE NOT NULL , `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 +161,10 @@ public function hookUpgrade($args) set_option('geolocation_basemap', $stamenBasemaps[$currentBasemap]); } } + if (version_compare($args['old_version'], '4.0', '<')) { + $db = get_db(); + $db->query("ALTER TABLE `$db->Location` ADD COLUMN `label` VARCHAR(255) NOT NULL DEFAULT '' AFTER `address`, DROP COLUMN `map_type`"); + } } /** @@ -244,15 +246,6 @@ public function hookDefineRoutes($args) ]); $router->addRoute('items_map', $mapRoute); - // Trying to make the route look like a KML file so google will eat it. - // @todo Include page parameter if this works. - $kmlRoute = new Zend_Controller_Router_Route_Regex('geolocation/map\.kml', [ - 'controller' => 'map', - 'action' => 'browse', - 'module' => 'geolocation', - 'output' => 'kml', - ]); - $router->addRoute('map_kml', $kmlRoute); } public function hookAdminHead($args) @@ -267,12 +260,11 @@ public function hookPublicHead($args) private function _head() { - $pluginLoader = Zend_Registry::get('plugin_loader'); - $geolocation = $pluginLoader->getPlugin('Geolocation'); - $version = $geolocation->getIniVersion(); + $version = Zend_Registry::get('plugin_loader')->getPlugin('Geolocation')->getIniVersion(); queue_css_file('leaflet/leaflet', null, null, 'javascripts', $version); + queue_css_file('leaflet-draw/leaflet.draw', null, null, 'javascripts', $version); queue_css_file('geolocation-marker', null, null, 'css', $version); - queue_js_file(['leaflet/leaflet', 'leaflet/leaflet-providers', 'map'], 'javascripts', [], $version); + queue_js_file(['leaflet/leaflet', 'leaflet/leaflet-providers', 'leaflet-draw/leaflet.draw', 'map'], 'javascripts', [], $version); if (get_option('geolocation_cluster')) { queue_css_file(['MarkerCluster', 'MarkerCluster.Default'], null, null, 'javascripts/leaflet-markercluster', $version); @@ -287,32 +279,42 @@ 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 an empty geolocation_locations + // value means 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); + // Build an index of existing locations. As POST entries are matched to existing + // records, they are removed from $remaining. Whatever is left at the end was + // not in the POST and gets deleted. + $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 (json_decode($post['geolocation_locations'] ?? '[]', true) 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 +322,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 ($locations) { $html = '' . '
' . '

' . __('Geolocation') . '

' @@ -341,9 +343,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 ($locations) { $width = $this->_filterCssLength(get_option('geolocation_item_map_width'), '100%'); $height = $this->_filterCssLength(get_option('geolocation_item_map_height'), '300px'); $html = "
"; @@ -518,22 +520,6 @@ public function filterPublicNavigationMain($navArray) return $navArray; } - public function filterResponseContexts($contexts) - { - $contexts['kml'] = ['suffix' => 'kml', - 'headers' => ['Content-Type' => 'text/xml']]; - return $contexts; - } - - public function filterActionContexts($contexts, $args) - { - $controller = $args['controller']; - if ($controller instanceof Geolocation_MapController) { - $contexts['browse'] = ['kml']; - } - return $contexts; - } - public function filterAdminItemsFormTabs($tabs, $args) { // insert the map tab before the Miscellaneous tab @@ -563,8 +549,11 @@ public function filterPublicNavigationItems($navArray) public function filterApiResources($apiResources) { $apiResources['geolocations'] = [ - 'record_type' => 'Location', - 'actions' => ['get', 'index', 'post', 'put', 'delete'], + 'record_type' => 'Location', + 'actions' => ['get', 'index', 'post', 'put', 'delete'], + // Whitelist item_id as an allowed GET param on the index action; + // without this the API router rejects ?item_id=X requests. + 'index_params' => ['item_id'], ]; return $apiResources; } @@ -579,14 +568,15 @@ 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']; + // count+url is the Omeka API format for multi-resource references; + // ApiController validates this shape and rejects plain arrays of objects. $extend['geolocations'] = [ - 'id' => $locationId, - 'url' => Omeka_Record_Api_AbstractRecordAdapter::getResourceUrl("/geolocations/$locationId"), + 'count' => count($locations), + 'url' => Omeka_Record_Api_AbstractRecordAdapter::getResourceUrl('/geolocations') . '?item_id=' . $item->id, 'resource' => 'geolocations', ]; return $extend; @@ -602,7 +592,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); } } @@ -692,89 +682,75 @@ public function geolocationShortcode($args) * @param Item $item * @param string $label if empty string, a default string will be used. Set * null if you don't want a label. - * @param boolean $confirmLocationChange * @param Omeka_View $view - * @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 (json_decode($_POST['geolocation_locations'] ?? '[]', true) 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, + // For no-location items this sets the default center. + // For single-location items this sets the initial zoom correctly. + // For multi-location items fitBounds overrides the center on tab select. + $center = [ + 'latitude' => (float) get_option('geolocation_default_latitude'), + 'longitude' => (float) get_option('geolocation_default_longitude'), + 'zoomLevel' => (float) get_option('geolocation_default_zoom_level'), ]; - if ($location or $usePost) { - $options['point'] = [ - 'latitude' => $lat, - 'longitude' => $lng, - 'zoomLevel' => $zoom, - ]; - $center = $options['point']; + if ($existingLocations) { + $center['latitude'] = $existingLocations[0]['latitude']; + $center['longitude'] = $existingLocations[0]['longitude']; + $center['zoomLevel'] = $existingLocations[0]['zoom_level']; } - $options['confirmLocationChange'] = $confirmLocationChange; + + $options = []; + $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, ]); } - protected function _getCenter() - { - return [ - 'latitude' => (float) get_option('geolocation_default_latitude'), - 'longitude' => (float) get_option('geolocation_default_longitude'), - 'zoomLevel' => (float) get_option('geolocation_default_zoom_level'), - ]; - } - protected function _filterCssLength($length, $default) { $length = trim((string) $length); @@ -872,17 +848,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 +863,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 (!$itemLocations) { return; } @@ -909,17 +875,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 +924,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 +940,20 @@ 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, + '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..16dc2ef 100644 --- a/controllers/MapController.php +++ b/controllers/MapController.php @@ -9,30 +9,34 @@ public function init() public function browseAction() { - $table = $this->_helper->db->getTable(); - $locationTable = $this->_helper->db->getTable('Location'); + [$params, $limit, $currentPage] = $this->_getBrowseParams(); + $this->view->totalItems = $this->_helper->db->getTable()->count($params); + $this->view->params = $params; + + Zend_Registry::set('pagination', [ + 'page' => $currentPage, + 'per_page' => $limit, + 'total_results' => $this->view->totalItems, + ]); + } + + public function browseJsonAction() + { + [$params, $limit, $currentPage] = $this->_getBrowseParams(); + + $items = $this->_helper->db->getTable()->findBy($params, $limit, $currentPage); + $this->view->items = $items; + $this->view->locations = $this->_helper->db->getTable('Location')->findLocationsByItem($items); + $this->getResponse()->setHeader('Content-Type', 'application/json'); + } + + private function _getBrowseParams() + { $params = $this->getAllParams(); $params['geolocation-mapped'] = true; $limit = (int) get_option('geolocation_per_page'); $currentPage = $this->getParam('page', 1); - - // Only get pagination data for the "normal" page, only get - // item/location data for the KML output. - if ($this->_helper->contextSwitch->getCurrentContext() == 'kml') { - $items = $table->findBy($params, $limit, $currentPage); - $this->view->items = $items; - $this->view->locations = $locationTable->findLocationByItem($items); - } else { - $this->view->totalItems = $table->count($params); - $this->view->params = $params; - - $pagination = [ - 'page' => $currentPage, - 'per_page' => $limit, - 'total_results' => $this->view->totalItems, - ]; - Zend_Registry::set('pagination', $pagination); - } + return [$params, $limit, $currentPage]; } } diff --git a/models/Api/Location.php b/models/Api/Location.php index 7063412..cf6273c 100644 --- a/models/Api/Location.php +++ b/models/Api/Location.php @@ -25,8 +25,8 @@ public function getRepresentation(Omeka_Record_AbstractRecord $record) 'latitude' => $record->latitude, 'longitude' => $record->longitude, '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 +47,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 +57,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; @@ -85,15 +72,15 @@ public function setPutData(Omeka_Record_AbstractRecord $record, $data) 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 = ''; } + if (isset($data->label)) { + $record->label = $data->label; + } else { + $record->label = ''; + } } } diff --git a/models/Location.php b/models/Location.php index 32c2c7f..9477393 100644 --- a/models/Location.php +++ b/models/Location.php @@ -10,20 +10,20 @@ class Location extends Omeka_Record_AbstractRecord implements Zend_Acl_Resource_ public $latitude; public $longitude; public $zoom_level; - public $map_type; public $address; + public $label; /** * Executes before the record is saved. */ protected function beforeSave($args) { - if (is_null($this->map_type)) { - $this->map_type = ''; - } if (is_null($this->address)) { $this->address = ''; } + if (is_null($this->label)) { + $this->label = ''; + } } /** @@ -38,18 +38,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..f495a16 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,31 +31,20 @@ 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; } /** - * Add permission check to location queries. + * Join items so that public permissions on items are enforced for locations. * - * Since all locations belong to an item we can override this method to join - * the items table and add a permission check to the select object. + * Locations have no visibility of their own — a location is public only if + * its item is public. Joining the items table here means every query on + * this table automatically excludes locations for private items. * * @return Omeka_Db_Select */ diff --git a/plugin.ini b/plugin.ini index 28c2979..f824914 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="4.0" diff --git a/tests/Geolocation_IntegrationHelper.php b/tests/Geolocation_IntegrationHelper.php index 6f6b154..97b6289 100644 --- a/tests/Geolocation_IntegrationHelper.php +++ b/tests/Geolocation_IntegrationHelper.php @@ -38,8 +38,6 @@ public function _addPluginHooksAndFilters($pluginBroker, $pluginName) // Add plugin filters add_filter('admin_navigation_main', 'geolocation_admin_nav'); - add_filter('define_response_contexts', 'geolocation_kml_response_context'); - add_filter('define_action_contexts', 'geolocation_kml_action_context'); add_filter('admin_items_form_tabs', 'geolocation_item_form_tabs'); } } diff --git a/views/helpers/GeolocationMapBrowse.php b/views/helpers/GeolocationMapBrowse.php index c14f572..ccc16a5 100644 --- a/views/helpers/GeolocationMapBrowse.php +++ b/views/helpers/GeolocationMapBrowse.php @@ -18,8 +18,8 @@ public function geolocationMapBrowse($divId = 'map', $options = [], $attrs = [], if (!array_key_exists('uri', $options)) { // This should not be a link to the public side b/c then all the URLs that - // are generated inside the KML will also link to the public side. - $options['uri'] = url('geolocation/map.kml'); + // are generated inside the JSON will also link to the public side. + $options['uri'] = url('geolocation/map/browse-json'); } if (!array_key_exists('fitMarkers', $options)) { diff --git a/views/helpers/GeolocationMapOptions.php b/views/helpers/GeolocationMapOptions.php index 1c587e3..c30605e 100644 --- a/views/helpers/GeolocationMapOptions.php +++ b/views/helpers/GeolocationMapOptions.php @@ -27,6 +27,11 @@ public function geolocationMapOptions($options = []) $options['custom_map'] = json_decode((string) get_option('geolocation_custom_map'), true); + $options['strings'] = [ + 'fitAllMarkers' => __('Fit all markers'), + 'label' => __('Label'), + ]; + return js_escape($options); } diff --git a/views/helpers/GeolocationMapSingle.php b/views/helpers/GeolocationMapSingle.php index 6c61812..8058f97 100644 --- a/views/helpers/GeolocationMapSingle.php +++ b/views/helpers/GeolocationMapSingle.php @@ -2,46 +2,57 @@ 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.').'

'; + // For single-location items this sets the initial zoom correctly. + // For multi-location items fitMarkers() overrides the center after all points are added. + $center = [ + 'latitude' => $locations[0]->latitude, + 'longitude' => $locations[0]->longitude, + 'zoomLevel' => $locations[0]->zoom_level, + ]; + + $points = []; + foreach ($locations as $loc) { + $point = [ + 'latitude' => $loc->latitude, + 'longitude' => $loc->longitude, + 'zoomLevel' => $loc->zoom_level, + 'label' => $loc->label, + ]; + 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/css/geolocation-marker.css b/views/shared/css/geolocation-marker.css index c413414..1b05bb5 100644 --- a/views/shared/css/geolocation-marker.css +++ b/views/shared/css/geolocation-marker.css @@ -40,6 +40,21 @@ div#geolocation { margin-bottom:0px; } +.leaflet-control-fit-all a { + display: flex !important; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + padding: 0; +} + +.leaflet-control-fit-all a svg { + width: 14px; + height: 14px; + flex-shrink: 0; +} + img.leaflet-tile, img.leaflet-marker-icon, img.leaflet-marker-shadow { 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; ?>