Eliminar o modulo do Google Maps non usado.

This commit is contained in:
Laegnur 2024-04-04 15:11:26 +02:00
parent 0b9c73d8f3
commit 02db83f5ba
16 changed files with 0 additions and 3165 deletions

View file

@ -1,276 +0,0 @@
<?php
/**
* ProcessWire Map Marker Fieldtype
*
* Holds an address and geocodes it to latitude and longitude via Google Maps
*
* For documentation about the fields used in this class, please see:
* /wire/core/Fieldtype.php
*
* ProcessWire 2.x
* Copyright (C) 2016 by Ryan Cramer
* Licensed under MPL 2.0
*
* https://processwire.com
*
* @todo implement a getMatchQuery method and support LIKE with address.
*
* @property string $googleApiKey
*
*/
class FieldtypeMapMarker extends Fieldtype implements ConfigurableModule {
public static function getModuleInfo() {
return array(
'title' => 'Map Marker',
'version' => 209,
'summary' => 'Field that stores an address with latitude and longitude coordinates and has built-in geocoding capability with Google Maps API.',
'installs' => 'InputfieldMapMarker',
'icon' => 'map-marker',
);
}
/**
* Include our MapMarker class, which serves as the value for fields of type FieldtypeMapMarker
*
*/
public function __construct() {
require_once(dirname(__FILE__) . '/MapMarker.php');
}
/**
* Return the Inputfield required by this Fieldtype
*
* @param Page $page
* @param Field $field
* @return InputfieldMapMarker
*
*/
public function getInputfield(Page $page, Field $field) {
/** @var InputfieldMapMarker $inputfield */
$inputfield = $this->modules->get('InputfieldMapMarker');
$inputfield->set('googleApiKey', $this->get('googleApiKey'));
return $inputfield;
}
/**
* Return all compatible Fieldtypes
*
* @param Field $field
* @return null
*
*/
public function ___getCompatibleFieldtypes(Field $field) {
// there are no other fieldtypes compatible with this one
return null;
}
/**
* Sanitize value for runtime
*
* @param Page $page
* @param Field $field
* @param MapMarker $value
* @return MapMarker
*
*/
public function sanitizeValue(Page $page, Field $field, $value) {
// if it's not a MapMarker, then just return a blank MapMarker
if(!$value instanceof MapMarker) $value = $this->getBlankValue($page, $field);
// if the address changed, tell the $page that this field changed
if($value->isChanged('address')) $page->trackChange($field->name);
return $value;
}
/**
* Get a blank value used by this fieldtype
*
* @param Page $page
* @param Field $field
* @return MapMarker
*
*/
public function getBlankValue(Page $page, Field $field) {
return new MapMarker();
}
/**
* Given a raw value (value as stored in DB), return the value as it would appear in a Page object
*
* @param Page $page
* @param Field $field
* @param string|int|array $value
* @return string|int|array|object $value
*
*/
public function ___wakeupValue(Page $page, Field $field, $value) {
// get a blank MapMarker instance
$marker = $this->getBlankValue($page, $field);
if("$value[lat]" === "0") $value['lat'] = '';
if("$value[lng]" === "0") $value['lng'] = '';
// populate the marker
$marker->address = $value['data'];
$marker->lat = $value['lat'];
$marker->lng = $value['lng'];
$marker->status = $value['status'];
$marker->zoom = $value['zoom'];
$marker->setTrackChanges(true);
return $marker;
}
/**
* Given an 'awake' value, as set by wakeupValue, convert the value back to a basic type for storage in DB.
*
* @param Page $page
* @param Field $field
* @param string|int|array|object $value
* @return string|int
* @throws WireException
*
*/
public function ___sleepValue(Page $page, Field $field, $value) {
$marker = $value;
if(!$marker instanceof MapMarker)
throw new WireException("Expecting an instance of MapMarker");
// if the address was changed, then force it to geocode the new address
if($marker->isChanged('address') && $marker->address && $marker->status != MapMarker::statusNoGeocode) $marker->geocode();
$sleepValue = array(
'data' => $marker->address,
'lat' => strlen($marker->lat) ? $marker->lat : 0,
'lng' => strlen($marker->lng) ? $marker->lng : 0,
'status' => $marker->status,
'zoom' => $marker->zoom
);
return $sleepValue;
}
/**
* Return the database schema in specified format
*
* @param Field $field
* @return array
*
*/
public function getDatabaseSchema(Field $field) {
// get the default schema
$schema = parent::getDatabaseSchema($field);
$schema['data'] = "VARCHAR(255) NOT NULL DEFAULT ''"; // address (reusing the 'data' field from default schema)
$schema['lat'] = "FLOAT(10,6) NOT NULL DEFAULT 0"; // latitude
$schema['lng'] = "FLOAT(10,6) NOT NULL DEFAULT 0"; // longitude
$schema['status'] = "TINYINT NOT NULL DEFAULT 0"; // geocode status
$schema['zoom'] = "TINYINT NOT NULL DEFAULT 0"; // zoom level (schema v1)
$schema['keys']['latlng'] = "KEY latlng (lat, lng)"; // keep an index of lat/lng
$schema['keys']['data'] = 'FULLTEXT KEY `data` (`data`)';
$schema['keys']['zoom'] = "KEY zoom (zoom)";
if($field->id) $this->updateDatabaseSchema($field, $schema);
return $schema;
}
/**
* Update the DB schema, if necessary
*
* @param Field $field
* @param array $schema
*
*/
protected function updateDatabaseSchema(Field $field, array $schema) {
$requiredVersion = 1;
$schemaVersion = (int) $field->get('schemaVersion');
if($schemaVersion >= $requiredVersion) {
// already up-to-date
return;
}
if($schemaVersion == 0) {
// update schema to v1: add 'zoom' column
$schemaVersion = 1;
$database = $this->wire('database');
$table = $database->escapeTable($field->getTable());
$query = $database->prepare("SHOW TABLES LIKE '$table'");
$query->execute();
$row = $query->fetch(\PDO::FETCH_NUM);
$query->closeCursor();
if(!empty($row)) {
$query = $database->prepare("SHOW COLUMNS FROM `$table` WHERE field='zoom'");
$query->execute();
if(!$query->rowCount()) try {
$database->exec("ALTER TABLE `$table` ADD zoom $schema[zoom] AFTER status");
$this->message("Added 'zoom' column to '$field->table'");
} catch(Exception $e) {
$this->error($e->getMessage());
}
}
}
$field->set('schemaVersion', $schemaVersion);
$field->save();
}
/**
* Match values for PageFinder
*
* @param DatabaseQuerySelect $query
* @param string $table
* @param string $subfield
* @param string $operator
* @param string $value
* @return DatabaseQuerySelect
*
*/
public function getMatchQuery($query, $table, $subfield, $operator, $value) {
if(!$subfield || $subfield == 'address') $subfield = 'data';
if($subfield != 'data' || $this->wire('database')->isOperator($operator)) {
// if dealing with something other than address, or operator is native to SQL,
// then let Fieldtype::getMatchQuery handle it instead
return parent::getMatchQuery($query, $table, $subfield, $operator, $value);
}
// if we get here, then we're performing either %= (LIKE and variations) or *= (FULLTEXT and variations)
$ft = new DatabaseQuerySelectFulltext($query);
$ft->match($table, $subfield, $operator, $value);
return $query;
}
/**
* Module configuration
*
* @param array $data
* @return InputfieldWrapper
*
*/
public static function getModuleConfigInputfields(array $data) {
$inputfields = new InputfieldWrapper();
/** @var InputfieldText $f */
$f = wire('modules')->get('InputfieldText');
$f->attr('name', 'googleApiKey');
$f->label = __('Google Maps API Key');
$f->icon = 'map';
$f->description = sprintf(__('[Click here](%s) for instructions from Google on how to obtain an API key.'),
'https://developers.google.com/maps/documentation/javascript/get-api-key');
$f->attr('value', isset($data['googleApiKey']) ? $data['googleApiKey'] : '');
$inputfields->add($f);
return $inputfields;
}
}

View file

@ -1,73 +0,0 @@
.InputfieldMapMarker input[type=number],
.InputfieldMapMarker input[type=text] {
width: 99.5%;
}
.InputfieldMapMarkerToggle span {
display: none;
}
.InputfieldMapMarkerAddress {
float: left;
width: 70%;
padding-right: 2%;
}
.InputfieldMapMarkerToggle {
float: left;
width: 28%;
}
.InputfieldMapMarkerLat,
.InputfieldMapMarkerLng {
width: 42%;
float: left;
padding-right: 2%;
}
.InputfieldMapMarkerZoom {
float: left;
width: 10%;
}
.InputfieldMapMarker .notes {
clear: both;
}
.InputfieldMapMarkerMap {
width: 100%;
height: 300px;
clear: left;
}
@media only screen and (min-width: 768px) {
.InputfieldMapMarkerAddress {
width: 38%;
padding-right: 1%;
}
.InputfieldMapMarkerToggle {
width: 2%;
padding-right: 0.5%;
position: relative;
}
.InputfieldMapMarkerToggle strong {
/* hide geocode label */
display: none;
}
.InputfieldMapMarkerLat,
.InputfieldMapMarkerLng {
width: 23%;
padding-right: 1%;
}
.InputfieldMapMarkerZoom {
float: left;
width: 9.5%;
}
}

View file

@ -1,130 +0,0 @@
/**
* Display a Google Map and pinpoint a location for InputfieldMapMarker
*
*/
var InputfieldMapMarker = {
options: {
zoom: 12, // mats, previously 5
draggable: true, // +mats
center: null,
//key: config.InputfieldMapMarker.googleApiKey,
mapTypeId: google.maps.MapTypeId.HYBRID,
scrollwheel: false,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
},
scaleControl: false
},
init: function(mapId, lat, lng, zoom, mapType) {
var options = InputfieldMapMarker.options;
if(zoom < 1) zoom = 12;
options.center = new google.maps.LatLng(lat, lng);
options.zoom = parseInt(zoom);
if(mapType == 'SATELLITE') options.mapTypeId = google.maps.MapTypeId.SATELLITE;
else if(mapType == 'ROADMAP') options.mapTypeId = google.maps.MapTypeId.ROADMAP;
var map = new google.maps.Map(document.getElementById(mapId), options);
var marker = new google.maps.Marker({
position: options.center,
map: map,
draggable: options.draggable
});
var $map = $('#' + mapId);
var $lat = $map.siblings(".InputfieldMapMarkerLat").find("input[type=text]");
var $lng = $map.siblings(".InputfieldMapMarkerLng").find("input[type=text]");
var $addr = $map.siblings(".InputfieldMapMarkerAddress").find("input[type=text]");
var $addrJS = $map.siblings(".InputfieldMapMarkerAddress").find("input[type=hidden]");
var $toggle = $map.siblings(".InputfieldMapMarkerToggle").find("input[type=checkbox]");
var $zoom = $map.siblings(".InputfieldMapMarkerZoom").find("input[type=number]");
var $notes = $map.siblings(".notes");
$lat.val(marker.getPosition().lat());
$lng.val(marker.getPosition().lng());
$zoom.val(map.getZoom());
google.maps.event.addListener(marker, 'dragend', function(event) {
var geocoder = new google.maps.Geocoder();
var position = this.getPosition();
$lat.val(position.lat());
$lng.val(position.lng());
if($toggle.is(":checked")) {
geocoder.geocode({ 'latLng': position }, function(results, status) {
if(status == google.maps.GeocoderStatus.OK && results[0]) {
$addr.val(results[0].formatted_address);
$addrJS.val($addr.val());
}
$notes.text(status);
});
}
});
google.maps.event.addListener(map, 'zoom_changed', function() {
$zoom.val(map.getZoom());
});
$addr.blur(function() {
if(!$toggle.is(":checked")) return true;
var geocoder = new google.maps.Geocoder();
geocoder.geocode({ 'address': $(this).val()}, function(results, status) {
if(status == google.maps.GeocoderStatus.OK && results[0]) {
var position = results[0].geometry.location;
map.setCenter(position);
marker.setPosition(position);
$lat.val(position.lat());
$lng.val(position.lng());
$addrJS.val($addr.val());
}
$notes.text(status);
});
return true;
});
$zoom.change(function() {
map.setZoom(parseInt($(this).val()));
});
$toggle.click(function() {
if($(this).is(":checked")) {
$notes.text('Geocode ON');
// google.maps.event.trigger(marker, 'dragend');
$addr.trigger('blur');
} else {
$notes.text('Geocode OFF');
}
return true;
});
// added by diogo to solve the problem of maps not rendering correctly in hidden elements
// trigger a resize on the map when either the tab button or the toggle field bar are pressed
// get the tab element where this map is integrated
var $map = $('#' + mapId);
var $tab = $('#_' + $map.closest('.InputfieldFieldsetTabOpen').attr('id'));
// get the inputfield where this map is integrated and add the tab to the stack
var $inputFields = $map.closest('.Inputfield').find('.InputfieldStateToggle').add($tab);
$inputFields.on('click',function(){
// give it time to open
window.setTimeout(function(){
google.maps.event.trigger(map,'resize');
map.setCenter(options.center);
}, 200);
});
}
};
$(document).ready(function() {
$(".InputfieldMapMarkerMap").each(function() {
var $t = $(this);
InputfieldMapMarker.init($t.attr('id'), $t.attr('data-lat'), $t.attr('data-lng'), $t.attr('data-zoom'), $t.attr('data-type'));
});
});

