- Registriert
- März 2001
- Beiträge
- 17.205
November 2005: Anleitung angepasst an vBulletin 3.5
Da im Feedback und per E-Mail Fragen eingetroffen sind wie wir die Mitgliederkarte verwirklicht haben, wollen wir hier einige Infos geben. Dies ist jedoch keinesfalls eine komplette Anleitung! Wir haben den Membermap-Hack von vBulletin-Germany erweitert und an unsere Bedürfnisse angepasst und auch nur um diese Anpassungen soll es hier gehen.
Der in unseren Auge zentrale Schwachpunkt dieses Hacks ist, dass die Kartengrafik bei jedem Seitenaufruf neu erstellt wird. Dies kann bei großen Boards (viele Kartenaufrufe und Einträge) zu Performance-Problemen führen. Daher generieren wir die Mitgliederkarte alle paar Minuten per Cronjob neu, aber dazu später mehr.
Als zweite Sache ist uns aufgefallen, dass die Zuordnung der Postleitzahlen zu einem Ort eher suboptimal ist. Die Postleitzahlen werden in der geodb_locations-Tabelle durch Kommas getrennt in der Spalte plz vom Typ text gespeichert und dann folgende Query zum Ermitteln eines Ortes eingesetzt:
Diese Query kann MySQL aufgrund der Platzhalter nicht durch Verwendung eines Indizes optimieren und wird deshalb zum zweiten Performance-Problem.
Wir haben uns daher für ein anderes Datenbank-Layout entschieden, das zudem nur die Daten beinhaltet, die auch für die Mitgliederkarte verwendet werden:
Die Tabelle geo_postcodes nimmt also zu Zuordnung von Land+PLZ zu einem Ort vor, dessen Name sowie Breiten- und Längenangaben dann in geo_locations zu finden sind (Verknüpfung erfolgt über die Spalte locationid).
Zunächst braucht man jedoch logischerweise die Daten aus der OpenGeoDB. Wir haben uns die Datei opengeodb-0.2.3b-mysql-0.1.3-layout.zip heruntergeladen und entpackt. Zunächst haben wir in der Datei 01_create_tables.sql jedoch alle Vorkommnisse von " TYPE=InnoDB CHARACTER SET utf8" entfernt, da wir vorerst lieber ISO-8859-x als Zeichensatz anstatt UTF-8 und MyISAM anstatt InnoDB als MySQL-Table-Handler verwenden wollen. Dann haben wir die Datenbankstruktur importiert:
Danach steht man vor dem Problem, dass sämtliche Ortsnamen etc in den die eigentlichen Daten beinhaltenden Dateien wie DE.sql und AT.sql ebenfalls im Zeichensatz UTF-8 vorliegen. Daher haben wir diese zunächst mit Hilfe des Tools iconv konvertiert, z.B. für die Datei mit den deutschen Orten:
Danach kann man diese Daten in MySQL importieren.
Nun hat man die OpenGeoDB-Datenbank in MySQL-Tabellen vom Typ MyISAM und mit dem Zeichensatz ISO-8859-1. Wie eingangs erwähnt wollen wir jedoch ein für die Membermap optimiertes Datenbank-Layout verwenden. Zum Konvertieren der Daten haben wir folgendes Script verwendet:
Jetzt hat man das optimierte Datenbank-Layout.
Dann braucht man ein paar Felder in der Tabelle vb_userfield, in der die PLZ etc eines jeden Benutzers gespeichert wird. Diese Felder könnte man über das AdminCP einrichten, wir haben uns jedoch dafür entschieden sie manuell einzurichten, da wir im Profil nach Angabe von Land+PLZ die Wahl eines exakten Ortes ermöglichen wollen und man dazu ein Dropdown-Menü dynamisch mit Inhalt füllen muss, was vBulletin von sich aus soweit wir wissen nicht kann:
cb_postcode ist vom Typ Varchar anstatt Integer, um Probleme mit Postleitzahlen, deren erste Ziffer eine Null ist, zu umgehen. Das Prefix "cb_" dient nur der Markierung, damit wir irgendwann noch wissen, welche Felder von uns stammen und welche von vBulletin. =)
Damit man im Profil Angaben zum Wohnort machen kann haben wir die vBulletin-Datei profile.php editiert. Nach folgender Zeile
haben wir folgendes eingefügt:
Weiter unten in der profile.php, nach den folgenden Zeilen
muss noch der Code zum Speichern dieser Angaben hinzugefügt werden:
Zudem muss noch das vBulletin-Template "modifyprofile" angepasst werden. Folgenden Code haben wir vor dem letzten Table-Tag in diesem Template eingefügt:
Nun sollte man in der Lage sein, in seinem Profil Land+PLZ einzugeben. Zudem sollte man daraufhin alle in diesem Postleitzahlbereich liegenden Orte per Dropdown-Menü auswählen können.
Das Script, das die Membermap alle paar Minuten generiert (und dafür z.B. per Cronjob alle paar Minuten ausgeführt werden muss!), sieht wie folgt aus (am Anfang müssen ein paar Konfigurationsvariablen gesetzt werden!):
Die Datei membermap.php, die für die Ausgabe der Karte zuständig ist und die man im vBulletin-Verzeichnis bei den anderen PHP-Dateien ablegt, sieht wie folgt aus (Wert von $mapdatafile gegen Ende anpassen!):
Nun fehlt nur noch das Template "membermaphome", das man im AdminCP von vBulletin hinzufügt:
Die darin erwähnten Dateien cb_membermap.css und cb_membermap.js (unsere Alternative zur overlib.js des Original MapHacks, funktioniert wahrscheinlich mit weniger Browsern, ist jedoch kleiner und hat einen netten Fade-In-Effekt) dürft ihr auf euren Server kopieren und modifizieren, vorausgesetzt dass der Hinweis am Anfang der Dateien erhalten bleibt!
Ich denke das war alles. Wie eingangs erwähnt ist dies keine eigenständige Anleitung, Grundvoraussetzung ist das Verständnis des verlinkten Original MapHack!
Da im Feedback und per E-Mail Fragen eingetroffen sind wie wir die Mitgliederkarte verwirklicht haben, wollen wir hier einige Infos geben. Dies ist jedoch keinesfalls eine komplette Anleitung! Wir haben den Membermap-Hack von vBulletin-Germany erweitert und an unsere Bedürfnisse angepasst und auch nur um diese Anpassungen soll es hier gehen.
Der in unseren Auge zentrale Schwachpunkt dieses Hacks ist, dass die Kartengrafik bei jedem Seitenaufruf neu erstellt wird. Dies kann bei großen Boards (viele Kartenaufrufe und Einträge) zu Performance-Problemen führen. Daher generieren wir die Mitgliederkarte alle paar Minuten per Cronjob neu, aber dazu später mehr.
Als zweite Sache ist uns aufgefallen, dass die Zuordnung der Postleitzahlen zu einem Ort eher suboptimal ist. Die Postleitzahlen werden in der geodb_locations-Tabelle durch Kommas getrennt in der Spalte plz vom Typ text gespeichert und dann folgende Query zum Ermitteln eines Ortes eingesetzt:
Code:
$sql = 'SELECT * FROM geodb_locations WHERE plz LIKE "%'.$plzf.'%" AND adm0="'.$land.'"';
Wir haben uns daher für ein anderes Datenbank-Layout entschieden, das zudem nur die Daten beinhaltet, die auch für die Mitgliederkarte verwendet werden:
Code:
CREATE TABLE `geo_locations` (
`locationid` int(10) unsigned NOT NULL auto_increment,
`name` varchar(255) NOT NULL default '',
`latitude` float NOT NULL default '0',
`longitude` float NOT NULL default '0',
PRIMARY KEY (`locationid`)
) TYPE=MyISAM;
CREATE TABLE `geo_postcodes` (
`postcode` mediumint(10) unsigned NOT NULL default '0',
`country` enum('DE','AT','CH','FL') NOT NULL default 'DE',
`locationid` int(10) unsigned NOT NULL default '0',
KEY `postcode` (`postcode`,`country`)
) TYPE=MyISAM;
Zunächst braucht man jedoch logischerweise die Daten aus der OpenGeoDB. Wir haben uns die Datei opengeodb-0.2.3b-mysql-0.1.3-layout.zip heruntergeladen und entpackt. Zunächst haben wir in der Datei 01_create_tables.sql jedoch alle Vorkommnisse von " TYPE=InnoDB CHARACTER SET utf8" entfernt, da wir vorerst lieber ISO-8859-x als Zeichensatz anstatt UTF-8 und MyISAM anstatt InnoDB als MySQL-Table-Handler verwenden wollen. Dann haben wir die Datenbankstruktur importiert:
Code:
mysql DATENBANKNAME<01_create_tables.sql -u BENUTZERNAME -p
Code:
iconv -f utf-8 -t iso-8859-1 ./DE.sql > DE_iso8859-1.sql
Code:
mysql DATENBANKNAME<DE_iso8859-1.sql -u BENUTZERNAME -p
Nun hat man die OpenGeoDB-Datenbank in MySQL-Tabellen vom Typ MyISAM und mit dem Zeichensatz ISO-8859-1. Wie eingangs erwähnt wollen wir jedoch ein für die Membermap optimiertes Datenbank-Layout verwenden. Zum Konvertieren der Daten haben wir folgendes Script verwendet:
PHP:
#!/usr/bin/php
<?php
mysql_connect('__SERVER__', '__BENUTZER__', '__PASSWORT__');
mysql_select_db('__DATENBANK__');
/*
// Konfiguration
*/
$countries = array('DE', 'AT', 'CH', 'FL');
/*
// Konvertierung
*/
mysql_query("TRUNCATE TABLE geo_locations") or die(mysql_error());
mysql_query("TRUNCATE TABLE geo_postcodes") or die(mysql_error());
$sql = "SELECT
name AS location,
adm0 AS country,
breite AS latitude,
laenge AS longitude,
plz AS postcodes
FROM geodb_locations
WHERE adm0 IN ('" . implode("','", $countries) . "')";
$result = mysql_query($sql) or die(mysql_error());
while ($row = mysql_fetch_assoc($result)) {
if ($row['postcodes'] == '?') {
continue;
}
$sql = "INSERT INTO geo_locations SET
name='" . addslashes($row['location']) . "',
latitude='" . addslashes($row['latitude']) . "',
longitude='" . addslashes($row['longitude']) . "'";
mysql_query($sql) or die(mysql_error());
$locationid = mysql_insert_id();
$postcodes = explode(',', $row['postcodes']);
foreach ($postcodes as $postcode) {
$sql = "INSERT INTO geo_postcodes SET
postcode='" . addslashes($postcode) . "',
country='" . addslashes($row['country']) . "',
locationid='" . addslashes($locationid) . "'";
mysql_query($sql) or die(mysql_error());
}
}
?>
Dann braucht man ein paar Felder in der Tabelle vb_userfield, in der die PLZ etc eines jeden Benutzers gespeichert wird. Diese Felder könnte man über das AdminCP einrichten, wir haben uns jedoch dafür entschieden sie manuell einzurichten, da wir im Profil nach Angabe von Land+PLZ die Wahl eines exakten Ortes ermöglichen wollen und man dazu ein Dropdown-Menü dynamisch mit Inhalt füllen muss, was vBulletin von sich aus soweit wir wissen nicht kann:
Code:
ALTER TABLE `vb_userfield` ADD `cb_postcode` VARCHAR( 5 ) NOT NULL ,
ADD `cb_country` CHAR( 2 ) NOT NULL ,
ADD `cb_locationid` INT UNSIGNED NOT NULL ;
Damit man im Profil Angaben zum Wohnort machen kann haben wir die vBulletin-Datei profile.php editiert. Nach folgender Zeile
PHP:
fetch_profilefields(0);
PHP:
// ############################### Mitgliederkarte ###############################
$cb_postcode = $vbulletin->userinfo['cb_postcode'];
$cb_countries = array(
'' => '',
'DE' => 'Deutschland',
'FL' => 'Liechtenstein',
'AT' => 'Österreich',
'CH' => 'Schweiz'
);
$cb_countrydropdown = '';
foreach ($cb_countries as $cb_countryshort => $cb_country) {
if ($cb_countryshort == $vbulletin->userinfo['cb_country']) {
$cb_countrydropdown .= "<option value=\"$cb_countryshort\" selected=\"selected\">$cb_country</option>";
} else {
$cb_countrydropdown .= "<option value=\"$cb_countryshort\">$cb_country</option>";
}
}
$sql = 'SELECT
geo_locations.locationid,
geo_locations.name
FROM geo_postcodes
LEFT JOIN geo_locations USING(locationid)
WHERE geo_postcodes.postcode=' . intval($vbulletin->userinfo['cb_postcode']);
$result = $db->query_read($sql);
$cb_citydropdown = '';
while ($row = $db->fetch_array($result)) {
if ($row['locationid'] == $vbulletin->userinfo['cb_locationid']) {
$cb_citydropdown .= "<option value=\"$row[locationid]\" selected=\"selected\">$row[name]</option>";
} else {
$cb_citydropdown .= "<option value=\"$row[locationid]\">$row[name]</option>";
}
}
// ############################### Mitgliederkarte ###############################
Weiter unten in der profile.php, nach den folgenden Zeilen
PHP:
($hook = vBulletinHook::fetch_hook('profile_updateprofile')) ? eval($hook) : false;
// save the data
$userdata->save();
muss noch der Code zum Speichern dieser Angaben hinzugefügt werden:
PHP:
// ############################### Mitgliederkarte ###############################
$vbulletin->input->clean_array_gpc('p', array(
'cb_postcode' => TYPE_STR,
'cb_country' => TYPE_STR,
'cb_locationid' => TYPE_INT
));
if (preg_match('#^(\d){4,5}$#', $vbulletin->GPC['cb_postcode'])) {
$cb_postcode = $vbulletin->GPC['cb_postcode'];
} else {
$cb_postcode = '';
}
if (in_array($vbulletin->GPC['cb_country'], array('DE', 'FL', 'AT', 'CH'))) {
$cb_country = $vbulletin->GPC['cb_country'];
} else {
$cb_country = '';
}
if ($vbulletin->GPC['cb_locationid'] != false) {
$cb_locationid = $vbulletin->GPC['cb_locationid'];
} else {
$cb_locationid = 0;
}
$db->query_write("UPDATE " . TABLE_PREFIX . "userfield SET
cb_postcode = '$cb_postcode',
cb_country = '$cb_country',
cb_locationid = $cb_locationid
WHERE userid = " . $vbulletin->userinfo['userid']
);
// PLZ oder Land hat sich geändert -> neuen Standardort setzen
if ($cb_postcode != $vbulletin->userinfo['cb_postcode'] || $cb_country != $vbulletin->userinfo['cb_country']) {
$sql = "SELECT locationid FROM geo_postcodes WHERE postcode='$cb_postcode' AND country='$cb_country'";
if (($location = $db->query_first($sql)) !== false) {
$locationid = $location['locationid'];
} else {
$locationid = 0;
}
$db->query_write("UPDATE " . TABLE_PREFIX . "userfield SET cb_locationid=$locationid
WHERE userid=" . $vbulletin->userinfo['userid']);
}
// ############################### Mitgliederkarte ###############################
Zudem muss noch das vBulletin-Template "modifyprofile" angepasst werden. Folgenden Code haben wir vor dem letzten Table-Tag in diesem Template eingefügt:
Code:
<table class="tborder" cellpadding="$stylevar[cellpadding]" cellspacing="$stylevar[cellspacing]" border="0" width="100%" align="center">
<tr>
<td class="thead">Mitgliederkarte - Optional zur Ausgabe deines Wohnortes auf der Mitgliederkarte</td>
</tr>
<tr>
<td class="panelsurround" align="center">
<div class="panel">
<div style="width:$stylevar[formwidth_usercp]" align="$stylevar[left]">
<fieldset class="fieldset">
<legend>Land und Postleitzahl</legend>
<table cellpadding="0" cellspacing="$stylevar[formspacer]" border="0" width="100%">
<tr>
<td>Land:</td>
</tr>
<tr>
<td><select name="cb_country">$cb_countrydropdown</select></td>
</tr>
<tr>
<td>Postleitzahl:</td>
</tr>
<tr>
<td><input type="text" name="cb_postcode" size="5" value="$cb_postcode" /></td>
</tr>
</table>
</fieldset>
<fieldset class="fieldset">
<legend>Genaue Ortsangabe</legend>
<if condition="$cb_citydropdown != ''">
<table cellpadding="0" cellspacing="$stylevar[formspacer]" border="0" width="100%">
<tr>
<td>Orte mit oben angegebener Postleitzahl:</td>
</tr>
<tr>
<td><select name="cb_locationid">$cb_citydropdown</select></td>
</tr>
</table>
<else />
In der Datenbank konnten keine zu dieser Postleitzahl gehörenden Orte gefunden werden!
</if>
</fieldset>
</div>
</div>
</td>
</tr>
</table>
<br />
Das Script, das die Membermap alle paar Minuten generiert (und dafür z.B. per Cronjob alle paar Minuten ausgeführt werden muss!), sieht wie folgt aus (am Anfang müssen ein paar Konfigurationsvariablen gesetzt werden!):
PHP:
#!/usr/bin/php
<?php
/*
// Konfiguration
*/
define('DSN', "mysql://__BENUTZER__:__PASSWORT__@__SERVER__/__DATENBANK__");
$radii = array(0 => 1, 2 => 2, 5 => 3, 10 => 4);
// Pfad zu Template- und Ausgabe-Grafiken
$maptemplate = '/var/www/localhost/templates/membermap.png';
$mapoutput = '/var/www/localhost/htdocs/membermap/map.png';
/*
// Geo-Klasse
*/
require_once('Geo/Geo.php');
$geodb = Geo::setupSource('DB', DSN);
$db = DB::connect(DSN, true);
if (DB::isError($db)) {
die ($db->getMessage());
}
$map2 = Geo::setupMap($maptemplate);
$map2->setRange(5.68, 17.2, 45.8, 55.1);
/*
// Eingegebene Länder und Postleitzahlen auslesen
*/
$sql = "SELECT cb_locationid FROM vb_userfield WHERE cb_locationid!=0 GROUP BY cb_locationid";
$res = $db->query($sql);
$users = array();
while ($row = $res->fetchRow()) {
list($locationid) = $row;
/*
// Stadtname und Koordinaten auslesen
*/
$sql = "SELECT name, latitude, longitude FROM geo_locations WHERE locationid=$locationid";
$temp = $geodb->performQuery($sql);
if (count($temp) == 0) {
continue;
}
list($ort) = $temp;
/*
// Boardies aus dieser Stadt auslesen
*/
$sql = "SELECT
vb_user.userid,
vb_user.username,
vb_userfield.cb_postcode,
vb_userfield.cb_country
FROM vb_userfield
LEFT JOIN vb_user USING(userid)
WHERE vb_userfield.cb_locationid=$locationid
ORDER BY vb_user.username ASC";
$res2 = $db->query($sql);
while ($row2 = $res2->fetchRow()) {
$i = $map2->addGeoObjectIncrease($ort, 'orange', $radii);
$users[$i][] = "<a href=\"member.php?u=$row2[0]\">$row2[1]</a>";
}
}
$map2->saveImage($mapoutput);
$code = $map2->getImageMap('map');
$userarray = array();
foreach ($users as $users2) {
$userarray[] = "'" . str_replace('\'', '\\\'', implode(', ', $users2)) . "'";
}
$code .= "\n\n<script type=\"text/javascript\">\nvar mmap_users = new Array(\n";
$code .= implode(",\n", $userarray);
$code .= "\n);\n</script>\n";
// In der $code-Variable steht nun der JavaScript-Code zum Anzeigen der Popups
// Dieser muss irgendwo gespeichert werden. Zum Beispiel in einer Datenbank,
// aber eine normale Datei tut es auch. Zum Beispiel:
$mapdatafile = '/var/www/localhost/membermap/membermap.data';
file_put_contents($mapdatafile, $code);
?>
Die Datei membermap.php, die für die Ausgabe der Karte zuständig ist und die man im vBulletin-Verzeichnis bei den anderen PHP-Dateien ablegt, sieht wie folgt aus (Wert von $mapdatafile gegen Ende anpassen!):
PHP:
<?php
// ######################## SET PHP ENVIRONMENT ###########################
error_reporting(E_ALL & ~E_NOTICE);
// ##################### DEFINE IMPORTANT CONSTANTS #######################
define('THIS_SCRIPT', 'calendar');
// ################### PRE-CACHE TEMPLATES AND DATA ######################
// pre-cache templates used by all actions
$globaltemplates = array('membermaphome');
// ######################### REQUIRE BACK-END ############################
require_once('./global.php');
// #######################################################################
// ######################## START MAIN SCRIPT ############################
// #######################################################################
// get permissions to view forumhome
if (!($permissions['forumpermissions'] & $vbulletin->bf_ugp_forumpermissions['canview']))
{
print_no_permission();
}
// draw nav bar
$navbits = array('' => 'Mitgliederkarte');
$navbits = construct_navbits($navbits);
$mapdatafile = "/var/www/localhost/membermap/membermap.data";
$html = file_get_contents($mapdatafile);
eval('$navbar = "' . fetch_template('navbar') . '";');
eval('print_output("' . fetch_template('membermaphome') . '");');
?>
Nun fehlt nur noch das Template "membermaphome", das man im AdminCP von vBulletin hinzufügt:
Code:
$stylevar[htmldoctype]
<html dir="$stylevar[textdirection]" lang="$stylevar[languagecode]">
<head>
$headinclude
<script type="text/javascript" src="/clientscripts/cb_membermap.js"></script>
<link rel="stylesheet" type="text/css" href="/stylesheets/cb_membermap.css" />
<title>$vboptions[bbtitle] - Mitgliederkarte</title>
</head>
<body onload="mmap_watch_areas()">
$header
$navbar
<table class="tborder" cellpadding="$stylevar[cellpadding]" cellspacing="$stylevar[cellspacing]" border="0" width="100%" align="center">
<tr>
<td class="tcat">
<strong>Mitgliederkarte</strong>
</td>
</tr>
<tr>
<td class="alt1" align="center">
$html
<div align="center">
<img src="https://pics.computerbase.de/membermap/map.png" width="950" height="1243" usemap="#map" alt="" border="0" /><br />
<small>Trage Postleitzahl und Land in deinem <a href="profile.php?do=editprofile">Profil</a> ein, nach spätestens vier Minuten erscheinst du dann auf der Karte!</small>
</div>
</td>
</tr>
</table>
<div id="membermap_popup" style="display:none"></div>
$forumrules
$footer
</body>
</html>
Die darin erwähnten Dateien cb_membermap.css und cb_membermap.js (unsere Alternative zur overlib.js des Original MapHacks, funktioniert wahrscheinlich mit weniger Browsern, ist jedoch kleiner und hat einen netten Fade-In-Effekt) dürft ihr auf euren Server kopieren und modifizieren, vorausgesetzt dass der Hinweis am Anfang der Dateien erhalten bleibt!
Ich denke das war alles. Wie eingangs erwähnt ist dies keine eigenständige Anleitung, Grundvoraussetzung ist das Verständnis des verlinkten Original MapHack!
Zuletzt bearbeitet: