diff --git a/WalkingTourPlugin.php b/WalkingTourPlugin.php index 388df5a..f53a7ea 100644 --- a/WalkingTourPlugin.php +++ b/WalkingTourPlugin.php @@ -54,7 +54,7 @@ public function hookInstall() $db = $this->_db; $tourQuery = " - CREATE TABLE IF NOT EXISTS `$db->Tour` ( + CREATE TABLE IF NOT EXISTS `$db->WalkingTour` ( `id` int( 10 ) unsigned NOT NULL auto_increment, `title` varchar( 255 ) collate utf8_unicode_ci default NULL, `description` text collate utf8_unicode_ci NOT NULL, @@ -68,7 +68,7 @@ public function hookInstall() ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci "; $tourItemQuery = " - CREATE TABLE IF NOT EXISTS `$db->TourItem` ( + CREATE TABLE IF NOT EXISTS `$db->WalkingTourItem` ( `id` INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT, `tour_id` INT( 10 ) UNSIGNED NOT NULL, `ordinal` INT NOT NULL, @@ -86,27 +86,75 @@ public function hookInstall() public function hookUninstall() { $db = $this->_db; - $db->query("DROP TABLE IF EXISTS `$db->TourItem`"); - $db->query("DROP TABLE IF EXISTS `$db->Tour`"); + $db->query("DROP TABLE IF EXISTS `$db->WalkingTourItem`"); + $db->query("DROP TABLE IF EXISTS `$db->WalkingTour`"); $this->_uninstallOptions(); } public function hookUpgrade($args) { - - $oldVersion = $args['old_version']; - $newVersion = $args['new_version']; $db = $this->_db; + $oldVersion = $args['old_version']; - if (version_compare($oldVersion, '0.1-dev', "<")) { - $sql = "ALTER TABLE `{$db->prefix}tour_items` MODIFY COLUMN `exhibit_id` INT NOT NULL;"; - $db->query($sql); - } + $oldTourTable = "{$db->prefix}tours"; + $newWalkingTourTable = "{$db->prefix}walking_tours"; + + $oldTourItemTable = "{$db->prefix}tour_items"; + $newWalkingTourItemTable = "{$db->prefix}walking_tour_items"; + + $db->query("CREATE TABLE IF NOT EXISTS `$newWalkingTourTable` ( + `id` int( 10 ) unsigned NOT NULL auto_increment, + `title` varchar( 255 ) collate utf8_unicode_ci default NULL, + `description` text collate utf8_unicode_ci NOT NULL, + `route` text collate utf8_unicode_ci, + `credits` text collate utf8_unicode_ci, + `postscript_text` text collate utf8_unicode_ci, + `featured` tinyint( 1 ) default '0', + `public` tinyint( 1 ) default '0', + `color` text collate utf8_unicode_ci, + PRIMARY KEY( `id` ) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;" + ); + + $db->query("CREATE TABLE IF NOT EXISTS `$newWalkingTourItemTable` ( + `id` INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT, + `tour_id` INT( 10 ) UNSIGNED NOT NULL, + `ordinal` INT NOT NULL, + `item_id` INT( 10 ) UNSIGNED NOT NULL, + `exhibit_id` INT NOT NULL, + PRIMARY KEY( `id` ), + KEY `tour` ( `tour_id` ) + ) ENGINE=InnoDB" + ); + + if (version_compare($oldVersion, '2.0.0', '<')) { + $checkOldTourTable = $db->query("SHOW TABLES LIKE '$oldTourTable'")->fetchAll(); + + if (!empty($checkOldTourTable)) { + $migrateTourSql = "INSERT INTO `$newWalkingTourTable` ( + id, title, description, route, credits, postscript_text, featured, public, color) + SELECT + id, title, description, route, credits, postscript_text, featured, public, color + FROM `$oldTourTable` + WHERE id NOT IN (SELECT id FROM `$newWalkingTourTable`)"; + $db->query($migrateTourSql); + + // $db->query("DROP TABLE `$oldTourTable` text;"); + } - if (version_compare($oldVersion, '1.0.0', '<=')) { - $sql = "ALTER TABLE `{$db->prefix}tours` ADD COLUMN `route` TEXT;"; - $db->query($sql);} + $checkOldTourItemTable = $db->query("SHOW TABLES LIKE '$oldTourItemTable'")->fetchAll(); + + if (!empty($checkOldTourItemTable)) { + $migrateItemSql = "INSERT INTO `$newWalkingTourItemTable` ( + id, tour_id, ordinal, item_id, exhibit_id) + SELECT id, tour_id, ordinal, item_id, exhibit_id + FROM `$oldTourItemTable` + WHERE id NOT IN (SELECT id FROM `$newWalkingTourItemTable`)"; + $db->query($migrateItemSql); + // $db->query("DROP TABLE `$oldTourItemTable` text;"); + } } + } public function hookDefineAcl($args) { @@ -191,8 +239,8 @@ public function hookAdminDashboard() // Get the database. $db = get_db(); - // Get the Tour table. - $table = $db->getTable('Tour'); + // Get the Walking Tour table. + $table = $db->getTable('WalkingTour'); // Build the select query. $select = $table->getSelect(); @@ -205,15 +253,15 @@ public function hookAdminDashboard() for ($i = 0; $i <= 5; $i++) { if (array_key_exists($i, $results) && is_object($results[$i])) { - $tourItems .= '

' - . $results[$i]->title . '

Edit

'; + $tourItems .= '

' + . $results[$i]->title . '

Edit

'; } } $html .= '
'; $html .= '

' . __('Recent Tours') . '

'; $html .= '' . $tourItems . ''; - $html .= '

' . __('Add a new tour') . '

'; + $html .= '

' . __('Add a new tour') . '

'; $html .= '
'; echo $html; @@ -233,23 +281,25 @@ public function hookAdminHead() public function filterPublicNavigationMain($nav) { - $nav[] = array('label' => 'Map', 'uri' => url('map')); + $nav[] = array( + 'label' => 'Map', + 'uri' => url('map') + ); return $nav; } public function filterSearchRecordTypes($recordTypes) { - $recordTypes['Tour'] = __('Tour'); + $recordTypes['WalkingTour'] = __('Walking Tour'); return $recordTypes; } public function filterAdminNavigationMain($nav) { - $nav['Tours'] = array( + $nav['WalkingTours'] = array( 'label' => __('Walking Tours'), - 'action' => 'browse', - 'controller' => 'tours' + 'uri' => url('walking-tours/browse') ); return $nav; } diff --git a/controllers/IndexController.php b/controllers/IndexController.php index 5672dbd..c5b307c 100755 --- a/controllers/IndexController.php +++ b/controllers/IndexController.php @@ -21,8 +21,8 @@ public function publicTours() { // Get the database. $db = get_db(); - // Get the Tour table. - $tour_table = $db->getTable('Tour'); + // Get the Walking Tour table. + $tour_table = $db->getTable('WalkingTour'); // Build the select query. $select = $tour_table->getSelect(); // Fetch some items with our select. @@ -46,7 +46,7 @@ public function saveRouteAction() { $tourId = $this->getRequest()->getPost('tour_id'); $route = $this->getRequest()->getPost('route'); $db = get_db(); - $tourTable = $db->getTable('Tour'); + $tourTable = $db->getTable('WalkingTour'); $tour = $tourTable->find($tourId); if ($tour) { $tour->route = $route; @@ -57,6 +57,73 @@ public function saveRouteAction() { } } + public function previewAction() + { + if (!$this->_request->isXmlHttpRequest()) { + throw new Omeka_Controller_Exception_403; + } + + $tourId = (int) $this->_request->getPost('tour_id'); + $itemIds = $this->_request->getPost('item_ids', array()); + + if (is_string($itemIds)) { + $itemIds = array_filter(array_map('trim', explode(',', $itemIds))); + } + + $itemIds = array_values(array_filter(array_map('intval', (array) $itemIds))); + + $db = $this->_helper->db->getDb(); + $tourTable = $db->getTable('WalkingTour'); + $tour = $tourTable->find($tourId); + + if (!$tour) { + $this->_helper->json(array('error' => 'Tour not found.')); + return; + } + + $locations = array(); + if (!empty($itemIds)) { + $prefix = $db->prefix; + $placeholders = implode(',', array_fill(0, count($itemIds), '?')); + $sql = "SELECT item_id, latitude, longitude FROM {$prefix}locations WHERE item_id IN ($placeholders)"; + $rows = $db->fetchAll($sql, $itemIds); + foreach ($rows as $row) { + $locations[(int) $row['item_id']] = $row; + } + } + + $features = array(); + foreach ($itemIds as $itemId) { + if (!isset($locations[$itemId])) { + continue; + } + + $features[] = array( + 'type' => 'Feature', + 'geometry' => array( + 'type' => 'Point', + 'coordinates' => array((float) $locations[$itemId]['longitude'], (float) $locations[$itemId]['latitude']), + ), + 'properties' => array( + 'id' => $itemId, + 'marker-color' => $tour->color, + ), + ); + } + + $this->_helper->json(array( + 'Data' => array( + 'type' => 'FeatureCollection', + 'features' => $features, + ), + 'Color' => $tour->color, + 'Tour Name' => $tour->title, + 'Description' => $tour->description, + 'Credits' => $tour->credits, + 'Route' => $tour->route, + )); + } + /** * Display the map. */ @@ -74,12 +141,12 @@ public function indexAction() ->appendFile(src('modernizr.custom.63332', 'javascripts', 'js')) ->appendFile(src('Polyline.encoded', 'javascripts', 'js')) ->appendFile('//cdn.jsdelivr.net/npm/@allmaps/leaflet/dist/bundled/allmaps-leaflet-1.9.umd.js') - ->appendFile(src('walking-tour', 'javascripts', 'js')); + ->appendFile(src('walking-tour-public', 'javascripts', 'js')); $this->view->headLink() ->appendStylesheet('//code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css', 'all') // ->appendStylesheet('//cdn.leafletjs.com/leaflet-0.7/leaflet.css', 'all') // ->appendStylesheet('//cdn.leafletjs.com/leaflet-0.7/leaflet.ie.css', 'all', 'lte IE 8') - ->appendStylesheet(src('walking-tour', 'css', 'css')); + ->appendStylesheet(src('walking-tour-public', 'css', 'css')); // ->appendStylesheet(src('/../../../themes/mall-theme', 'css', 'css')); } @@ -117,20 +184,36 @@ public function queryAction() $joins = array("$db->Item AS items ON items.id = locations.item_id"); $wheres = array("items.public = 1"); $prefix = $db->prefix; + $previewTourId = (int) $this->_request->getParam('tour_id'); + $previewItemIds = array(); + $postedItemIds = trim((string) $this->_request->getParam('item_ids')); + + if ($postedItemIds !== '') { + foreach (explode(',', $postedItemIds) as $postedItemId) { + $postedItemId = (int) trim($postedItemId); + if ($postedItemId) { + $previewItemIds[] = $postedItemId; + } + } + } // Filter public tours' items $request_tour_id = $this->publicTours(); $colorArray = array(); - $tourItemTable = $db->getTable('TourItem'); + $tourItemTable = $db->getTable('WalkingTourItem'); $tourItemsIDs = array(); $returnArray = array(); foreach ($request_tour_id['id'] as $tour_id => $tour_title) { + if ($previewTourId && $tour_id == $previewTourId) { + $tourItemsIDs[$tour_id] = $previewItemIds; + continue; + } + if ($tour_id != 0) { - $tourItemsDat = $tourItemTable->fetchObjects("SELECT item_id FROM " . $prefix . "tour_items - WHERE tour_id = $tour_id"); + $tourItemsDat = $tourItemTable->fetchObjects("SELECT item_id FROM " . $prefix . "walking_tour_items WHERE tour_id = $tour_id"); } else { - $tourItemsDat = $tourItemTable->fetchObjects("SELECT item_id FROM " . $prefix . "tour_items"); + $tourItemsDat = $tourItemTable->fetchObjects("SELECT item_id FROM " . $prefix . "walking_tour_items"); } $tourItemsIDs[$tour_id] = array(); foreach ($tourItemsDat as $dat) { @@ -140,6 +223,20 @@ public function queryAction() foreach ($tourItemsIDs as $tour_id => $item_array) { + $returnArray[$tour_id]["Data"] = array('type' => 'FeatureCollection', 'features' => array()); + $returnArray[$tour_id]["Color"] = $request_tour_id['color'][$tour_id]; + $returnArray[$tour_id]["Tour Name"] = $request_tour_id['id'][$tour_id]; + $returnArray[$tour_id]["Description"] = $request_tour_id['description'][$tour_id]; + $returnArray[$tour_id]["Credits"] = $request_tour_id['credits'][$tour_id]; + + $tourTable = $db->getTable('WalkingTour'); + $tour = $tourTable->find($tour_id); + $returnArray[$tour_id]["Route"] = $tour ? $tour->route : null; + + if (empty($item_array)) { + continue; + } + $tourItemsID = implode(", ", $item_array); $wheres = array("items.public = 1"); $wheres[] = $db->quoteInto("items.id IN ($tourItemsID)", Zend_Db::INT_TYPE); @@ -154,6 +251,7 @@ public function queryAction() } $sql .= "\nGROUP BY items.id"; + // TODO ERROR $dbItems = $db->query($sql)->fetchAll(); $orderedItems = array(); @@ -166,7 +264,6 @@ public function queryAction() } } // Build geoJSON: http://www.geojson.org/geojson-spec.html - $returnArray[$tour_id]["Data"] = array('type' => 'FeatureCollection', 'features' => array()); foreach ($orderedItems as $row) { $returnArray[$tour_id]["Data"]['features'][] = array( 'type' => 'Feature', @@ -180,14 +277,6 @@ public function queryAction() ), ); } - $returnArray[$tour_id]["Color"] = $request_tour_id['color'][$tour_id]; - $returnArray[$tour_id]["Tour Name"] = $request_tour_id['id'][$tour_id]; - $returnArray[$tour_id]["Description"] = $request_tour_id['description'][$tour_id]; - $returnArray[$tour_id]["Credits"] = $request_tour_id['credits'][$tour_id]; - - $tourTable = $db->getTable('Tour'); - $tour = $tourTable->find($tour_id); - $returnArray[$tour_id]["Route"] = $tour ? $tour->route : null; } $this->_helper->json($returnArray); @@ -202,16 +291,24 @@ public function getItemAction() if (!$this->_request->isXmlHttpRequest()) { throw new Omeka_Controller_Exception_403; } - $item_id = $this->_request->getParam('id'); - $tour_id = $this->_request->getParam('tour'); + $item_id = (int) $this->_request->getParam('id'); + $tour_id = (int) $this->_request->getParam('tour_id', $this->_request->getParam('tour')); + + if (!$item_id || !$tour_id) { + $this->_helper->json(array('error' => 'Missing tour or item id.')); + return; + } $db = $this->_helper->db->getDb(); - $tourItemTable = $db->getTable('TourItem'); + $tourItemTable = $db->getTable('WalkingTourItem'); $prefix = $db->prefix; + $tourItem = $tourItemTable->fetchObjects("SELECT * FROM " . $prefix . "walking_tour_items WHERE tour_id = $tour_id AND item_id = $item_id"); - $tourItem = $tourItemTable->fetchObjects("SELECT * FROM " . $prefix . "tour_items - WHERE tour_id = $tour_id AND item_id = $item_id"); + if (empty($tourItem)) { + $this->_helper->json(array('error' => 'Tour item not found.')); + return; + } $exhibit_id = $tourItem[0]["exhibit_id"]; diff --git a/controllers/ToursController.php b/controllers/ToursController.php index 696c735..1ec1924 100644 --- a/controllers/ToursController.php +++ b/controllers/ToursController.php @@ -1,12 +1,12 @@ _helper->db->setDefaultModelName( 'Tour' ); + $this->_helper->db->setDefaultModelName( 'WalkingTour' ); } } diff --git a/helpers/TourFunctions.php b/helpers/TourFunctions.php index d51ef56..87e552f 100644 --- a/helpers/TourFunctions.php +++ b/helpers/TourFunctions.php @@ -34,39 +34,39 @@ function availableExhibit() { } } -function has_tours() +function has_walking_tours() { return( total_tours() > 0 ); } -function has_tours_for_loop() +function has_walking_tours_for_loop() { $view = get_view(); - return $view->tours && count( $view->tours ); + return $view->walking_tours && count( $view->walking_tours ); } -function tour( $fieldName, $options=array(), $tour=null ) +function tour( $fieldName, $options=array(), $walking_tour=null ) { - if( ! $tour ) { - $tour = get_current_tour(); + if( ! $walking_tour ) { + $walking_tour = get_current_tour(); } switch( strtolower( $fieldName ) ) { case 'id': - $text = $tour->id; + $text = $walking_tour->id; break; case 'title': - $text = $tour->title; + $text = $walking_tour->title; break; case 'description': - $text = $tour->description; + $text = $walking_tour->description; break; case 'credits': - $text = $tour->credits; + $text = $walking_tour->credits; break; case 'postscript_text': - $text = $tour->postscript_text; + $text = $walking_tour->postscript_text; break; default: throw new Exception( "\"$fieldName\" does not exist for tours!" ); @@ -92,7 +92,7 @@ function tour( $fieldName, $options=array(), $tour=null ) function get_current_tour() { - return get_view()->tour; + return get_view()->walking_tour; } function link_to_tour( @@ -116,7 +116,7 @@ function link_to_tour( function total_tours() { $view = get_view(); - return count( $view->tours ); + return count( $view->walking_tours ); } function nls2p($str) { diff --git a/models/Tour.php b/models/WalkingTour.php similarity index 88% rename from models/Tour.php rename to models/WalkingTour.php index cac1f09..5a6d4e3 100644 --- a/models/Tour.php +++ b/models/WalkingTour.php @@ -1,12 +1,12 @@ 'getItems','Image' => 'getImage' ); + protected $_related = array( + 'Items' => 'getItems', + 'Image' => 'getImage' + ); public function _initializeMixins() { @@ -32,7 +35,7 @@ public function getItems() public function removeAllItems( ) { $db = get_db(); - $tiTable = $db->getTable( 'TourItem' ); + $tiTable = $db->getTable( 'WalkingTourItem' ); $select = $tiTable->getSelect(); $select->where( 'tour_id = ?', array( $this->id ) ); @@ -54,7 +57,7 @@ public function addItem( $item_id, $exhibit_id = 0 , $ordinal = null ) # Get the next ordinal $db = get_db(); - $tiTable = $db->getTable( 'TourItem' ); + $tiTable = $db->getTable( 'WalkingTourItem' ); $select = $tiTable->getSelectForCount(); $select->where( 'tour_id = ?', array( $this->id ) ); if($ordinal === null) { @@ -62,7 +65,7 @@ public function addItem( $item_id, $exhibit_id = 0 , $ordinal = null ) } # Create, assign, and save the new tour item connection - $tourItem = new TourItem; + $tourItem = new WalkingTourItem; $tourItem->tour_id = $this->id; $tourItem->item_id = $item_id; $tourItem->ordinal = $ordinal; @@ -96,6 +99,7 @@ protected function afterSave($args) { $post=$args['post']; if($post && isset($post['tour_item_ids']) && !$args['insert']){ + // if($post && isset($post['tour_item_ids'])){ $this->removeAllItems(); diff --git a/models/TourItem.php b/models/WalkingTourItem.php similarity index 77% rename from models/TourItem.php rename to models/WalkingTourItem.php index 0553116..c3d3186 100644 --- a/models/TourItem.php +++ b/models/WalkingTourItem.php @@ -4,7 +4,7 @@ * Tour Item. * @package: Omeka */ -class TourItem extends Omeka_Record_AbstractRecord +class WalkingTourItem extends Omeka_Record_AbstractRecord { public $tour_id; public $item_id; @@ -12,18 +12,18 @@ class TourItem extends Omeka_Record_AbstractRecord public $exhibit_id = -1; protected $_related = array( - 'Tour' => 'getTour', + 'WalkingTour' => 'getWalkingTour', 'Item' => 'getItem', ); protected function getItem() { - return $this->getTable( 'Item' )->find( $this->item_id ); + return $this->getTable( 'WalkingTourItem' )->find( $this->item_id ); } - protected function getTour() + protected function getWalkingTour() { - return $this->getTable( 'Tour' )->find( $this->tour_id ); + return $this->getTable( 'WalkingTour' )->find( $this->tour_id ); } protected function _validate() diff --git a/models/TourTable.php b/models/WalkingTourTable.php similarity index 81% rename from models/TourTable.php rename to models/WalkingTourTable.php index 1f5de47..20a312b 100644 --- a/models/TourTable.php +++ b/models/WalkingTourTable.php @@ -1,6 +1,6 @@ getTable( 'Item' ); $select = $itemTable->getSelect(); $iAlias = $itemTable->getTableAlias(); - $select->joinInner( array( 'ti' => $db->TourItem ), + $select->joinInner( array( 'ti' => $db->WalkingTourItem ), "ti.item_id = $iAlias.id", array() ); $select->where( 'ti.tour_id = ?', array( $tour_id ) ); $select->order( 'ti.ordinal ASC' ); $items = $itemTable->fetchObjects( "SELECT i.*, ti.ordinal, ti.exhibit_id - FROM ".$prefix."items i LEFT JOIN ".$prefix."tour_items ti + FROM ".$prefix."items i LEFT JOIN ".$prefix."walking_tour_items ti ON i.id = ti.item_id WHERE ti.tour_id = ? ORDER BY ti.ordinal ASC", @@ -31,13 +31,13 @@ public function findImageByTourId( $tour_id ) { $itemTable = $this->getTable( 'File' ); $select = $itemTable->getSelect(); $iAlias = $itemTable->getTableAlias(); - $select->joinInner( array( 'ti' => $db->TourItem ), + $select->joinInner( array( 'ti' => $db->WalkingTourItem ), "ti.item_id = $iAlias.id", array() ); $select->where( 'ti.tour_id = ?', array( $tour_id ) ); $select->order( 'ti.ordinal ASC' ); $items = $itemTable->fetchObjects( "SELECT f.*, ti.ordinal - FROM ".$prefix."files f LEFT JOIN ".$prefix."tour_items ti + FROM ".$prefix."files f LEFT JOIN ".$prefix."walking_tour_items ti ON i.id = ti.item_id WHERE ti.tour_id = ? ORDER BY ti.ordinal ASC", @@ -49,7 +49,7 @@ public function findImageByTourId( $tour_id ) { public function getSelect() { - $select = parent::getSelect()->order('tours.id'); + $select = parent::getSelect()->order('walking_tours.id'); $permissions = new Omeka_Db_Select_PublicPermissions( 'WalkingTourBuilder_Tours' ); $permissions->apply( $select, 'tours', null ); diff --git a/plugin.ini b/plugin.ini index 6de57f6..5ea789b 100755 --- a/plugin.ini +++ b/plugin.ini @@ -5,7 +5,7 @@ description="Adds the ability to create and display walking tours on a map" license="GPLv3" link="" support_link="https://github.com/DigitalCarleton/WalkingTour" -version="1.0.1" +version="1.0.3" omeka_minimum_version="3.0" omeka_target_version="3.1.2" tags="map, tour" diff --git a/routes.ini b/routes.ini index 0fbe13a..160efc9 100644 --- a/routes.ini +++ b/routes.ini @@ -1,22 +1,22 @@ -[routes] -tours.route = "tours/:action" +[routes] ; for admin site +tours.route = "walking-tours/:action" tours.defaults.module = walking-tour tours.defaults.controller = tours tours.defaults.action = "browse" -tourAction.route = "tours/:action/:id" +tourAction.route = "walking-tours/:action/:id" tourAction.defaults.module = walking-tour tourAction.defaults.controller = tours tourAction.defaults.action = "show" tourAction.reqs.id = "\d+" -tourItemAction.route = "tours/edit/:id/:action/:item" +tourItemAction.route = "walking-tours/edit/:id/:action/:item" tourItemAction.defaults.module = walking-tour tourItemAction.defaults.controller = tours tourItemAction.reqs.id = "\d+" tourItemAction.reqs.item = "\d+" -oldTour.route = "tour-builder/tours/:action/:id" +oldTour.route = "tour-builder/walking-tours/:action/:id" oldTour.defaults.module = walking-tour oldTour.defaults.controller = tours oldTour.defaults.action = "browse" diff --git a/views/admin/css/tour-1.7.css b/views/admin/css/tour-1.7.css index a357198..9f0ce95 100644 --- a/views/admin/css/tour-1.7.css +++ b/views/admin/css/tour-1.7.css @@ -1,111 +1,194 @@ -#admin-tour-image img{max-width: 100%; } -#tour.edit #admin-tour-image img{margin: 1em 0;} -#tour.show #admin-tour-image img{margin: 0 0;} -#tour.edit input[type="text"]{margin-bottom:0; width:100%;} -#tour.edit input#image{background:#eaeaea;padding:.5em 0 .5em .5em;width: 100%;border: 1px solid #D8D8D8;} -#tour.edit p.explanation{padding:.25em 0;} -#tour.edit .file-helper{display: inline-block;font-style: italic;padding: .5em 0 0;} - -#tour.browse .fa{ - font-family:"FontAwesome"; +#admin-tour-image img { + max-width: 100%; +} + +#tour.edit #admin-tour-image img { + margin: 1em 0; +} + +#tour.show #admin-tour-image img { + margin: 0 0; +} + +#tour.edit input[type="text"] { + margin-bottom: 0; + width: 100%; +} + +#tour.edit input#image { + background: #eaeaea; + padding: .5em 0 .5em .5em; + width: 100%; + border: 1px solid #D8D8D8; +} + +#tour.edit p.explanation { + padding: .25em 0; +} + +#tour.edit .file-helper { + display: inline-block; + font-style: italic; + padding: .5em 0 0; +} + +#tour-form { + display: flex; + flex-wrap: wrap; +} + +#tour-form #save.panel, +#tour-form #public-featured.panel */ { + background: #fff; + border: 1px solid #d8d8d8; + margin-bottom: 1rem; +} + +#tour-form #public-featured .checkbox { + margin-bottom: 0.75rem; +} + +#tour-form .tour-form-sidebar { + background: #fff; + box-sizing: border-box; + padding: 0 0 1rem 0; +} + +#tour.browse .fa { + font-family: "FontAwesome"; font-style: normal; font-weight: lighter; - font-size:1.35em; - line-height:inherit; + font-size: 1.35em; + line-height: inherit; vertical-align: middle; padding-left: .25em; - text-shadow:0 0 2px #fff; + text-shadow: 0 0 2px #fff; } -#tour.browse i.fa.fa-camera:after{ + +#tour.browse i.fa.fa-camera:after { content: "\f030"; - color: #ccc; + color: #ccc; float: right; display: inline-block; } -.hidden{ - display:none; + +.hidden { + display: none; visibility: hidden; } -.admin-tour-browse-meta{ + +.admin-tour-browse-meta { margin: .25em 0; - color:#777; + color: #777; } -#tourbuilder-item-list{ + +#tourbuilder-item-list { padding-left: 0; } -#tour-items-picker{ - min-height: 600px; + +#tour-items-picker { + min-height: 600px; min-height: 90vh; } -#tour-item-search, #tour-item-exhibit-search{ - padding: 10px; - width: 100%; - box-sizing: border-box; - margin: 0 !important; - border: 0px solid transparent; + +#tour-item-search, +#tour-item-exhibit-search { + padding: 10px; + width: 100%; + box-sizing: border-box; + margin: 0 !important; + border: 0px solid transparent; } -.input-container{ - background:linear-gradient(to bottom, #22546b, #102d3b); + +.input-container { + background: linear-gradient(to bottom, #22546b, #102d3b); padding: 1em !important; box-sizing: border-box; } -.exhibit-input-container{ + +.exhibit-input-container { margin-left: 3rem; border: 1px solid #D6D5C2; box-sizing: border-box; } -.exhibit{ + +.exhibit { cursor: pointer; - margin: 0 1em; - flex: 0 0 80px; + margin: 0 1em; + flex: 0 0 80px; } -.remove{ + +.remove { cursor: pointer; flex: 0 0; } -.tour-item-ui{ + +.tour-item-ui { display: flex; - padding-bottom: 0.5em; + padding-bottom: 0.5em; } -.tour-item-header{ + +.tour-item-header { display: flex; } -.tour-item-title p{ + +.tour-item-title p { margin: 0; } + .exhibit-name { margin-top: 0.2em; - color: #666; - font-style: italic; + color: #666; + font-style: italic; } -.ui-menu-item{ +.ui-menu-item { padding-left: 5px; } -#sortable { list-style-type: none; margin: 2em 0 0; padding: 0; width: 100%;} +#sortable { + list-style-type: none; + margin: 2em 0 0; + padding: 0; + width: 100%; +} + #sortable li { display: flex; padding: 0.5em 1em; - margin:0; - cursor: move; cursor: grab; cursor: -moz-grab; cursor: -webkit-grab; + margin: 0; + cursor: move; + cursor: grab; + cursor: -moz-grab; + cursor: -webkit-grab; flex-direction: column; } -#sortable li span:first-child {flex-grow: 1;} -.ui-state-highlight { line-height: 1.2em; color:#fff;background: linear-gradient(to bottom, #eaf2e1, #8eb763);text-shadow: -1px -1px 1px rgba(0,0,0,.5);} -svg#drag{ +#sortable li span:first-child { + flex-grow: 1; +} + +.ui-state-highlight { + line-height: 1.2em; + color: #fff; + background: linear-gradient(to bottom, #eaf2e1, #8eb763); + text-shadow: -1px -1px 1px rgba(0, 0, 0, .5); +} + +svg#drag { margin-right: 1em; vertical-align: middle; margin-top: 0.4em; } -.ui-state-highlight svg#drag{ - fill-opacity:.55; +.ui-state-highlight svg#drag { + fill-opacity: .55; } -.ui-state-highlight svg#drag #top path{ - fill:#fff; + +.ui-state-highlight svg#drag #top path { + fill: #fff; } -.ui-state-highlight svg#drag #shadow path{ - fill:#eaf2e1; + +.ui-state-highlight svg#drag #shadow path { + fill: #eaf2e1; } \ No newline at end of file diff --git a/views/admin/javascripts/walking-tour.js b/views/admin/javascripts/walking-tour-admin.js similarity index 79% rename from views/admin/javascripts/walking-tour.js rename to views/admin/javascripts/walking-tour-admin.js index aa72e98..09ef88c 100755 --- a/views/admin/javascripts/walking-tour.js +++ b/views/admin/javascripts/walking-tour-admin.js @@ -1,4 +1,8 @@ jQuery(document).ready(function ($) { + var currentTour = (typeof window.currentTour !== 'undefined' && window.currentTour !== null && window.currentTour !== '') + ? window.currentTour.toString() + : null; + var markers; var map; var markerData; @@ -25,7 +29,7 @@ jQuery(document).ready(function ($) { var IS_AUTO_FIT; var historicMapLayer; - + var jqXhr; var locationMarker; var allItems = {}; @@ -34,7 +38,7 @@ jQuery(document).ready(function ($) { var baseUrl = window.location.origin; var urlpaths = window.location.pathname.split("/"); if (urlpaths[1] != "admin") { baseUrl += "/" + urlpaths[1] }; - + /* * JQuery Setup */ @@ -242,12 +246,165 @@ jQuery(document).ready(function ($) { $('#info-panel-container').fadeToggle(200, 'linear'); }); + async function refreshCurrentTourPreview() { + if (!currentTour) { + return; + } + + var itemIds = ($('#tour_item_ids').val() || '') + .split(',') + .map(function (value) { + return parseInt(value, 10); + }) + .filter(function (value) { + return !isNaN(value) && value > 0; + }); + + if (!itemIds.length) { + markerData[currentTour] = markerData[currentTour] || {}; + markerData[currentTour].Data = { type: 'FeatureCollection', features: [] }; + markerData[currentTour].geoJson = L.geoJson([]); + markerData[currentTour].walkingPath = L.polyline([], { + color: markerData[currentTour].Color || '#000000', + weight: 3, + opacity: 1, + smoothFactor: 1 + }); + doFilters(); + return; + } + + try { + var response = await $.ajax({ + url: baseUrl + '/walking-tour/index/preview', + method: 'POST', + dataType: 'json', + data: { + tour_id: currentTour, + item_ids: itemIds.join(',') + } + }); + + markerData[currentTour] = response; + + var itemIDList = []; + var features = (response.Data && response.Data.features) ? response.Data.features : []; + features.forEach(function (feature) { + itemIDList.push(feature.properties.id); + }); + + var previewMarkerFontHtmlStyles = ` + transform: rotate(-45deg); + color:white; + text-align: center; + padding: 0.2rem 0 0.18rem 0; + font-size: 15px; + `; + + var numMarker = 1; + var geoJsonLayer = L.geoJson(features, { + pointToLayer: function (feature, latlng) { + var numberIcon = L.divIcon({ + className: "my-custom-pin", + iconSize: [25, 41], + iconAnchor: [12, 40], + popupAnchor: [0, -5], + html: `

${numMarker}

` + }); + numMarker++; + return L.marker(latlng, { icon: numberIcon }); + } + }); + + markerData[currentTour].Data = response.Data; + markerData[currentTour].geoJson = geoJsonLayer; + + var pointList = []; + features.forEach(function (feature) { + if (feature.geometry && feature.geometry.coordinates) { + pointList.push(new L.LatLng(feature.geometry.coordinates[1], feature.geometry.coordinates[0])); + } + }); + + async function getRoute(points) { + var pointsParam = []; + points.forEach(function (ele) { + pointsParam.push([ele.lng, ele.lat]); + }); + if (pointsParam.length < 2) { + return null; + } + const response = await fetch('https://api.openrouteservice.org/v2/directions/foot-walking/geojson', { + method: 'POST', + headers: { + 'Accept': 'application/json, application/geo+json, application/gpx+xml, img/png; charset=utf-8', + 'Content-Type': 'application/json', + 'Authorization': '5b3ce3597851110001cf62489dde4c6690bc423bb86bd99921c5da77' + }, + body: `{"coordinates": ${JSON.stringify(pointsParam)}}` + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('OpenRouteService API error:', response.status, response.statusText, errorText); + return null; + } + + return response.json(); + } + + if (pointList.length >= 2) { + const routeData = await getRoute(pointList); + if (routeData && routeData.features && routeData.features.length) { + var path = routeData.features[0].geometry.coordinates; + path = path.map(function (coord) { + return [coord[1], coord[0]]; + }); + markerData[currentTour].walkingPath = L.polyline(path, { + color: response.Color || '#000000', + weight: 3, + opacity: 1, + smoothFactor: 1 + }); + } else { + markerData[currentTour].walkingPath = L.polyline([], { + color: response.Color || '#000000', + weight: 3, + opacity: 1, + smoothFactor: 1 + }); + } + } else { + markerData[currentTour].walkingPath = L.polyline([], { + color: response.Color || '#000000', + weight: 3, + opacity: 1, + smoothFactor: 1 + }); + } + + createCustomCSS(); + doFilters(); + } catch (error) { + console.error('Failed to refresh tour preview:', error); + } + } + + $(document).on('tourItemsUpdated', function () { + refreshCurrentTourPreview(); + }); + $(document).on('tourOrderChanged', async function (event, updatedOrder) { async function getRoute(points) { const coordinates = points.map(point => [point[1], point[0]]); // Convert to [lng, lat] format const url = "https://api.openrouteservice.org/v2/directions/foot-walking/geojson"; - + + if (coordinates.length < 2) { + console.error("At least two coordinates are required to request a route.", coordinates); + return null; + } + try { const response = await fetch(url, { method: "POST", @@ -259,12 +416,13 @@ jQuery(document).ready(function ($) { coordinates: coordinates }) }); - + if (!response.ok) { - console.error("OpenRouteService API error:", response.statusText); + const errorText = await response.text(); + console.error("OpenRouteService API error:", response.status, response.statusText, errorText); return null; } - + const data = await response.json(); const route = data.features[0].geometry.coordinates.map(coord => [coord[1], coord[0]]); // Convert back to [lat, lng] saveRoute(data); @@ -275,11 +433,11 @@ jQuery(document).ready(function ($) { return null; } } - + if (markers) { map.removeLayer(markers); } - + // Map the updated order to coordinates const reorderedPoints = updatedOrder.map((id, index) => { const feature = markerData[currentTour].Data.features.find(f => f.properties.id === id); @@ -289,15 +447,15 @@ jQuery(document).ready(function ($) { } return feature ? [feature.geometry.coordinates[1], feature.geometry.coordinates[0]] : null; }).filter(point => point !== null); - + if (reorderedPoints.length < 2) { console.error("At least two points are required to calculate a route."); return; } - + // Query OpenRouteService for the new route const route = await getRoute(reorderedPoints); - + const reorderedPath = L.polyline(route, { color: markerData[currentTour].Color || '#000000', weight: 3, @@ -321,7 +479,7 @@ jQuery(document).ready(function ($) { const marker = L.marker(latlng, { icon: numberIcon }); markers.addLayer(marker); }); - + markers.addLayer(reorderedPath); map.addLayer(markers); }); @@ -330,7 +488,7 @@ jQuery(document).ready(function ($) { * Query backend */ - + jqXhr = $.post(baseUrl + '/walking-tour/index/map-config', function (response) { mapSetUp(response); doQuery(); @@ -339,7 +497,7 @@ jQuery(document).ready(function ($) { // Retain previous form state, if needed. retainFormState(); - function mapLocateCenter(map){ + function mapLocateCenter(map) { map.flyTo(MAP_CENTER, MAP_ZOOM); } @@ -446,10 +604,10 @@ jQuery(document).ready(function ($) { tour_id: currentTour, route: JSON.stringify(route) }, - success: function(response) { + success: function (response) { console.log('Route saved to database'); }, - error: function(xhr, status, error) { + error: function (xhr, status, error) { console.error('Failed to save route:', error); } }) @@ -475,6 +633,10 @@ jQuery(document).ready(function ($) { points.forEach(ele => { pointsParam.push([ele.lng, ele.lat]) }) + if (pointsParam.length < 2) { + console.error("At least two coordinates are required to request a route.", pointsParam); + return { features: [] }; + } url = "https://api.openrouteservice.org/v2/directions/foot-walking/geojson" const response = await fetch(url, { method: "POST", // *GET, POST, PUT, DELETE, etc. @@ -486,6 +648,11 @@ jQuery(document).ready(function ($) { }, body: `{"coordinates": ${JSON.stringify(pointsParam)}}`, // body data type must match "Content-Type" header }) + if (!response.ok) { + const errorText = await response.text(); + console.error("OpenRouteService API error:", response.status, response.statusText, errorText); + return { features: [] }; + } return response.json(); } @@ -495,6 +662,13 @@ jQuery(document).ready(function ($) { var markerBounds = L.latLngBounds(); jqXhr = $.post(baseUrl + '/walking-tour/index/query', function (response) { markerData = response; + console.log(markerData); + if (!currentTour) { + var tourIds = Object.keys(markerData || {}); + if (tourIds.length) { + currentTour = tourIds[0]; + } + } dataArray = Object.entries(markerData) for (const tour in markerData) { itemArray = itemArray.concat(markerData[tour]['Data']['features']) @@ -528,7 +702,7 @@ jQuery(document).ready(function ($) { onEachFeature: function (feature, layer) { layer.on('click', function (e) { // center click location - map.flyTo(e.latlng,MAP_ZOOM + MAP_MAX_ZOOM_STOP); + map.flyTo(e.latlng, MAP_ZOOM + MAP_MAX_ZOOM_STOP); // Close the filtering var filterButton = $('filter-button'); filterButton.removeClass('on'). @@ -539,7 +713,7 @@ jQuery(document).ready(function ($) { var marker = this; //response = allItems[`${tourId}:${feature.properties.id}`] //if (response == undefined) { - // $.post(baseUrl + '/walking-tour//index/get-item', { id: feature.properties.id, tour: tourId }, function (response) { + // $.post(baseUrl + '/walking-tour/index/get-item', { id: feature.properties.id, tour: tourId }, function (response) { // allItems[`${tourId}:${feature.properties.id}`] = response; // featureOnclickAction(response, layer, marker, itemIDList, value, tourId); // }) @@ -563,36 +737,45 @@ jQuery(document).ready(function ($) { pointList[i] = point; } getOverallPath(pointList, key).then((data) => { - saveRoute(data); - - var path = data["features"][0]["geometry"]["coordinates"]; - path = orderCoords(path); - for (var p of path) { - walkingPath.push(p); + if (data && data.features && data.features.length) { + saveRoute(data); + + var path = data.features[0].geometry.coordinates; + path = orderCoords(path); + for (var p of path) { + walkingPath.push(p); + } + var tourPolyline = new L.Polyline(walkingPath, { + color: value["Color"], + weight: 3, + opacity: 1, + smoothFactor: 1 + }); + + markerData[tourId].walkingPath = tourPolyline; + } else { + console.error('OpenRouteService returned no features for tour', tourId, data); + markerData[tourId].walkingPath = new L.Polyline([], { color: value["Color"] || '#000000', weight: 3 }); } - var tourPolyline = new L.Polyline(walkingPath, { - color: value["Color"], - weight: 3, - opacity: 1, - smoothFactor: 1 - }); - - markerData[tourId].walkingPath = tourPolyline; - resolve() + resolve(); + }).catch(function (err) { + console.error('Error fetching route for tour', tourId, err); + markerData[tourId].walkingPath = new L.Polyline([], { color: value["Color"] || '#000000', weight: 3 }); + resolve(); }); }); }) Promise.all(requests).then(() => { createCustomCSS(); - if (IS_AUTO_FIT){ - map.fitBounds(markerBounds, {padding: [10, 10]}) - mapLocateCenter = function(map) { - map.fitBounds(markerBounds, {padding: [10, 10]}) - } + if (IS_AUTO_FIT) { + map.fitBounds(markerBounds, { padding: [10, 10] }) + mapLocateCenter = function (map) { + map.fitBounds(markerBounds, { padding: [10, 10] }) + } var curZoom = map._zoom; - map.setMaxZoom( curZoom + MAP_MAX_ZOOM_STOP); - map.setMinZoom( curZoom - MAP_MIN_ZOOM_STOP); - // map["options"]["minZoom"] = curZoom - MAP_MIN_ZOOM_STOP + map.setMaxZoom(curZoom + MAP_MAX_ZOOM_STOP); + map.setMinZoom(curZoom - MAP_MIN_ZOOM_STOP); + // map["options"]["minZoom"] = curZoom - MAP_MIN_ZOOM_STOP } doFilters(); }); @@ -605,6 +788,7 @@ jQuery(document).ready(function ($) { * This must be called on every form change. */ function doFilters() { + console.log(markers) // Remove the current markers. if (markers) { map.removeLayer(markers); @@ -662,10 +846,10 @@ jQuery(document).ready(function ($) { for (const tour_id in markerData) { var color = markerData[tour_id]['Color'] - if (color.length == 0){ + if (color.length == 0) { color = "#000000" } - + var rgb = hexToRgb(color) css += `#filters div label.label${tour_id}:before { background-color: ${color} !important; @@ -809,9 +993,9 @@ jQuery(document).ready(function ($) { rightContent += '

No descriptions available.

'; } rightContent += '
' - rightContent += ''+ DETAIL_BUTTON_TEXT +''; - if (response.exhibitUrl != ""){ - rightContent += ''+ EXHIBIT_BUTTON_TEXT +''; + rightContent += '' + DETAIL_BUTTON_TEXT + ''; + if (response.exhibitUrl != "") { + rightContent += '' + EXHIBIT_BUTTON_TEXT + ''; } rightContent += '
' infoContent += '
' + rightContent + '
'; @@ -915,9 +1099,9 @@ jQuery(document).ready(function ($) { return b_new } - /* - * Revert to default (original) form state. - */ + /* + * Revert to default (original) form state. + */ function revertFormState() { if (historicMapLayer) { removeHistoricMapLayer(); @@ -1020,10 +1204,10 @@ jQuery(document).ready(function ($) { function hexToRgb(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) } : null; - } + } }); diff --git a/views/admin/tours/add.php b/views/admin/tours/add.php index ed96d43..2571ded 100644 --- a/views/admin/tours/add.php +++ b/views/admin/tours/add.php @@ -1,43 +1,58 @@ 'Add Tour', 'content_class' => 'vertical-nav', - 'bodyclass' => 'tours primary add-tour-form' ) ); +echo head(array( + 'title' => 'Add Tour', + 'content_class' => 'vertical-nav', + 'bodyclass' => 'tours primary add-tour-form' +)); echo flash(); ?>
- - - + +
- formSubmit( 'submit', __('Add Tour'), - array( 'id' => 'save-changes', - 'class' => 'submit big green button' ) ); ?> + formSubmit( + 'submit', + __('Add Tour'), + array( + 'id' => 'save-changes', + 'class' => 'submit big green button' + ) + ); ?>
@@ -46,4 +61,4 @@ - + \ No newline at end of file diff --git a/views/admin/tours/browse.php b/views/admin/tours/browse.php index a34b229..06a0407 100644 --- a/views/admin/tours/browse.php +++ b/views/admin/tours/browse.php @@ -20,10 +20,10 @@
- + @@ -38,7 +38,7 @@ 'show','id' => $tour->id ), 'tourAction' ); $editUrl = url( array( 'action' => 'edit','id' => $tour->id ), 'tourAction' ); @@ -72,7 +72,7 @@ - +

diff --git a/views/admin/tours/edit.php b/views/admin/tours/edit.php index 9e7363a..32b26e4 100644 --- a/views/admin/tours/edit.php +++ b/views/admin/tours/edit.php @@ -1,62 +1,76 @@ $tourTitle, 'content_class' => 'vertical-nav', - 'bodyclass' => 'edit','bodyid'=>'tour' ) ); +echo head(array( + 'title' => $tourTitle, + 'content_class' => 'vertical-nav', + 'bodyclass' => 'tours primary edit add-tour-form', + 'bodyid' => 'tour' +)); echo flash(); ?> -
-
- formSubmit( 'submit', __('Save Changes'), - array( 'id' => 'save-changes', - 'class' => 'submit big green button' ) ); ?> - id ) ); ?>" - class="big blue button" target="_blank"> +
+
+ formSubmit( + 'submit', + __('Save Changes'), + array( + 'id' => 'save-changes', + 'class' => 'submit big green button' + ) + ); ?> + - 'delete-confirm big red button' ), - 'delete-confirm' ); ?> + 'delete-confirm big red button'), + 'delete-confirm' + ); ?>
- - -
+ \ No newline at end of file diff --git a/views/admin/tours/show.php b/views/admin/tours/show.php index 2661846..e7768ea 100644 --- a/views/admin/tours/show.php +++ b/views/admin/tours/show.php @@ -14,46 +14,46 @@
- +

Title

- +
- +

Credits

- +
- +

Description

- +
- +

Postscript Text

- '.htmlspecialchars_decode(metadata( 'tour', 'postscript_text' )).''; ?> + '.htmlspecialchars_decode(metadata( 'walking_tour', 'postscript_text' )).''; ?>
getItems(); -if( $tour->getItems() ): ?> +$items = $walking_tour->getItems(); +if( $walking_tour->getItems() ): ?>

Items

@@ -75,7 +75,7 @@
- @@ -98,13 +98,13 @@ class="big blue button" target="_blank"> : - public) ? __('Yes') : __('No'); ?> + public) ? __('Yes') : __('No'); ?>

: - featured) ? __('Yes') : __('No'); ?> + featured) ? __('Yes') : __('No'); ?>

diff --git a/views/public/css/walking-tour.css b/views/public/css/walking-tour-public.css similarity index 100% rename from views/public/css/walking-tour.css rename to views/public/css/walking-tour-public.css diff --git a/views/public/index/index.php b/views/public/index/index.php index 4bb7c51..f1730ef 100755 --- a/views/public/index/index.php +++ b/views/public/index/index.php @@ -11,7 +11,7 @@
-

Tours

+

Walking Tours

Choose tour