View file

@ -1,329 +0,0 @@
<?php
/**
* ProcessWire Map Marker Inputfield
*
* Provides the admin control panel inputs for FieldtypeMapMarker
*
* ProcessWire 2.x
* Copyright (C) 2016 by Ryan Cramer
* Licensed under MPL 2.0
*
* https://processwire.com
*
* @property string $defaultAddr
* @property int $defaultZoom
* @property string $defaultType
* @property string $defaultLat
* @property string $defaultLng
* @property int $height
* @property string $googleApiKey
*
*/
class InputfieldMapMarker extends Inputfield {
public static function getModuleInfo() {
return array(
'title' => 'Map Marker',
'version' => 209,
'summary' => "Provides input for the MapMarker Fieldtype",
'requires' => 'FieldtypeMapMarker',
'icon' => 'map-marker',
);
}
const defaultAddr = 'Castaway Cay';
/**
* Just in case this Inputfield is being used separately from FieldtypeMapmarker, we include the MapMarker class
*
*/
public function __construct() {
require_once(dirname(__FILE__) . '/MapMarker.php');
$this->set('defaultAddr', self::defaultAddr);
$this->set('defaultZoom', 12);
$this->set('defaultType', 'HYBRID');
$this->set('defaultLat', '');
$this->set('defaultLng', '');
$this->set('height', 300);
$this->set('googleApiKey', '');
parent::__construct();
}
/**
* Set an attribute to this Inputfield
*
* In this case, we just capture the 'value' attribute and make sure it's something valid
*
* @param string $key
* @param mixed $value
* @return $this
* @throws WireException
*
*/
public function setAttribute($key, $value) {
if($key == 'value' && !$value instanceof MapMarker && !is_null($value)) {
throw new WireException("This input only accepts a MapMarker for it's value");
}
return parent::setAttribute($key, $value);
}
public function isEmpty() {
return (!$this->value || ((float) $this->value->lat) === 0.0);
}
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
$url = 'https://maps.google.com/maps/api/js';
$key = $this->get('googleApiKey');
if($key) $url .= "?key=$key";
$this->config->scripts->add($url);
return parent::renderReady($parent, $renderValueMode);
}
/**
* Render the markup needed to draw the Inputfield
*
* @return string
*
*/
public function ___render() {
$name = $this->attr('name');
$id = $this->attr('id');
$marker = $this->attr('value');
if($marker->lat == 0.0) $marker->lat = $this->defaultLat;
if($marker->lng == 0.0) $marker->lng = $this->defaultLng;
if(!$marker->zoom) $marker->zoom = $this->defaultZoom;
$address = htmlentities($marker->address, ENT_QUOTES, "UTF-8");
$toggleChecked = $marker->status != MapMarker::statusNoGeocode ? " checked='checked'" : '';
$status = $marker->status == MapMarker::statusNoGeocode ? 0 : $marker->status;
$mapType = $this->defaultType;
$height = $this->height ? (int) $this->height : 300;
$labels = array(
'addr' => $this->_('Address'),
'lat' => $this->_('Latitude'),
'lng' => $this->_('Longitude'),
'geo' => $this->_('Geocode?'),
'zoom' => $this->_('Zoom')
);
$out = <<< _OUT
<span></span>
<p class='InputfieldMapMarkerAddress'>
<label>
<strong>$labels[addr]</strong>
<br />
<input type='text' id='{$id}' name='{$name}' value='{$address}' /><br />
</label>
<input type='hidden' id='_{$name}_js_geocode_address' name='_{$name}_js_geocode_address' value='' />
</p>
<p class='InputfieldMapMarkerToggle'>
<label>
<br />
<input title='Geocode ON/OFF' type='checkbox' name='_{$name}_status' id='_{$name}_toggle' value='$status'$toggleChecked />
<strong>$labels[geo]</strong>
</label>
</p>
<p class='InputfieldMapMarkerLat'>
<label>
<strong>$labels[lat]</strong><br />
<input type='text' id='_{$id}_lat' name='_{$name}_lat' value='{$marker->lat}' />
</label>
</p>
<p class='InputfieldMapMarkerLng'>
<label>
<strong>$labels[lng]</strong><br />
<input type='text' id='_{$id}_lng' name='_{$name}_lng' value='{$marker->lng}' />
</label>
</p>
<p class='InputfieldMapMarkerZoom'>
<label>
<strong>$labels[zoom]</strong><br />
<input type='number' min='0' id='_{$id}_zoom' name='_{$name}_zoom' value='{$marker->zoom}' />
</label>
</p>
_OUT;
$out .= "<div class='InputfieldMapMarkerMap' " .
"id='_{$id}_map' " .
"style='height: {$height}px' " .
"data-lat='$marker->lat' " .
"data-lng='$marker->lng' " .
"data-zoom='$marker->zoom' " .
"data-type='$mapType'>" .
"</div>";
$this->notes = $marker->statusString;
if(!$this->get('googleApiKey')) {
$msg = $this->_('Please setup a Google Maps API key in the FieldtypeMapMarker module settings');
if($this->wire('user')->isSuperuser()) {
$link = "<a href='{$this->config->urls->admin}module/edit?name=FieldtypeMapMarker'>";
$msg = "$link$msg</a>";
$this->warning($msg, Notice::allowMarkup);
} else {
$this->warning($msg);
}
}
return $out;
}
/**
* Process the input after a form submission
*
* @param WireInputData $input
* @return $this
*
*/
public function ___processInput(WireInputData $input) {
$name = $this->attr('name');
$marker = $this->attr('value');
if(!isset($input->$name)) return $this;
if($input->$name == $this->defaultAddr) {
$marker->set('address', '');
} else {
$marker->set('address', $input->$name);
}
$lat = $input["_{$name}_lat"];
$lng = $input["_{$name}_lng"];
$precision = 4;
if( ((string) round($lat, $precision)) != ((string) round($this->defaultLat, $precision)) ||
((string) round($lng, $precision)) != ((string) round($this->defaultLng, $precision))) {
$marker->set('lat', $lat);
$marker->set('lng', $lng);
} else {
// $this->message("Kept lat/lng at unset value", Notice::debug);
}
$zoom = $input["_{$name}_zoom"];
if($zoom > -1 && $zoom < 30) $marker->zoom = (int) $zoom;
$status = $input["_{$name}_status"];
if(is_null($status)) $marker->set('status', MapMarker::statusNoGeocode); // disable geocode
else $marker->set('status', (int) $status);
// if the address changed, then redo the geocoding.
// while we do this in the Fieldtype, we also do it here in case this Inputfield is used on it's own.
// the MapMarker class checks to make sure it doesn't do the same geocode twice.
if($marker->isChanged('address') && $marker->address && $marker->status != MapMarker::statusNoGeocode) {
// double check that the address wasn't already populated by the JS geocoder
// this prevents user-dragged markers that don't geocode to an exact location from getting
// unintentionally moved by the PHP-side geocoder
if($input["_{$name}_js_geocode_address"] == $marker->address) {
// prevent the geocoder from running in the fieldtype
$marker->skipGeocode = true;
$this->message('Skipping geocode (already done by JS geocoder)', Notice::debug);
} else {
$marker->geocode();
}
}
return $this;
}
public function ___getConfigInputfields() {
$inputfields = parent::___getConfigInputfields();
/** @var InputfieldText $field */
$field = $this->modules->get('InputfieldText');
$field->attr('name', 'defaultAddr');
$field->label = $this->_('Default Address');
$field->description = $this->_('This will be geocoded to become the starting point of the map.');
$field->attr('value', $this->defaultAddr);
$field->notes = $this->_('When modifying the default address, please make the Latitude and Longitude fields below blank, which will force the system to geocode your new address.');
$inputfields->add($field);
if(!$this->defaultLat && !$this->defaultLng) {
$m = new MapMarker();
$m->address = $this->defaultAddr;
$status = $m->geocode();
if($status > 0) {
$this->defaultLat = $m->lat;
$this->defaultLng = $m->lng;
$this->message($this->_('Geocoded your default address. Please hit save once again to commit the new default latitude and longitude.'));
}
}
/** @var InputfieldText $field */
$field = $this->modules->get('InputfieldText');
$field->attr('name', 'defaultLat');
$field->label = $this->_('Default Latitude');
$field->attr('value', $this->defaultLat);
$field->columnWidth = 50;
$inputfields->add($field);
/** @var InputfieldText $field */
$field = $this->modules->get('InputfieldText');
$field->attr('name', 'defaultLng');
$field->label = $this->_('Default Longitude');
$field->attr('value', $this->defaultLng);
$field->columnWidth = 50;
$inputfields->add($field);
/** @var InputfieldRadios $field */
$field = $this->modules->get('InputfieldRadios');
$field->attr('name', 'defaultType');
$field->label = $this->_('Default Map Type');
$field->addOption('HYBRID', $this->_('Hybrid'));
$field->addOption('ROADMAP', $this->_('Road Map'));
$field->addOption('SATELLITE', $this->_('Satellite'));
$field->attr('value', $this->defaultType);
$field->optionColumns = 1;
$field->columnWidth = 50;
$inputfields->add($field);
/** @var InputfieldInteger $field */
$field = $this->modules->get('InputfieldInteger');
$field->attr('name', 'height');
$field->label = $this->_('Map Height (in pixels)');
$field->attr('value', $this->height);
$field->attr('type', 'number');
$field->columnWidth = 50;
$inputfields->add($field);
/** @var InputfieldInteger $field */
$field = $this->modules->get('InputfieldInteger');
$field->attr('name', 'defaultZoom');
$field->label = $this->_('Default Zoom');
$field->description = $this->_('Enter a value between 1 and 23. The highest zoom level is typically somewhere between 19-23, depending on the location being zoomed and how much data Google has for the location.'); // Zoom level description
$field->attr('value', $this->defaultZoom);
$field->attr('type', 'number');
$inputfields->add($field);
/** @var InputfieldMarkup $field */
$field = $this->modules->get('InputfieldMarkup');
$field->label = $this->_('API Notes');
$field->description = $this->_('You can access individual values from this field using the following from your template files:');
$field->value =
"<pre>" .
"\$page->{$this->name}->address\n" .
"\$page->{$this->name}->lat\n" .
"\$page->{$this->name}->lng\n" .
"\$page->{$this->name}->zoom" .
"</pre>";
$inputfields->add($field);
return $inputfields;
}
}

View file

@ -1,126 +0,0 @@
<?php
/**
* Class to hold an address and geocode it to latitude/longitude
*
* @property string $lat
* @property string $lng
* @property string $address
* @property int $status
* @property int $zoom
* @property bool $skipGeocode
* @property string $statusString
*
*/
class MapMarker extends WireData {
const statusNoGeocode = -100;
protected $geocodeStatuses = array(
0 => 'N/A',
1 => 'OK',
2 => 'OK_ROOFTOP',
3 => 'OK_RANGE_INTERPOLATED',
4 => 'OK_GEOMETRIC_CENTER',
5 => 'OK_APPROXIMATE',
-1 => 'UNKNOWN',
-2 => 'ZERO_RESULTS',
-3 => 'OVER_QUERY_LIMIT',
-4 => 'REQUEST_DENIED',
-5 => 'INVALID_REQUEST',
-100 => 'Geocode OFF', // RCD
);
protected $geocodedAddress = '';
public function __construct() {
$this->set('lat', '');
$this->set('lng', '');
$this->set('address', '');
$this->set('status', 0);
$this->set('zoom', 0);
// temporary runtime property to indicate the geocode should be skipped
$this->set('skipGeocode', false);
$this->set('statusString', '');
}
public function set($key, $value) {
if($key == 'lat' || $key == 'lng') {
// if value isn't numeric, then it's not valid: make it blank
if(strpos($value, ',') !== false) $value = str_replace(',', '.', $value); // convert 123,456 to 123.456
if(!is_numeric($value)) $value = '';
} else if($key == 'address') {
$value = wire('sanitizer')->text($value);
} else if($key == 'status') {
$value = (int) $value;
if(!isset($this->geocodeStatuses[$value])) $value = -1; // -1 = unknown
} else if($key == 'zoom') {
$value = (int) $value;
}
return parent::set($key, $value);
}
public function get($key) {
if($key == 'statusString') return str_replace('_', ' ', $this->geocodeStatuses[$this->status]);
return parent::get($key);
}
public function geocode() {
if($this->skipGeocode) return -100;
// check if address was already geocoded
if($this->geocodedAddress == $this->address) return $this->status;
$this->geocodedAddress = $this->address;
$url = "https://maps.googleapis.com/maps/api/geocode/json?address=" . urlencode($this->address);
$apiKey = $this->modules->get('FieldtypeMapMarker')->get('googleApiKey');
if($apiKey) $url .= "&key=$apiKey";
$http = new WireHttp();
$json = $http->getJSON($url, true);
if(empty($json['status']) || $json['status'] != 'OK') {
$this->error("Error geocoding address");
if(isset($json['status'])) $this->status = (int) array_search($json['status'], $this->geocodeStatuses);
else $this->status = -1;
$this->lat = 0;
$this->lng = 0;
return $this->status;
}
$geometry = $json['results'][0]['geometry'];
$location = $geometry['location'];
$locationType = $geometry['location_type'];
$this->lat = $location['lat'];
$this->lng = $location['lng'];
$statusString = $json['status'] . '_' . $locationType;
$status = array_search($statusString, $this->geocodeStatuses);
if($status === false) $status = 1; // OK
$this->status = $status;
$this->message("Geocode {$this->statusString}: '{$this->address}'");
return $this->status;
}
/**
* If accessed as a string, then just output the lat, lng coordinates
*
*/
public function __toString() {
return "$this->address ($this->lat, $this->lng, $this->zoom) [$this->statusString]";
}
}

View file

@ -1,214 +0,0 @@
/**
* ProcessWire Map Markup (JS)
*
* Renders maps for the FieldtypeMapMarker module
*
* ProcessWire 2.x
* Copyright (C) 2016 by Ryan Cramer
* Licensed under MPL 2.0
*
* http://processwire.com
*
* Javascript Usage:
* =================
* var map = new MarkupGoogleMap();
* map.setOption('any-google-maps-option', 'value');
* map.setOption('zoom', 12); // example
*
* // init(container ID, latitude, longitude):
* map.init('#map-div', 26.0936823, -77.5332796);
*
* // addMarker(latitude, longitude, optional URL, optional URL to icon file):
* map.addMarker(26.0936823, -77.5332796, 'en.wikipedia.org/wiki/Castaway_Cay', '');
* map.addMarker(...you may have as many of these as you want...);
*
* // optionally fit the map to the bounds of the markers you added
* map.fitToMarkers();
*
*/
function MarkupGoogleMap() {
this.map = null;
this.markers = [];
this.numMarkers = 0;
this.icon = '';
this.iconHover = '';
this.shadow = '';
this.hoverBox = null;
this.hoverBoxOffsetTop = 0;
this.hoverBoxOffsetLeft = 0;
this.options = {
zoom: 10,
center: null,
mapTypeId: google.maps.MapTypeId.ROADMAP,
scrollwheel: false,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
},
scaleControl: false,
// disable points of interest
styles: [{
featureType: "poi",
stylers: [ { visibility: "off" } ]
}]
};
this._currentURL = '';
this.init = function(mapID, lat, lng) {
if(lat != 0) this.options.center = new google.maps.LatLng(lat, lng);
this.map = new google.maps.Map(document.getElementById(mapID), this.options);
}
this.setOption = function(key, value) {
this.options[key] = value;
}
this.setIcon = function(url) {
this.icon = url;
}
this.setIconHover = function(url) {
this.iconHover = url;
}
this.setShadow = function(url) {
this.shadow = url;
}
this.setHoverBox = function(markup) {
if(!markup.length) {
this.hoverBox = null;
return;
}
this.hoverBox = $(markup);
var $hoverBox = this.hoverBox;
this.hoverBoxOffsetTop = parseInt($hoverBox.attr('data-top'));
this.hoverBoxOffsetLeft = parseInt($hoverBox.attr('data-left'));
$("body").append($hoverBox);
// keep it hidden/out of the way until needed
$hoverBox.css({
position: 'absolute',
left: 0,
top: '-100px'
});
$hoverBox.mouseout(function() {
$hoverBox.hide();
}).click(function() {
if(this._currentURL.length > 0) window.location.href = this._currentURL;
});
}
this.addMarker = function(lat, lng, url, title, icon, shadow) {
if(lat == 0.0) return;
var latLng = new google.maps.LatLng(lat, lng);
var zIndex = 99999 + this.numMarkers;
var markerOptions = {
position: latLng,
map: this.map,
linkURL: '',
zIndex: zIndex
};
if(typeof icon !== "undefined" && icon.length > 0) markerOptions.icon = icon;
else if(this.icon) markerOptions.icon = this.icon;
// console.log(markerOptions);
if(typeof shadow !== "undefined" && shadow.length > 0) markerOptions.shadow = shadow;
else if(this.shadow.length > 0) markerOptions.shadow = this.shadow;
var marker = new google.maps.Marker(markerOptions);
if(url.length > 0) marker.linkURL = url;
if(this.hoverBox) marker.hoverBoxTitle = title;
else marker.setTitle(title);
this.markers[this.numMarkers] = marker;
this.numMarkers++;
if(marker.linkURL.length > 0) {
google.maps.event.addListener(marker, 'click', function(e) {
window.location.href = marker.linkURL;
});
}
if(markerOptions.icon !== "undefined" && this.iconHover) {
var iconHover = this.iconHover;
google.maps.event.addListener(marker, 'mouseover', function(e) {
marker.setIcon(iconHover);
});
google.maps.event.addListener(marker, 'mouseout', function(e) {
marker.setIcon(markerOptions.icon);
});
}
if(this.hoverBox) {
var $hoverBox = this.hoverBox;
var offsetTop = this.hoverBoxOffsetTop;
var offsetLeft = this.hoverBoxOffsetLeft;
var mouseMove = function(e) {
$hoverBox.css({
'top': e.pageY + offsetTop,
'left': e.pageX + offsetLeft
});
};
// console.log($hoverBox);
google.maps.event.addListener(marker, 'mouseover', function(e) {
this._currentURL = url;
$hoverBox.html("<span>" + marker.hoverBoxTitle + "</span>")
.css('top', '0px')
.css('left', '0px')
.css('display', 'block')
.css('width', 'auto')
.css('z-index', 9999);
$hoverBox.show();
$(document).mousemove(mouseMove);
});
google.maps.event.addListener(marker, 'mouseout', function(e) {
$hoverBox.hide();
$(document).unbind("mousemove", mouseMove);
});
}
}
this.fitToMarkers = function() {
var bounds = new google.maps.LatLngBounds();
var map = this.map;
for(var i = 0; i < this.numMarkers; i++) {
var latLng = this.markers[i].position;
bounds.extend(latLng);
}
map.fitBounds(bounds);
var listener = google.maps.event.addListener(map, "idle", function() {
if(map.getZoom() < 2) map.setZoom(2);
google.maps.event.removeListener(listener);
});
}
}

View file

@ -1,251 +0,0 @@
<?php
/**
* ProcessWire Map Markup
*
* Renders maps for the FieldtypeMapMarker module
*
* ProcessWire 2.x
* Copyright (C) 2016 by Ryan Cramer
* Licensed under MPL 2.0
*
* https://processwire.com
*
* USAGE:
* ======
*
* Add this somewhere before your closing </head> tag:
*
* <script type='text/javascript' src='https://maps.googleapis.com/maps/api/js'></script>
*
* In the location where you want to output your map, do the following in your template file:
*
* $map = $modules->get('MarkupGoogleMap');
* echo $map->render($page, 'map'); // replace 'map' with the name of your FieldtypeMap field
*
* To render a map with multiple markers on it, specify a PageArray rather than a single $page:
*
* $items = $pages->find("template=something, map!='', sort=title");
* $map = $modules->get('MarkupGoogleMap');
* echo $map->render($items, 'map');
*
* To specify options, provide a 3rd argument with an options array:
*
* $map = $modules->get('MarkupGoogleMap');
* echo $map->render($items, 'map', array('height' => '500px'));
*
*
* OPTIONS
* =======
* Here is a list of all possible options (with defaults shown):
*
* // default width of the map
* 'width' => '100%'
*
* // default height of the map
* 'height' => '300px'
*
* // zoom level
* 'zoom' => 12 (or $field->defaultZoom)
*
* // map type: ROADMAP, HYBRID or SATELLITE
* 'type' => 'HYBRID' or $field->defaultType
*
* // map ID attribute
* 'id' => "mgmap"
*
* // map class attribute
* 'class' => "MarkupGoogleMap"
*
* // center latitude
* 'lat' => $field->defaultLat
*
* // center longitude
* 'lng' => $field->defaultLng
*
* // set to false only if you will style the map <div> yourself
* 'useStyles' => true
*
* // allows single-marker map to use marker settings rather than map settings
* 'useMarkerSettings' => true
*
* // field to use for the marker link, or blank to not link
* 'markerLinkField' => 'url'
*
* // field to use for the marker title
* 'markerTitleField' => 'title'
*
* // map will automatically adjust to fit to the given markers (when multiple markers)
* 'fitToMarkers' => true
*
* // use hover box? When true, shows a tooltip-type box when you hover the marker, populated with the markerTitleField
* // this is often more useful than the default presentation google maps uses
* 'useHoverBox' => false
*
* // when useHoverBox is true, you can specify the markup used for it. Use the following (which is the default) as your starting point:
* 'hoverBoxMarkup' => "<div data-top='-10' data-left='15' style='background: #000; color: #fff; padding: 0.25em 0.5em; border-radius: 3px;'></div>",
*
* // FUll URL to icon file to use for markers. Blank=use default Google marker icon.
* 'icon' => '',
*
* // Any extra javascript initialization code you want to occur before the map itself is drawn
* 'init' => '',
*
*/
class MarkupGoogleMap extends WireData implements Module {
public static function getModuleInfo() {
return array(
'title' => 'Map Markup (Google Maps)',
'version' => 101,
'summary' => 'Renders Google Maps for the MapMarker Fieldtype',
'requires' => 'FieldtypeMapMarker',
);
}
/**
* Include our MapMarker class, which serves as the value for fields of type FieldtypeMapMarker
*
*/
public function init() {
require_once(dirname(__FILE__) . '/MapMarker.php');
}
/**
* Get associative array of map options
*
* @param string $fieldName
* @return array
* @throws WireException
*
*/
public function getOptions($fieldName) {
static $n = 0;
$field = $this->fields->get($fieldName);
if(!$field) throw new WireException("Unknown field: $fieldName");
return array(
'useStyles' => true,
'fitToMarkers' => true,
'useMarkerSettings' => true,
'useHoverBox' => false,
'hoverBoxMarkup' => "<div data-top='-10' data-left='15' style='background: #000; color: #fff; padding: 0.25em 0.5em; border-radius: 3px;'></div>",
'markerLinkField' => 'url',
'markerTitleField' => 'title',
'width' => '100%',
'height' => $field->height,
'zoom' => $field->defaultZoom ? (int) $field->defaultZoom : 12,
'type' => $field->defaultType ? $field->defaultType : 'HYBRID',
'id' => "mgmap" . (++$n),
'class' => "MarkupGoogleMap",
'lat' => $field->defaultLat,
'lng' => $field->defaultLng,
'icon' => '', // url to icon (blank=use default)
'iconHover' => '', // url to icon when hovered (default=none)
'shadow' => '', // url to icon shadow (blank=use default)
'init' => '', // additional javascript code to insert in map initialization
'n' => $n,
);
}
/**
* Get the script tag for loading Google Maps
*
* @return string
* @throws WireException
*
*/
public function getGMapScript() {
$url = 'https://maps.google.com/maps/api/js';
$key = $this->wire('modules')->get('FieldtypeMapMarker')->get('googleApiKey');
if($key) $url .= "?key=$key";
return "<script type='text/javascript' src='$url'></script>";
}
/**
* Render map markup
*
* @param PageArray|Page $pageArray Page (or multiple pages in PageArray) containing map field
* @param $fieldName Name of the map field
* @param array $options Options to adjust behavior
* @return string
* @throws WireException
*
*/
public function render($pageArray, $fieldName, array $options = array()) {
static $n = 0;
$n++;
$defaultOptions = $this->getOptions($fieldName);
$options = array_merge($defaultOptions, $options);
if($pageArray instanceof Page) {
$page = $pageArray;
$pageArray = new PageArray();
$pageArray->add($page);
}
$height = $options['height'];
$width = $options['width'];
if(empty($height)) $height = 300;
if(ctype_digit("$height")) $height .= "px";
if(ctype_digit("$width")) $width .= "px";
$style = '';
if($options['useStyles'] && !empty($height) && !empty($width)) {
$style = " style='width: $width; height: $height;'";
}
$lat = $options['lat'];
$lng = $options['lng'];
$zoom = $options['zoom'] > 0 ? (int) $options['zoom'] : $defaultOptions['zoom'];
$type = in_array($options['type'], array('ROADMAP', 'SATELLITE', 'HYBRID')) ? $options['type'] : 'HYBRID';
if($options['useMarkerSettings'] && (count($pageArray) == 1 || !$lat)) {
// single marker overrides lat, lng and zoom settings
$marker = $pageArray->first()->get($fieldName);
$lat = $marker->lat;
$lng = $marker->lng;
if($marker->zoom > 0) $zoom = (int) $marker->zoom;
}
$id = $options['id'];
$out = '';
if($n === 1) $out .= "<script type='text/javascript' src='{$this->config->urls->MarkupGoogleMap}MarkupGoogleMap.js'></script>";
$out .= "<div id='$id' class='$options[class]'$style></div>";
$out .= "<script type='text/javascript'>" .
"if(typeof google === 'undefined' || typeof google.maps === 'undefined') { " .
"alert('MarkupGoogleMap Error: Please add the maps.google.com script in your document head.'); " .
"} else { " .
"var $id = new MarkupGoogleMap(); " .
"$id.setOption('zoom', $zoom); " .
"$id.setOption('mapTypeId', google.maps.MapTypeId.$type); " .
($options['icon'] ? "$id.setIcon('$options[icon]'); " : "") .
($options['iconHover'] ? "$id.setIconHover('$options[iconHover]'); " : "") .
($options['shadow'] ? "$id.setShadow('$options[shadow]'); " : "") .
($options['useHoverBox'] ? "$id.setHoverBox('" . str_replace("'", '"', $options['hoverBoxMarkup']) . "');" : "") .
$options['init'] .
"$id.init('$id', $lat, $lng); ";
foreach($pageArray as $page) {
$marker = $page->get($fieldName);
if(!$marker instanceof MapMarker) continue;
if(!$marker->lat) continue;
$url = $options['markerLinkField'] ? $page->get($options['markerLinkField']) : '';
$title = $options['markerTitleField'] ? $page->get($options['markerTitleField']) : '';
$out .= "$id.addMarker($marker->lat, $marker->lng, '$url', '$title', ''); ";
}
if(count($pageArray) > 1 && $options['fitToMarkers']) $out .= "$id.fitToMarkers(); ";
$out .= "}</script>";
return $out;
}
}

View file

@ -1,147 +0,0 @@
# FieldtypeMapMarker Module for ProcessWire
This Fieldtype for ProcessWire holds an address or location name, and automatically
geocodes the address to latitude/longitude using Google Maps API. The resulting
values may be used to populate any kind of map (whether Google Maps or another).
This Fieldtype was created to serve as an example of creating a custom Fieldtype and
Inputfield that contains multiple pieces of data. Though the Fieldtype has now gone
far beyond that and is relatively full featured. As a result, it may no longer be
the simplest example of how to implement a Fieldtype/Inputfield, though it is very
effective and useful.
MapMarker also has a corresponding Inputfield and Markup module, named
InputfieldMapMarker and MarkupGoogleMap. When you install FieldtypeMapMarker, the
Inputfield will also be installed and used for input on the admin side. Installation
of MarkupGoogleMap is optional. It provides a simple way to render Google maps with
the data managed by FieldtypeMapMarker.
This Fieldtype has a [support forum](http://processwire.com/talk/index.php/topic,752.0.html)
## Using Map Marker
### How to install
1. Copy all of the files for this module into /site/modules/FieldtypeMapMarker/
2. In your admin, go to the Modules screen and "check for new modules." Click *install*
for the Map Marker Fieldtype.
3. In your admin, go to Setup > Fields > Add New Field. Choose MapMarker as the type.
If you are not sure what to name your field, simply "map" is a good one! Once created,
configure the settings on the *input* tab.
4. Add your new "map" field to one or more templates, as you would any other field.
### How to use from the page editor
1. Create or edit a page using one of the templates you added the "map" field to.
2. Type in a location or address into the "address" box for the map field. Then click
outside of the address, and the Javascript geocoder should automatically populate the
latitude, longitude and map location. The Google geocoder will accept full addresses
or known location names. For instance, you could type in "Disney Land" and it knows
how to find locations like that.
3. The geocoding also works in reverse. You may drag the map marker wherever you want
and it will populate the address field for you. You may also populate the latitude,
longitude and zoom fields manually if you like. Unchecking the box between address
and latitude disables the geocoder.
### How to use from the API, in your template files
In your template files, you can utilize this data for your own Google Maps (or anything
else that you might need latitude/longitude for).
Lets assume that your field is called 'map'. Here is how you would access the
components of it from the API:
```````````
echo $page->map->address; // outputs the address you entered
echo $page->map->lat; // outputs the latitude
echo $page->map->lng; // outputs the longitude
echo $page->map->zoom; // outputs the zoom level
`````````
-------------
## Markup Google Map
This package also comes with a module called MarkupGoogleMap. It provides a simple means
of outputting a Google Map based on the data managed by FieldtypeMapMarker. To install,
simply click "install" for the Google Maps (Markup) module. This is a Markup module,
meaning it exists primarily to generate markup for output on the front-end of your site.
### How to use
Add this somewhere before your closing `</head>` tag:
`````````
<script type='text/javascript' src='https://maps.googleapis.com/maps/api/js?sensor=false'></script>
`````````
In the location where you want to output your map, place the following in your template file:
`````````
$map = $modules->get('MarkupGoogleMap');
echo $map->render($page, 'map');
`````````
In the above, $page is the Page object that has the 'map' field. Rreplace 'map' with the name of
your FieldtypeMap field
To render a map with multiple markers on it, specify a PageArray rather than a single $page:
`````````
$items = $pages->find("template=something, map!='', sort=title");
$map = $modules->get('MarkupGoogleMap');
echo $map->render($items, 'map');
`````````
To specify options, provide a 3rd argument with an options array:
`````````
$map = $modules->get('MarkupGoogleMap');
echo $map->render($items, 'map', array('height' => '500px'));
`````````
### Options
Here is a list of all possible options (with defaults shown):
`width`
Width of the map (type: string; default: 100%).
`height`
Height of the map (type: string; default: 300px)
`zoom`
Zoom level 1-25 (type: integer; default: from your field settings)
`type`
Map type: ROADMAP, HYBRID or SATELLITE (type: string; default: from your field settings)
`id`
Map ID attribute (type: string; default: mgmap)
`class`
Map class attribute (type: string; default: MarkupGoogleMap)
`lat`
Map center latitude (type: string|float; default: from your field settings)
`lng`
Map center longitude (type: string|float; default: from your field settings)
`useStyles`
Whether to populate inline styles to the map div for width/height (type: boolean; default: true).
Set to false only if you will style the map div yourself.
`useMarkerSettings`
Makes single-marker map use marker settings rather than map settings (type: boolean; default: true).
`markerLinkField`
Page field to use for the marker link, or blank to not link (type: string; default: url).
`markerTitleField`
Page field to use for the marker title, or blank not to use a marker title (type: string; default: title).
`fitToMarkers`
When multiple markers are present, set map to automatically adjust to fit to the given markers (type: boolean; default: true).
---------

View file

@ -1,299 +0,0 @@
<?php namespace ProcessWire;
/**
* ProcessWire Map Marker Fieldtype
*
* Holds an address and geocodes it to latitude and longitude via Google Maps
*
* For documentation about the fields used in this class, please see:
* /wire/core/Fieldtype.php
*
* ProcessWire 3.x
* Copyright (C) 2023 by Ryan Cramer
* Licensed under MPL 2.0
*
* https://processwire.com
*
* @todo implement a getMatchQuery method and support LIKE with address.
*
* @property string $googleApiKey
*
*/
class FieldtypeMapMarker extends Fieldtype implements ConfigurableModule {
public static function getModuleInfo() {
return array(
'title' => 'Map Marker',
'version' => 300,
'summary' => 'Field that stores an address with latitude and longitude coordinates and has built-in geocoding capability with Google Maps API.',
'installs' => 'InputfieldMapMarker',
'icon' => 'map-marker',
);
}
/**
* Include our MapMarker class, which serves as the value for fields of type FieldtypeMapMarker
*
*/
public function __construct() {
parent::__construct();
require_once(dirname(__FILE__) . '/MapMarker.php');
}
public function set($key, $value) {
/*
if($key === 'googleApiKey' && strpos($this->wire('config')->httpHost, 'localhost') !== false) {
// disable API key when in localhost environment
if($this->wire('page')->template == 'admin') $value = '';
}
*/
return parent::set($key, $value);
}
public function getGoogleMapsURL() {
$url = 'https://maps.google.com/maps/api/js';
$key = $this->get('googleApiKey');
if($key) $url .= "?key=$key";
return $url;
}
/**
* Return the Inputfield required by this Fieldtype
*
* @param Page $page
* @param Field $field
* @return InputfieldMapMarker
*
*/
public function getInputfield(Page $page, Field $field) {
/** @var InputfieldMapMarker $inputfield */
$inputfield = $this->wire()->modules->get('InputfieldMapMarker');
$inputfield->set('googleApiKey', $this->get('googleApiKey'));
return $inputfield;
}
/**
* Return all compatible Fieldtypes
*
* @param Field $field
* @return null
*
*/
public function ___getCompatibleFieldtypes(Field $field) {
// there are no other fieldtypes compatible with this one
return null;
}
/**
* Sanitize value for runtime
*
* @param Page $page
* @param Field $field
* @param MapMarker $value
* @return MapMarker
*
*/
public function sanitizeValue(Page $page, Field $field, $value) {
// if it's not a MapMarker, then just return a blank MapMarker
if(!$value instanceof MapMarker) $value = $this->getBlankValue($page, $field);
// if the address changed, tell the $page that this field changed
if($value->isChanged('address')) $page->trackChange($field->name);
return $value;
}
/**
* Get a blank value used by this fieldtype
*
* @param Page $page
* @param Field $field
* @return MapMarker
*
*/
public function getBlankValue(Page $page, Field $field) {
$value = new MapMarker();
$this->wire($value);
return $value;
}
/**
* Given a raw value (value as stored in DB), return the value as it would appear in a Page object
*
* @param Page $page
* @param Field $field
* @param string|int|array $value
* @return MapMarker
*
*/
public function ___wakeupValue(Page $page, Field $field, $value) {
// get a blank MapMarker instance
$marker = $this->getBlankValue($page, $field);
if("$value[lat]" === "0") $value['lat'] = '';
if("$value[lng]" === "0") $value['lng'] = '';
// populate the marker
$marker->address = $value['data'];
$marker->lat = $value['lat'];
$marker->lng = $value['lng'];
$marker->status = $value['status'];
$marker->zoom = $value['zoom'];
$marker->setTrackChanges(true);
return $marker;
}
/**
* Given an 'awake' value, as set by wakeupValue, convert the value back to a basic type for storage in DB.
*
* @param Page $page
* @param Field $field
* @param string|int|array|object $value
* @return array
* @throws WireException
*
*/
public function ___sleepValue(Page $page, Field $field, $value) {
$marker = $value;
if(!$marker instanceof MapMarker) {
throw new WireException("Expecting an instance of MapMarker");
}
// if the address was changed, then force it to geocode the new address
if($marker->isChanged('address') && $marker->address && $marker->status != MapMarker::statusNoGeocode) {
$marker->geocode();
}
$sleepValue = array(
'data' => $marker->address,
'lat' => strlen($marker->lat) ? $marker->lat : 0,
'lng' => strlen($marker->lng) ? $marker->lng : 0,
'status' => $marker->status,
'zoom' => $marker->zoom
);
return $sleepValue;
}
/**
* Return the database schema in specified format
*
* @param Field $field
* @return array
*
*/
public function getDatabaseSchema(Field $field) {
// get the default schema
$schema = parent::getDatabaseSchema($field);
$schema['data'] = "VARCHAR(255) NOT NULL DEFAULT ''"; // address (reusing the 'data' field from default schema)
$schema['lat'] = "FLOAT(10,6) NOT NULL DEFAULT 0"; // latitude
$schema['lng'] = "FLOAT(10,6) NOT NULL DEFAULT 0"; // longitude
$schema['status'] = "TINYINT NOT NULL DEFAULT 0"; // geocode status
$schema['zoom'] = "TINYINT NOT NULL DEFAULT 0"; // zoom level (schema v1)
$schema['keys']['latlng'] = "KEY latlng (lat, lng)"; // keep an index of lat/lng
$schema['keys']['data'] = 'FULLTEXT KEY `data` (`data`)';
$schema['keys']['zoom'] = "KEY zoom (zoom)";
if($field->id) $this->updateDatabaseSchema($field, $schema);
return $schema;
}
/**
* Update the DB schema, if necessary
*
* @param Field $field
* @param array $schema
*
*/
protected function updateDatabaseSchema(Field $field, array $schema) {
$requiredVersion = 1;
$schemaVersion = (int) $field->get('schemaVersion');
if($schemaVersion >= $requiredVersion) {
// already up-to-date
return;
}
if($schemaVersion == 0) {
// update schema to v1: add 'zoom' column
$schemaVersion = 1;
$database = $this->wire()->database;
$table = $database->escapeTable($field->getTable());
$query = $database->prepare("SHOW TABLES LIKE '$table'");
$query->execute();
$row = $query->fetch(\PDO::FETCH_NUM);
$query->closeCursor();
if(!empty($row)) {
$query = $database->prepare("SHOW COLUMNS FROM `$table` WHERE field='zoom'");
$query->execute();
if(!$query->rowCount()) try {
$database->exec("ALTER TABLE `$table` ADD zoom $schema[zoom] AFTER status");
$this->message("Added 'zoom' column to '$field->table'");
} catch(\Exception $e) {
$this->error($e->getMessage());
}
}
}
$field->set('schemaVersion', $schemaVersion);
$field->save();
}
/**
* Match values for PageFinder
*
* @param PageFinderDatabaseQuerySelect|DatabaseQuerySelect $query
* @param string $table
* @param string $subfield
* @param string $operator
* @param string $value
* @return DatabaseQuerySelect
*
*/
public function getMatchQuery($query, $table, $subfield, $operator, $value) {
if(!$subfield || $subfield == 'address') $subfield = 'data';
if($subfield != 'data' || $this->wire()->database->isOperator($operator)) {
// if dealing with something other than address, or operator is native to SQL,
// then let Fieldtype::getMatchQuery handle it instead
return parent::getMatchQuery($query, $table, $subfield, $operator, $value);
}
// if we get here, then we're performing either %= (LIKE and variations) or *= (FULLTEXT and variations)
$ft = new DatabaseQuerySelectFulltext($query);
$ft->match($table, $subfield, $operator, $value);
return $query;
}
/**
* Module configuration
*
* @param array $data
* @return InputfieldWrapper
*
*/
public static function getModuleConfigInputfields(array $data) {
$inputfields = new InputfieldWrapper();
if(wire()->config->demo) $data['googleApiKey'] = 'Not shown in demo mode';
/** @var InputfieldText $f */
$f = wire()->modules->get('InputfieldText');
$f->attr('name', 'googleApiKey');
$f->label = __('Google Maps API Key');
$f->icon = 'map';
$f->description = sprintf(__('[Click here](%s) for instructions from Google on how to obtain an API key.'),
'https://developers.google.com/maps/documentation/javascript/get-api-key');
$f->attr('value', isset($data['googleApiKey']) ? $data['googleApiKey'] : '');
$inputfields->add($f);
return $inputfields;
}
}

View file

@ -1,73 +0,0 @@
.InputfieldMapMarker input[type=number],
.InputfieldMapMarker input[type=text] {
width: 99.5%;
}
.InputfieldMapMarkerToggle span {
display: none;
}
.InputfieldMapMarkerAddress {
float: left;
width: 70%;
padding-right: 2%;
}
.InputfieldMapMarkerToggle {
float: left;
width: 28%;
}
.InputfieldMapMarkerLat,
.InputfieldMapMarkerLng {
width: 42%;
float: left;
padding-right: 2%;
}
.InputfieldMapMarkerZoom {
float: left;
width: 10%;
}
.InputfieldMapMarker .notes {
clear: both;
}
.InputfieldMapMarkerMap {
width: 100%;
height: 300px;
clear: left;
}
@media only screen and (min-width: 768px) {
.InputfieldMapMarkerAddress {
width: 38%;
padding-right: 1%;
}
.InputfieldMapMarkerToggle {
width: 2%;
padding-right: 0.5%;
position: relative;
}
.InputfieldMapMarkerToggle strong {
/* hide geocode label */
display: none;
}
.InputfieldMapMarkerLat,
.InputfieldMapMarkerLng {
width: 23%;
padding-right: 1%;
}
.InputfieldMapMarkerZoom {
float: left;
width: 9.5%;
}
}

View file

@ -1,130 +0,0 @@
/**
* Display a Google Map and pinpoint a location for InputfieldMapMarker
*
*/
var InputfieldMapMarker = {
options: {
zoom: 12, // mats, previously 5
draggable: true, // +mats
center: null,
//key: config.InputfieldMapMarker.googleApiKey,
mapTypeId: google.maps.MapTypeId.HYBRID,
scrollwheel: false,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
},
scaleControl: false
},
init: function(mapId, lat, lng, zoom, mapType) {
var options = InputfieldMapMarker.options;
if(zoom < 1) zoom = 12;
options.center = new google.maps.LatLng(lat, lng);
options.zoom = parseInt(zoom);
if(mapType == 'SATELLITE') options.mapTypeId = google.maps.MapTypeId.SATELLITE;
else if(mapType == 'ROADMAP') options.mapTypeId = google.maps.MapTypeId.ROADMAP;
var map = new google.maps.Map(document.getElementById(mapId), options);
var marker = new google.maps.Marker({
position: options.center,
map: map,
draggable: options.draggable
});
var $map = $('#' + mapId);
var $lat = $map.siblings(".InputfieldMapMarkerLat").find("input[type=text]");
var $lng = $map.siblings(".InputfieldMapMarkerLng").find("input[type=text]");
var $addr = $map.siblings(".InputfieldMapMarkerAddress").find("input[type=text]");
var $addrJS = $map.siblings(".InputfieldMapMarkerAddress").find("input[type=hidden]");
var $toggle = $map.siblings(".InputfieldMapMarkerToggle").find("input[type=checkbox]");
var $zoom = $map.siblings(".InputfieldMapMarkerZoom").find("input[type=number]");
var $notes = $map.siblings(".notes");
$lat.val(marker.getPosition().lat());
$lng.val(marker.getPosition().lng());
$zoom.val(map.getZoom());
google.maps.event.addListener(marker, 'dragend', function(event) {
var geocoder = new google.maps.Geocoder();
var position = this.getPosition();
$lat.val(position.lat());
$lng.val(position.lng());
if($toggle.is(":checked")) {
geocoder.geocode({ 'latLng': position }, function(results, status) {
if(status == google.maps.GeocoderStatus.OK && results[0]) {
$addr.val(results[0].formatted_address);
$addrJS.val($addr.val());
}
$notes.text(status);
});
}
});
google.maps.event.addListener(map, 'zoom_changed', function() {
$zoom.val(map.getZoom());
});
$addr.blur(function() {
if(!$toggle.is(":checked")) return true;
var geocoder = new google.maps.Geocoder();
geocoder.geocode({ 'address': $(this).val()}, function(results, status) {
if(status == google.maps.GeocoderStatus.OK && results[0]) {
var position = results[0].geometry.location;
map.setCenter(position);
marker.setPosition(position);
$lat.val(position.lat());
$lng.val(position.lng());
$addrJS.val($addr.val());
}
$notes.text(status);
});
return true;
});
$zoom.change(function() {
map.setZoom(parseInt($(this).val()));
});
$toggle.click(function() {
if($(this).is(":checked")) {
$notes.text('Geocode ON');
// google.maps.event.trigger(marker, 'dragend');
$addr.trigger('blur');
} else {
$notes.text('Geocode OFF');
}
return true;
});
// added by diogo to solve the problem of maps not rendering correctly in hidden elements
// trigger a resize on the map when either the tab button or the toggle field bar are pressed
// get the tab element where this map is integrated
var $map = $('#' + mapId);
var $tab = $('#_' + $map.closest('.InputfieldFieldsetTabOpen').attr('id'));
// get the inputfield where this map is integrated and add the tab to the stack
var $inputFields = $map.closest('.Inputfield').find('.InputfieldStateToggle').add($tab);
$inputFields.on('click',function(){
// give it time to open
window.setTimeout(function(){
google.maps.event.trigger(map,'resize');
map.setCenter(options.center);
}, 200);
});
}
};
$(document).ready(function() {
$(".InputfieldMapMarkerMap").each(function() {
var $t = $(this);
InputfieldMapMarker.init($t.attr('id'), $t.attr('data-lat'), $t.attr('data-lng'), $t.attr('data-zoom'), $t.attr('data-type'));
});
});

View file

@ -1,377 +0,0 @@
<?php namespace ProcessWire;
/**
* ProcessWire Map Marker Inputfield
*
* Provides the admin control panel inputs for FieldtypeMapMarker
*
* ProcessWire 3.x
* Copyright (C) 2023 by Ryan Cramer
* Licensed under MPL 2.0
*
* https://processwire.com
*
* @property string $defaultAddr
* @property int $defaultZoom
* @property string $defaultType
* @property string $defaultLat
* @property string $defaultLng
* @property int $height
* @property string $googleApiKey
*
*/
class InputfieldMapMarker extends Inputfield {
public static function getModuleInfo() {
return array(
'title' => 'Map Marker',
'version' => 300,
'summary' => "Provides input for the MapMarker Fieldtype",
'requires' => 'FieldtypeMapMarker',
'icon' => 'map-marker',
);
}
const defaultAddr = 'Castaway Cay';
/**
* Just in case this Inputfield is being used separately from FieldtypeMapmarker, we include the MapMarker class
*
*/
public function __construct() {
require_once(dirname(__FILE__) . '/MapMarker.php');
$this->set('defaultAddr', self::defaultAddr);
$this->set('defaultZoom', 12);
$this->set('defaultType', 'HYBRID');
$this->set('defaultLat', '');
$this->set('defaultLng', '');
$this->set('height', 300);
$this->set('googleApiKey', '');
parent::__construct();
}
/**
* Set an attribute to this Inputfield
*
* In this case, we just capture the 'value' attribute and make sure it's something valid
*
* @param string $key
* @param mixed $value
* @return $this
* @throws WireException
*
*/
public function setAttribute($key, $value) {
if($key == 'value' && !$value instanceof MapMarker && !is_null($value)) {
throw new WireException("This input only accepts a MapMarker for its value");
}
return parent::setAttribute($key, $value);
}
/**
* Is the value empty?
*
* @return bool
*
*/
public function isEmpty() {
return (!$this->value || ((float) $this->value->lat) === 0.0);
}
/**
* @return FieldtypeMapMarker
*
*/
public function fieldtype() {
/** @var FieldtypeMapMarker $fieldtype */
$fieldtype = $this->wire()->modules->get('FieldtypeMapMarker');
return $fieldtype;
}
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
/*
$url = 'https://maps.google.com/maps/api/js';
$key = $this->get('googleApiKey');
if($key) $url .= "?key=$key";
*/
$url = $this->fieldtype()->getGoogleMapsURL();
$this->wire()->config->scripts->add($url);
return parent::renderReady($parent, $renderValueMode);
}
/**
* Render the markup needed to draw the Inputfield
*
* @return string
*
*/
public function ___render() {
$sanitizer = $this->wire()->sanitizer;
$adminTheme = $this->wire()->adminTheme;
$name = $this->attr('name');
$id = $this->attr('id');
$marker = $this->attr('value');
if($marker->lat == 0.0) $marker->lat = $this->defaultLat;
if($marker->lng == 0.0) $marker->lng = $this->defaultLng;
if(!$marker->zoom) $marker->zoom = $this->defaultZoom;
$address = $sanitizer->entities($marker->address);
$toggleChecked = $marker->status != MapMarker::statusNoGeocode ? " checked='checked'" : '';
$status = $marker->status == MapMarker::statusNoGeocode ? 0 : $marker->status;
$mapType = $this->defaultType;
$height = $this->height ? (int) $this->height : 300;
$classes = array('input' => '', 'checkbox' => '');
if($adminTheme && method_exists($adminTheme, 'getClass')) {
foreach(array_keys($classes) as $key) {
$classes[$key] = $adminTheme->getClass($key);
}
}
$labels = array(
'addr' => $this->_('Address'),
'lat' => $this->_('Latitude'),
'lng' => $this->_('Longitude'),
'geo' => $this->_('Geocode?'),
'zoom' => $this->_('Zoom')
);
foreach($labels as $key => $label) {
$labels[$key] = $sanitizer->entities1($label);
}
$out = <<< _OUT
<span></span>
<p class='InputfieldMapMarkerAddress'>
<label>
<strong>$labels[addr]</strong>
<br />
<input type='text' id='{$id}' name='{$name}' value='{$address}' class='$classes[input]' /><br />
</label>
<input type='hidden' id='_{$name}_js_geocode_address' name='_{$name}_js_geocode_address' value='' />
</p>
<p class='InputfieldMapMarkerToggle'>
<label>
<br />
<input title='Geocode ON/OFF' type='checkbox' class='$classes[checkbox]' name='_{$name}_status' id='_{$name}_toggle' value='$status'$toggleChecked />
<strong>$labels[geo]</strong>
</label>
</p>
<p class='InputfieldMapMarkerLat'>
<label>
<strong>$labels[lat]</strong><br />
<input type='text' id='_{$id}_lat' name='_{$name}_lat' value='{$marker->lat}' class='$classes[input]' />
</label>
</p>
<p class='InputfieldMapMarkerLng'>
<label>
<strong>$labels[lng]</strong><br />
<input type='text' id='_{$id}_lng' name='_{$name}_lng' value='{$marker->lng}' class='$classes[input]' />
</label>
</p>
<p class='InputfieldMapMarkerZoom'>
<label>
<strong>$labels[zoom]</strong><br />
<input type='number' min='0' id='_{$id}_zoom' name='_{$name}_zoom' value='{$marker->zoom}' class='$classes[input]' />
</label>
</p>
_OUT;
$out .=
"<div class='InputfieldMapMarkerMap' " .
"id='_{$id}_map' " .
"style='height: {$height}px' " .
"data-lat='$marker->lat' " .
"data-lng='$marker->lng' " .
"data-zoom='$marker->zoom' " .
"data-type='$mapType'>" .
"</div>";
$this->notes = $marker->statusString;
if(!$this->get('googleApiKey')) {
$config = $this->wire()->config;
$msg = $sanitizer->entities1($this->_('Please setup a Google Maps API key in the FieldtypeMapMarker module settings'));
if($this->wire()->user->isSuperuser()) {
$link = "<a href='{$config->urls->admin}module/edit?name=FieldtypeMapMarker'>";
$msg = "$link$msg</a>";
$this->warning($msg, Notice::allowMarkup);
} else {
$this->warning($msg);
}
}
return $out;
}
/**
* Process the input after a form submission
*
* @param WireInputData $input
* @return $this
*
*/
public function ___processInput(WireInputData $input) {
$name = $this->attr('name');
$marker = $this->attr('value');
if(!isset($input->$name)) {
return $this;
}
if($input->$name == $this->defaultAddr) {
$marker->set('address', '');
} else {
$marker->set('address', $input->$name);
}
$lat = (float) $input["_{$name}_lat"];
$lng = (float) $input["_{$name}_lng"];
$defaultLat = (float) $this->defaultLat;
$defaultLng = (float) $this->defaultLng;
$precision = 4;
if( ((string) round($lat, $precision)) != ((string) round($defaultLat, $precision)) ||
((string) round($lng, $precision)) != ((string) round($defaultLng, $precision))) {
$marker->set('lat', $lat);
$marker->set('lng', $lng);
} else {
// $this->message("Kept lat/lng at unset value", Notice::debug);
}
$zoom = $input["_{$name}_zoom"];
if($zoom > -1 && $zoom < 30) $marker->zoom = (int) $zoom;
$status = $input["_{$name}_status"];
if(is_null($status)) {
$marker->set('status', MapMarker::statusNoGeocode); // disable geocode
} else {
$marker->set('status', (int) $status);
}
// if the address changed, then redo the geocoding.
// while we do this in the Fieldtype, we also do it here in case this Inputfield is used on it's own.
// the MapMarker class checks to make sure it doesn't do the same geocode twice.
if($marker->isChanged('address') && $marker->address && $marker->status != MapMarker::statusNoGeocode) {
// double check that the address wasn't already populated by the JS geocoder
// this prevents user-dragged markers that don't geocode to an exact location from getting
// unintentionally moved by the PHP-side geocoder
if($input["_{$name}_js_geocode_address"] == $marker->address) {
// prevent the geocoder from running in the fieldtype
$marker->skipGeocode = true;
$this->message('Skipping geocode (already done by JS geocoder)', Notice::debug);
} else {
$marker->geocode();
}
}
return $this;
}
/**
* @return InputfieldWrapper
*
*/
public function ___getConfigInputfields() {
$modules = $this->wire()->modules;
$inputfields = parent::___getConfigInputfields();
/** @var InputfieldText $field */
$field = $modules->get('InputfieldText');
$field->attr('name', 'defaultAddr');
$field->label = $this->_('Default Address');
$field->description = $this->_('This will be geocoded to become the starting point of the map.');
$field->attr('value', $this->defaultAddr);
$field->notes = $this->_('When modifying the default address, please make the Latitude and Longitude fields below blank, which will force the system to geocode your new address.');
$inputfields->add($field);
if(!$this->defaultLat && !$this->defaultLng) {
$m = new MapMarker();
$m->address = $this->defaultAddr;
$status = $m->geocode();
if($status > 0) {
$this->defaultLat = $m->lat;
$this->defaultLng = $m->lng;
$this->message($this->_('Geocoded your default address. Please hit save once again to commit the new default latitude and longitude.'));
}
}
/** @var InputfieldText $field */
$field = $modules->get('InputfieldText');
$field->attr('name', 'defaultLat');
$field->label = $this->_('Default Latitude');
$field->attr('value', $this->defaultLat);
$field->columnWidth = 50;
$inputfields->add($field);
/** @var InputfieldText $field */
$field = $modules->get('InputfieldText');
$field->attr('name', 'defaultLng');
$field->label = $this->_('Default Longitude');
$field->attr('value', $this->defaultLng);
$field->columnWidth = 50;
$inputfields->add($field);
/** @var InputfieldRadios $field */
$field = $modules->get('InputfieldRadios');
$field->attr('name', 'defaultType');
$field->label = $this->_('Default Map Type');
$field->addOption('HYBRID', $this->_('Hybrid'));
$field->addOption('ROADMAP', $this->_('Road Map'));
$field->addOption('SATELLITE', $this->_('Satellite'));
$field->attr('value', $this->defaultType);
$field->optionColumns = 1;
$field->columnWidth = 50;
$inputfields->add($field);
/** @var InputfieldInteger $field */
$field = $modules->get('InputfieldInteger');
$field->attr('name', 'height');
$field->label = $this->_('Map Height (in pixels)');
$field->attr('value', $this->height);
$field->attr('type', 'number');
$field->columnWidth = 50;
$inputfields->add($field);
/** @var InputfieldInteger $field */
$field = $modules->get('InputfieldInteger');
$field->attr('name', 'defaultZoom');
$field->label = $this->_('Default Zoom');
$field->description = $this->_('Enter a value between 1 and 23. The highest zoom level is typically somewhere between 19-23, depending on the location being zoomed and how much data Google has for the location.'); // Zoom level description
$field->attr('value', $this->defaultZoom);
$field->attr('type', 'number');
$inputfields->add($field);
/** @var InputfieldMarkup $field */
$field = $modules->get('InputfieldMarkup');
$field->label = $this->_('API Notes');
$field->description = $this->_('You can access individual values from this field using the following from your template files:');
$field->value =
"<pre>" .
"\$page->{$this->name}->address\n" .
"\$page->{$this->name}->lat\n" .
"\$page->{$this->name}->lng\n" .
"\$page->{$this->name}->zoom" .
"</pre>";
$inputfields->add($field);
return $inputfields;
}
}

View file

@ -1,126 +0,0 @@
<?php namespace ProcessWire;
/**
* Class to hold an address and geocode it to latitude/longitude
*
* @property string $lat
* @property string $lng
* @property string $address
* @property int $status
* @property int $zoom
* @property bool $skipGeocode
* @property string $statusString
*
*/
class MapMarker extends WireData {
const statusNoGeocode = -100;
protected $geocodeStatuses = array(
0 => 'N/A',
1 => 'OK',
2 => 'OK_ROOFTOP',
3 => 'OK_RANGE_INTERPOLATED',
4 => 'OK_GEOMETRIC_CENTER',
5 => 'OK_APPROXIMATE',
-1 => 'UNKNOWN',
-2 => 'ZERO_RESULTS',
-3 => 'OVER_QUERY_LIMIT',
-4 => 'REQUEST_DENIED',
-5 => 'INVALID_REQUEST',
-100 => 'Geocode OFF', // RCD
);
protected $geocodedAddress = '';
public function __construct() {
parent::__construct();
$this->set('lat', '');
$this->set('lng', '');
$this->set('address', '');
$this->set('status', 0);
$this->set('zoom', 0);
// temporary runtime property to indicate the geocode should be skipped
$this->set('skipGeocode', false);
$this->set('statusString', '');
}
public function set($key, $value) {
if($key == 'lat' || $key == 'lng') {
// if value isn't numeric, then it's not valid: make it blank
if(strpos($value, ',') !== false) $value = str_replace(',', '.', $value); // convert 123,456 to 123.456
if(!is_numeric($value)) $value = '';
} else if($key == 'address') {
$value = $this->wire()->sanitizer->text($value);
} else if($key == 'status') {
$value = (int) $value;
if(!isset($this->geocodeStatuses[$value])) $value = -1; // -1 = unknown
} else if($key == 'zoom') {
$value = (int) $value;
}
return parent::set($key, $value);
}
public function get($key) {
if($key == 'statusString') return str_replace('_', ' ', $this->geocodeStatuses[$this->status]);
return parent::get($key);
}
public function geocode() {
if($this->skipGeocode) return -100;
// check if address was already geocoded
if($this->geocodedAddress == $this->address) return $this->status;
$this->geocodedAddress = $this->address;
$url = "https://maps.googleapis.com/maps/api/geocode/json?address=" . urlencode($this->address);
/** @var FieldtypeMapMarker $fieldtype */
$fieldtype = $this->modules->get('FieldtypeMapMarker');
$apiKey = $fieldtype->get('googleApiKey');
if($apiKey) $url .= "&key=$apiKey";
$http = new WireHttp();
$json = $http->getJSON($url, true);
if(empty($json['status'])) $json['status'] = 'No response';
if($json['status'] != 'OK') {
$this->error("Error geocoding address: $json[status] ($url)");
if(isset($json['status'])) $this->status = (int) array_search($json['status'], $this->geocodeStatuses);
else $this->status = -1;
$this->lat = 0;
$this->lng = 0;
return $this->status;
}
$geometry = $json['results'][0]['geometry'];
$location = $geometry['location'];
$locationType = $geometry['location_type'];
$this->lat = $location['lat'];
$this->lng = $location['lng'];
$statusString = $json['status'] . '_' . $locationType;
$status = array_search($statusString, $this->geocodeStatuses);
if($status === false) $status = 1; // OK
$this->status = $status;
$this->message("Geocode {$this->statusString}: '{$this->address}'");
return $this->status;
}
/**
* If accessed as a string, then just output the lat, lng coordinates
*
*/
public function __toString() {
return "$this->address ($this->lat, $this->lng, $this->zoom) [$this->statusString]";
}
}

View file

@ -1,213 +0,0 @@
/**
* ProcessWire Map Markup (JS)
*
* Renders maps for the FieldtypeMapMarker module
*
* ProcessWire 3.x
* Copyright (C) 2023 by Ryan Cramer
* Licensed under MPL 2.0
*
* http://processwire.com
*
* Javascript Usage:
* =================
* var map = new MarkupGoogleMap();
* map.setOption('any-google-maps-option', 'value');
* map.setOption('zoom', 12); // example
*
* // init(container ID, latitude, longitude):
* map.init('#map-div', 26.0936823, -77.5332796);
*
* // addMarker(latitude, longitude, optional URL, optional URL to icon file):
* map.addMarker(26.0936823, -77.5332796, 'en.wikipedia.org/wiki/Castaway_Cay', '');
* map.addMarker(...you may have as many of these as you want...);
*
* // optionally fit the map to the bounds of the markers you added
* map.fitToMarkers();
*
*/
function MarkupGoogleMap() {
this.map = null;
this.markers = [];
this.numMarkers = 0;
this.icon = '';
this.iconHover = '';
this.shadow = '';
this.hoverBox = null;
this.hoverBoxOffsetTop = 0;
this.hoverBoxOffsetLeft = 0;
this.options = {
zoom: 10,
center: null,
mapTypeId: google.maps.MapTypeId.ROADMAP,
scrollwheel: false,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
},
scaleControl: false,
// disable points of interest
styles: [{
featureType: "poi",
stylers: [ { visibility: "off" } ]
}]
};
this._currentURL = '';
this.init = function(mapID, lat, lng) {
if(lat != 0) this.options.center = new google.maps.LatLng(lat, lng);
this.map = new google.maps.Map(document.getElementById(mapID), this.options);
}
this.setOption = function(key, value) {
this.options[key] = value;
}
this.setIcon = function(url) {
this.icon = url;
}
this.setIconHover = function(url) {
this.iconHover = url;
}
this.setShadow = function(url) {
this.shadow = url;
}
this.setHoverBox = function(markup) {
if(!markup.length) {
this.hoverBox = null;
return;
}
this.hoverBox = $(markup);
var $hoverBox = this.hoverBox;
this.hoverBoxOffsetTop = parseInt($hoverBox.attr('data-top'));
this.hoverBoxOffsetLeft = parseInt($hoverBox.attr('data-left'));
$("body").append($hoverBox);
// keep it hidden/out of the way until needed
$hoverBox.css({
position: 'absolute',
left: 0,
top: '-100px'
});
$hoverBox.mouseout(function() {
$hoverBox.hide();
}).click(function() {
if(this._currentURL.length > 0) window.location.href = this._currentURL;
});
}
this.addMarker = function(lat, lng, url, title, icon, shadow) {
if(lat == 0.0) return;
var latLng = new google.maps.LatLng(lat, lng);
var zIndex = 99999 + this.numMarkers;
var markerOptions = {
position: latLng,
map: this.map,
linkURL: '',
zIndex: zIndex
};
if(typeof icon !== "undefined" && icon.length > 0) markerOptions.icon = icon;
else if(this.icon) markerOptions.icon = this.icon;
// console.log(markerOptions);
if(typeof shadow !== "undefined" && shadow.length > 0) markerOptions.shadow = shadow;
else if(this.shadow.length > 0) markerOptions.shadow = this.shadow;
var marker = new google.maps.Marker(markerOptions);
if(url.length > 0) marker.linkURL = url;
if(this.hoverBox) marker.hoverBoxTitle = title;
else marker.setTitle(title);
this.markers[this.numMarkers] = marker;
this.numMarkers++;
if(marker.linkURL.length > 0) {
google.maps.event.addListener(marker, 'click', function(e) {
window.location.href = marker.linkURL;
});
}
if(markerOptions.icon !== "undefined" && this.iconHover) {
var iconHover = this.iconHover;
google.maps.event.addListener(marker, 'mouseover', function(e) {
marker.setIcon(iconHover);
});
google.maps.event.addListener(marker, 'mouseout', function(e) {
marker.setIcon(markerOptions.icon);
});
}
if(this.hoverBox) {
var $hoverBox = this.hoverBox;
var offsetTop = this.hoverBoxOffsetTop;
var offsetLeft = this.hoverBoxOffsetLeft;
var mouseMove = function(e) {
$hoverBox.css({
'top': e.pageY + offsetTop,
'left': e.pageX + offsetLeft
});
};
// console.log($hoverBox);
google.maps.event.addListener(marker, 'mouseover', function(e) {
this._currentURL = url;
$hoverBox.html("<span>" + marker.hoverBoxTitle + "</span>")
.css('top', '0px')
.css('left', '0px')
.css('display', 'block')
.css('width', 'auto')
.css('z-index', 9999);
$hoverBox.show();
$(document).mousemove(mouseMove);
});
google.maps.event.addListener(marker, 'mouseout', function(e) {
$hoverBox.hide();
$(document).unbind("mousemove", mouseMove);
});
}
}
this.fitToMarkers = function() {
var bounds = new google.maps.LatLngBounds();
var map = this.map;
for(var i = 0; i < this.numMarkers; i++) {
var latLng = this.markers[i].position;
bounds.extend(latLng);
}
map.fitBounds(bounds);
var listener = google.maps.event.addListener(map, "idle", function() {
if(map.getZoom() < 2) map.setZoom(2);
google.maps.event.removeListener(listener);
});
}
}

View file

@ -1,255 +0,0 @@
<?php namespace ProcessWire;
/**
* ProcessWire Map Markup
*
* Renders maps for the FieldtypeMapMarker module
*
* ProcessWire 3.x
* Copyright (C) 2023 by Ryan Cramer
* Licensed under MPL 2.0
*
* https://processwire.com
*
* USAGE:
* ======
*
* Add this somewhere before your closing </head> tag:
*
* <script type='text/javascript' src='<?=$modules->FieldtypeMapMarker->getGoogleMapsURL();?>'></script>
*
* In the location where you want to output your map, do the following in your template file:
*
* $map = $modules->get('MarkupGoogleMap');
* echo $map->render($page, 'map'); // replace 'map' with the name of your FieldtypeMap field
*
* To render a map with multiple markers on it, specify a PageArray rather than a single $page:
*
* $items = $pages->find("template=something, map!='', sort=title");
* $map = $modules->get('MarkupGoogleMap');
* echo $map->render($items, 'map');
*
* To specify options, provide a 3rd argument with an options array:
*
* $map = $modules->get('MarkupGoogleMap');
* echo $map->render($items, 'map', array('height' => '500px'));
*
*
* OPTIONS
* =======
* Here is a list of all possible options (with defaults shown):
*
* // default width of the map
* 'width' => '100%'
*
* // default height of the map
* 'height' => '300px'
*
* // zoom level
* 'zoom' => 12 (or $field->defaultZoom)
*
* // map type: ROADMAP, HYBRID or SATELLITE
* 'type' => 'HYBRID' or $field->defaultType
*
* // map ID attribute
* 'id' => "mgmap"
*
* // map class attribute
* 'class' => "MarkupGoogleMap"
*
* // center latitude
* 'lat' => $field->defaultLat
*
* // center longitude
* 'lng' => $field->defaultLng
*
* // set to false only if you will style the map <div> yourself
* 'useStyles' => true
*
* // allows single-marker map to use marker settings rather than map settings
* 'useMarkerSettings' => true
*
* // field to use for the marker link, or blank to not link
* 'markerLinkField' => 'url'
*
* // field to use for the marker title
* 'markerTitleField' => 'title'
*
* // map will automatically adjust to fit to the given markers (when multiple markers)
* 'fitToMarkers' => true
*
* // use hover box? When true, shows a tooltip-type box when you hover the marker, populated with the markerTitleField
* // this is often more useful than the default presentation google maps uses
* 'useHoverBox' => false
*
* // when useHoverBox is true, you can specify the markup used for it. Use the following (which is the default) as your starting point:
* 'hoverBoxMarkup' => "<div data-top='-10' data-left='15' style='background: #000; color: #fff; padding: 0.25em 0.5em; border-radius: 3px;'></div>",
*
* // FUll URL to icon file to use for markers. Blank=use default Google marker icon.
* 'icon' => '',
*
* // Any extra javascript initialization code you want to occur before the map itself is drawn
* 'init' => '',
*
*/
class MarkupGoogleMap extends WireData implements Module {
public static function getModuleInfo() {
return array(
'title' => 'Map Markup (Google Maps)',
'version' => 300,
'summary' => 'Renders Google Maps for the MapMarker Fieldtype',
'requires' => 'FieldtypeMapMarker',
);
}
/**
* Include our MapMarker class, which serves as the value for fields of type FieldtypeMapMarker
*
*/
public function init() {
require_once(dirname(__FILE__) . '/MapMarker.php');
}
/**
* Get associative array of map options
*
* @param string $fieldName
* @return array
* @throws WireException
*
*/
public function getOptions($fieldName) {
static $n = 0;
$field = $this->wire()->fields->get($fieldName);
if(!$field) throw new WireException("Unknown field: $fieldName");
return array(
'useStyles' => true,
'fitToMarkers' => true,
'useMarkerSettings' => true,
'useHoverBox' => false,
'hoverBoxMarkup' => "<div data-top='-10' data-left='15' style='background: #000; color: #fff; padding: 0.25em 0.5em; border-radius: 3px;'></div>",
'markerLinkField' => 'url',
'markerTitleField' => 'title',
'width' => '100%',
'height' => $field->get('height'),
'zoom' => $field->get('defaultZoom') ? (int) $field->get('defaultZoom') : 12,
'type' => $field->get('defaultType') ? $field->get('defaultType') : 'HYBRID',
'id' => "mgmap" . (++$n),
'class' => "MarkupGoogleMap",
'lat' => $field->get('defaultLat'),
'lng' => $field->get('defaultLng'),
'icon' => '', // url to icon (blank=use default)
'iconHover' => '', // url to icon when hovered (default=none)
'shadow' => '', // url to icon shadow (blank=use default)
'init' => '', // additional javascript code to insert in map initialization
'n' => $n,
);
}
/**
* Get the script tag for loading Google Maps
*
* @return string
* @throws WireException
*
*/
public function getGMapScript() {
$url = 'https://maps.google.com/maps/api/js';
$key = $this->wire()->modules->get('FieldtypeMapMarker')->get('googleApiKey');
if($key) $url .= "?key=$key";
return "<script type='text/javascript' src='$url'></script>";
}
/**
* Render map markup
*
* @param PageArray|Page $pageArray Page (or multiple pages in PageArray) containing map field
* @param string $fieldName Name of the map field
* @param array $options Options to adjust behavior
* @return string
* @throws WireException
*
*/
public function render($pageArray, $fieldName, array $options = array()) {
$config = $this->wire()->config;
static $n = 0;
$n++;
$defaultOptions = $this->getOptions($fieldName);
$options = array_merge($defaultOptions, $options);
if($pageArray instanceof Page) {
$page = $pageArray;
$pageArray = new PageArray();
$pageArray->add($page);
}
$height = $options['height'];
$width = $options['width'];
if(empty($height)) $height = 300;
if(ctype_digit("$height")) $height .= "px";
if(ctype_digit("$width")) $width .= "px";
$style = '';
if($options['useStyles'] && !empty($height) && !empty($width)) {
$style = " style='width: $width; height: $height;'";
}
$lat = $options['lat'];
$lng = $options['lng'];
$zoom = $options['zoom'] > 0 ? (int) $options['zoom'] : $defaultOptions['zoom'];
$type = in_array($options['type'], array('ROADMAP', 'SATELLITE', 'HYBRID')) ? $options['type'] : 'HYBRID';
if($options['useMarkerSettings'] && (count($pageArray) == 1 || !$lat)) {
// single marker overrides lat, lng and zoom settings
$marker = $pageArray->first()->get($fieldName);
$lat = $marker->lat;
$lng = $marker->lng;
if($marker->zoom > 0) $zoom = (int) $marker->zoom;
}
$id = $options['id'];
$out = '';
if($n === 1) {
$url = $config->urls('MarkupGoogleMap');
$out .= "<script type='text/javascript' src='{$url}MarkupGoogleMap.js'></script>";
}
$out .= "<div id='$id' class='$options[class]'$style></div>";
$out .=
"<script type='text/javascript'>" .
"if(typeof google === 'undefined' || typeof google.maps === 'undefined') { " .
"alert('MarkupGoogleMap Error: Please add the maps.google.com script in your document head.'); " .
"} else { " .
"var $id = new MarkupGoogleMap(); " .
"$id.setOption('zoom', $zoom); " .
"$id.setOption('mapTypeId', google.maps.MapTypeId.$type); " .
($options['icon'] ? "$id.setIcon('$options[icon]'); " : "") .
($options['iconHover'] ? "$id.setIconHover('$options[iconHover]'); " : "") .
($options['shadow'] ? "$id.setShadow('$options[shadow]'); " : "") .
($options['useHoverBox'] ? "$id.setHoverBox('" . str_replace("'", '"', $options['hoverBoxMarkup']) . "');" : "") .
$options['init'] .
"$id.init('$id', $lat, $lng); ";
foreach($pageArray as $page) {
$marker = $page->get($fieldName);
if(!$marker instanceof MapMarker) continue;
if(!$marker->lat) continue;
$url = $options['markerLinkField'] ? $page->get($options['markerLinkField']) : '';
$title = $options['markerTitleField'] ? $page->get($options['markerTitleField']) : '';
$out .= "$id.addMarker($marker->lat, $marker->lng, '$url', '$title', ''); ";
}
if(count($pageArray) > 1 && $options['fitToMarkers']) $out .= "$id.fitToMarkers(); ";
$out .= "}</script>";
return $out;
}
}

View file

@ -1,146 +0,0 @@
# FieldtypeMapMarker Module for ProcessWire
This Fieldtype for ProcessWire holds an address or location name, and automatically
geocodes the address to latitude/longitude using Google Maps API. The resulting
values may be used to populate any kind of map (whether Google Maps or another).
This Fieldtype was created to serve as an example of creating a custom Fieldtype and
Inputfield that contains multiple pieces of data. Though the Fieldtype has now gone
far beyond that and is relatively full featured. As a result, it may no longer be
the simplest example of how to implement a Fieldtype/Inputfield, though it is very
effective and useful.
MapMarker also has a corresponding Inputfield and Markup module, named
InputfieldMapMarker and MarkupGoogleMap. When you install FieldtypeMapMarker, the
Inputfield will also be installed and used for input on the admin side. Installation
of MarkupGoogleMap is optional. It provides a simple way to render Google maps with
the data managed by FieldtypeMapMarker.
This Fieldtype has a [support forum](http://processwire.com/talk/index.php/topic,752.0.html)
## Using Map Marker
### How to install
1. Copy all of the files for this module into /site/modules/FieldtypeMapMarker/
2. In your admin, go to the Modules screen and "check for new modules." Click *install*
for the Map Marker Fieldtype.
3. In your admin, go to Setup > Fields > Add New Field. Choose MapMarker as the type.
If you are not sure what to name your field, simply "map" is a good one! Once created,
configure the settings on the *input* tab.
4. Add your new "map" field to one or more templates, as you would any other field.
### How to use from the page editor
1. Create or edit a page using one of the templates you added the "map" field to.
2. Type in a location or address into the "address" box for the map field. Then click
outside of the address, and the Javascript geocoder should automatically populate the
latitude, longitude and map location. The Google geocoder will accept full addresses
or known location names. For instance, you could type in "Disney Land" and it knows
how to find locations like that.
3. The geocoding also works in reverse. You may drag the map marker wherever you want
and it will populate the address field for you. You may also populate the latitude,
longitude and zoom fields manually if you like. Unchecking the box between address
and latitude disables the geocoder.
### How to use from the API, in your template files
In your template files, you can utilize this data for your own Google Maps (or anything
else that you might need latitude/longitude for).
Lets assume that your field is called 'map'. Here is how you would access the
components of it from the API:
```php
echo $page->map->address; // outputs the address you entered
echo $page->map->lat; // outputs the latitude
echo $page->map->lng; // outputs the longitude
echo $page->map->zoom; // outputs the zoom level
```
-------------
## Markup Google Map
This package also comes with a module called MarkupGoogleMap. It provides a simple means
of outputting a Google Map based on the data managed by FieldtypeMapMarker. To install,
simply click "install" for the Google Maps (Markup) module. This is a Markup module,
meaning it exists primarily to generate markup for output on the front-end of your site.
### How to use
Add this somewhere before your closing `</head>` tag:
```html
<script type='text/javascript' src='https://maps.googleapis.com/maps/api/js?sensor=false'></script>
```
In the location where you want to output your map, place the following in your template file:
```php
$map = $modules->get('MarkupGoogleMap');
echo $map->render($page, 'map');
```
In the above, $page is the Page object that has the 'map' field. Replace 'map' with the name of
your map field.
To render a map with multiple markers on it, specify a PageArray rather than a single $page:
```php
$items = $pages->find("template=something, map!='', sort=title");
$map = $modules->get('MarkupGoogleMap');
echo $map->render($items, 'map');
````
To specify options, provide a 3rd argument with an options array:
```php
$map = $modules->get('MarkupGoogleMap');
echo $map->render($items, 'map', array('height' => '500px'));
```
### Options
Here is a list of all possible options (with defaults shown):
`width`
Width of the map (type: string; default: 100%).
`height`
Height of the map (type: string; default: 300px)
`zoom`
Zoom level 1-25 (type: integer; default: from your field settings)
`type`
Map type: ROADMAP, HYBRID or SATELLITE (type: string; default: from your field settings)
`id`
Map ID attribute (type: string; default: mgmap)
`class`
Map class attribute (type: string; default: MarkupGoogleMap)
`lat`
Map center latitude (type: string|float; default: from your field settings)
`lng`
Map center longitude (type: string|float; default: from your field settings)
`useStyles`
Whether to populate inline styles to the map div for width/height (type: boolean; default: true).
Set to false only if you will style the map div yourself.
`useMarkerSettings`
Makes single-marker map use marker settings rather than map settings (type: boolean; default: true).
`markerLinkField`
Page field to use for the marker link, or blank to not link (type: string; default: url).
`markerTitleField`
Page field to use for the marker title, or blank not to use a marker title (type: string; default: title).
`fitToMarkers`
When multiple markers are present, set map to automatically adjust to fit to the given markers (type: boolean; default: true).
---------