Compare commits
4 Commits
Author | SHA1 | Date |
---|---|---|
|
577bdc4a77 | 6 months ago |
![]() |
44960a2ad4 | 2 years ago |
|
66d399832e | 2 years ago |
|
3f03ea7cda | 2 years ago |
10 changed files with 473 additions and 672 deletions
@ -1,31 +0,0 @@ |
||||
|
||||
NAME = wolkal3000
|
||||
ICALPARSER = icalparser
|
||||
|
||||
##this shouldn't be here
|
||||
|
||||
#INSTALLDIR = /usr/share/wordpress/wp-content/plugins/$(NAME)
|
||||
#SSHACCOUNT = root@192.168.20.30
|
||||
VERSION = 0.3.3
|
||||
|
||||
|
||||
# Make sure we always ship the latest icalparser version
|
||||
icalparser: |
||||
if [ -d icalparser ] ; then \
|
||||
cd icalparser && git pull ; \
|
||||
else \
|
||||
git clone https://github.com/OzzyCzech/icalparser ; \
|
||||
fi
|
||||
|
||||
|
||||
release: icalparser |
||||
cd .. ; \
|
||||
rm -f $(NAME)-$(VERSION).zip ; \
|
||||
zip -9 -r $(NAME)-$(VERSION).zip $(NAME)/$(ICALPARSER)/readme.md $(NAME)/$(ICALPARSER)/LICENSE $(NAME)/$(ICALPARSER)/src/* $(NAME)/$(ICALPARSER)/tools/* $(NAME)/*.php $(NAME)/readme.* $(NAME)/README.*
|
||||
|
||||
|
||||
install: icalparser |
||||
rsync --delete -C -av ./ $(SSHACCOUNT):$(INSTALLDIR)
|
||||
ssh $(SSHACCOUNT) chown -R www-data:www-data $(INSTALLDIR)
|
||||
|
||||
|
@ -1,201 +0,0 @@ |
||||
<?php |
||||
|
||||
defined( 'ABSPATH' ) or die( 'No script kiddies please!' ); |
||||
|
||||
|
||||
/** |
||||
* Display the admin table |
||||
* |
||||
* @since 0.1.0 |
||||
* |
||||
*/ |
||||
|
||||
/* |
||||
* Quellen: |
||||
* https://codex.wordpress.org/Creating_Options_Pages |
||||
* http://ottopress.com/2009/wordpress-settings-api-tutorial/ |
||||
*/ |
||||
|
||||
add_action('admin_menu', 'gcal_admin_add_page'); |
||||
|
||||
function gcal_admin_add_page() { |
||||
add_options_page( 'WolKal3000 – Synchronisation von Kal3000 mit Wolke-Kalendern', 'WolKal3000', 'manage_options', 'kal3000-gcal-import', 'gcal_options_page'); |
||||
} |
||||
|
||||
function gcal_options_page() { |
||||
|
||||
if ( !current_user_can( 'manage_options' ) ) { |
||||
wp_die( __( 'You do not have sufficient permissions to access this page.' ) ); |
||||
} |
||||
|
||||
?> |
||||
|
||||
<div class="wrap"> |
||||
<h1><?= esc_html(get_admin_page_title()); ?></h1>
|
||||
<p>Mit WolKal3000 kannst du dein Kal3000-Plugin automatisch mit Kalendern in der grünen Wolke synchronisieren. Darüber hinaus kannst du jede andere ICS-Datei verwenden.</p> |
||||
|
||||
<form action="options.php" method="post"> |
||||
<?php settings_fields('gcal_options'); ?> |
||||
<?php do_settings_sections('gcal'); ?> |
||||
<input name="Submit" type="submit" value="<?php esc_attr_e('Save Changes'); ?>" />
|
||||
</br></br></br><hr></br> |
||||
<?php do_settings_sections('gcal2'); ?> |
||||
<input name="Submit" type="submit" value="<?php esc_attr_e('Save Changes'); ?>" />
|
||||
</form></div> |
||||
|
||||
<?php |
||||
} |
||||
|
||||
add_action('admin_init', 'gcal_admin_init'); |
||||
|
||||
function gcal_admin_init(){ |
||||
register_setting( 'gcal_options', 'gcal_options', 'gcal_options_validate' ); |
||||
add_settings_section('gcal_feeds', 'Wolke-Kalender einer Terminkategorie zuordnen', 'gcal_feeds_section_text', 'gcal'); |
||||
|
||||
// settings fields dynamisch pro Feed generieren, nur ein Callback mit Args nutzen. |
||||
$terms = get_terms( array( |
||||
'taxonomy' => 'termine_type', |
||||
'hide_empty' => false, ) |
||||
); |
||||
foreach($terms as $term){ |
||||
$unique_id = 'gcal_feed_' . $term->name; |
||||
$feed_name = $term->name; |
||||
add_settings_field($unique_id, 'Terminkategorie "'.$feed_name.'"', 'gcal_feeds_setting_string', 'gcal', 'gcal_feeds', array($unique_id)); |
||||
} |
||||
|
||||
add_settings_section('gcal_timer', 'Synchronisationsintervall', 'gcal_timer_section_text', 'gcal'); |
||||
add_settings_field('gcal_timer', 'Synchronisiere alle …', 'gcal_timer_setting_string', 'gcal', 'gcal_timer'); |
||||
|
||||
add_settings_section('gcal_geocoding', 'Geocoding (EXPERIMENTELL)', 'gcal_geocoding_section_text', 'gcal2'); |
||||
add_settings_field('gcal_geocoding', 'Geocoding-Methode', 'gcal_geocoding_setting_string', 'gcal2', 'gcal_geocoding'); |
||||
|
||||
add_settings_section('gcal_debugging', 'Entwickler*innenoptionen', 'gcal_debugging_section_text', 'gcal2'); |
||||
add_settings_field('gcal_debugging', 'Debugging', 'gcal_debugging_setting_string', 'gcal2', 'gcal_debugging'); |
||||
} |
||||
|
||||
|
||||
function gcal_feeds_section_text() { |
||||
?> |
||||
<p><b>Wolke-Kalender synchronisieren in eine ausgewählte Terminkategorie von Kal3000. Bitte trage hierfür die entsprechende Export-Adresse des gewünschten Wolke-Kalenders ein.</b></br><b>Falls zu einer Terminkategorie kein Wolke-Kalender synchronisiert werden soll, entsprechendes Feld bitte leer lassen.</b></p> |
||||
<p><b><a href="hilfeseite" target="_blank">Erfahre mehr darüber, wie du die Export-Adresse eines Wolke-Kalenders findest.</a></b> |
||||
</p> |
||||
<?php |
||||
} |
||||
|
||||
|
||||
|
||||
function gcal_feeds_setting_string($args) { |
||||
$options = get_option('gcal_options'); |
||||
$placeholder = "z.B. https://wolke.netzbegruenung.de/remote.php/dav/public-calendars/xxxxxxxxxxxxxxxx?export"; |
||||
// die id entspricht dem unique_id in add_settings_field. |
||||
// der name wird options.php als Name der zu setzenden Option übergeben |
||||
// der Value ist der inhalt von der $option[unique_id]. |
||||
echo '<input type="text" id="' . $args[0] . '" name="gcal_options[' . $args[0] . ']" value="' . $options[$args[0]] . '" size="80" maxlength="256" placeholder="' . $placeholder . '" > </br>'; |
||||
} |
||||
|
||||
|
||||
function gcal_timer_section_text() { |
||||
?> |
||||
<p><b>In welcher Regelmäßigkeit sollen die Wolke-Kalender synchronisiert werden? Bitte Zeitintervall in Minuten angeben.</b></br> |
||||
<b>Achtung: Um Änderungen wirksam werden zu lassen, muss das Plugin deaktiviert und wieder aktiviert werden. (Menüpunkt "Plugins")</b></p> |
||||
<?php |
||||
} |
||||
|
||||
|
||||
function gcal_timer_setting_string() { |
||||
$options = get_option('gcal_options'); |
||||
$placeholder = "default 60"; |
||||
echo '<input type="text" id="gcal_timer" name="gcal_options[gcal_timer]" value="' . $options['gcal_timer'] . '" size="6" maxlength="6" placeholder="' . $placeholder . '" > Minuten </br>'; |
||||
} |
||||
|
||||
|
||||
|
||||
function gcal_geocoding_section_text() { |
||||
?> |
||||
<p>Damit der Termin-Ort auf einer Karte eingezeichnet werden kann, müssen die Ortsinformationen von der Textform in geografische Länge und Breite umgerechnet werden.</br>Dies nennt sich Geocoding. Dabei handelt es sich um eine experimentelle Funktion von WolKal3000.</p> |
||||
<?php |
||||
} |
||||
|
||||
|
||||
function gcal_geocoding_setting_string() { |
||||
$options = get_option('gcal_options'); |
||||
$current = ( isset ($options['gcal_geocoding']) ? $options['gcal_geocoding'] : 'off' ); // default off |
||||
$apikey = ( isset ($options['gcal_apikey']) ? $options['gcal_apikey'] : '' ); // default empty |
||||
|
||||
$coders = array( |
||||
array( |
||||
'option' => 'off', |
||||
'name' => 'deaktiviert', |
||||
), /* |
||||
array( |
||||
'option' => 'official', |
||||
'name' => 'Google official - erfordert einen API Key --> ', |
||||
), |
||||
array( |
||||
'option' => 'inofficial', |
||||
'name' => 'Google inofficial', |
||||
), */ |
||||
array( |
||||
'option' => 'osm', |
||||
'name' => 'OpenStreetMap (EXPERIMENTELL)', |
||||
), |
||||
); |
||||
|
||||
foreach ( $coders as $coder ) { |
||||
$checked = ( $current == $coder['option'] ? 'checked' : '' ); |
||||
echo '<input type="radio" id="gcal_geocoding" name="gcal_options[gcal_geocoding]" value ="' . $coder['option'] . '" ' . $checked . '> ' . $coder['name']; |
||||
if ( $coder['option'] == 'official' ) { |
||||
echo '<input type="text" size="48" id="gcal_geocoding" name="gcal_options[gcal_apikey]" value="' . $apikey . '">'; |
||||
} |
||||
|
||||
echo '</br>' ; |
||||
} |
||||
} |
||||
|
||||
|
||||
function gcal_debugging_section_text() { |
||||
?> |
||||
<p>Debugging aktivieren? Speicherort: ${APACHE_LOG_DIR}/error.log</br></br> |
||||
Um die Performance zu verbessern, werden gefundene Geocoding-Daten zwischengespeichert. </br> |
||||
Zu Debugging-Zwecken kann der Zwischenspeicher (Cache) des Plugins gelöscht </br> |
||||
werden, um ein neues Geocoding aller Termin-Orte zu erzwingen. </br> |
||||
</p> |
||||
<?php |
||||
} |
||||
|
||||
|
||||
|
||||
function gcal_debugging_setting_string($args) { |
||||
$options = get_option('gcal_options'); |
||||
// example from https://code.tutsplus.com/tutorials/the-wordpress-settings-api-part-8-validation-sanitisation-and-input-i--wp-25361 |
||||
// echo '<input type="checkbox" id="gcal_debugging" name="gcal_options[gcal_debugging]" value="1"' . checked( 1, $options['gcal_debugging'], false ) . '> Debug-Logging aktivieren </br>'; |
||||
// let's make a select box: |
||||
?> |
||||
<select id="gcal_debugging" name="gcal_options[gcal_debugging]"> |
||||
<option value=0 <?php selected($options['gcal_debugging'], NONE); ?>>off</option>
|
||||
<option value=1 <?php selected($options['gcal_debugging'], CRIT); ?>>critical</option>
|
||||
<option value=2 <?php selected($options['gcal_debugging'], WARN); ?>>critical + warnings</option>
|
||||
<option value=3 <?php selected($options['gcal_debugging'], INFO); ?>>critical + warnings + info</option>
|
||||
</select> </br> |
||||
<?php |
||||
// actual logging is done by gcal_error_log() |
||||
// Cache reset on restart |
||||
echo '</br><input type="checkbox" id="gcal_reset_cache" name="gcal_options[gcal_reset_cache]" value="1"' . checked( 1, $options['gcal_reset_cache'], false ) . '> Geocoding-Cache nach Deaktivieren und Aktivieren des Plugins löschen </br>';} |
||||
|
||||
|
||||
|
||||
function gcal_options_validate($input) { |
||||
return $input; |
||||
|
||||
// TODO |
||||
|
||||
/* |
||||
$newinput['text_string'] = trim($input['text_string']); |
||||
if(!preg_match('/^[a-z0-9]{32}$/i', $newinput['text_string'])) { |
||||
$newinput['text_string'] = ''; |
||||
} |
||||
return $newinput; |
||||
*/ |
||||
} |
||||
|
||||
|
@ -1,229 +0,0 @@ |
||||
<?php |
||||
|
||||
defined( 'ABSPATH' ) or die( 'No script kiddies please!' ); |
||||
|
||||
|
||||
function gcal_import_geocity($location) { |
||||
|
||||
// Wenn die Adresse im Feld Stadt steht, wird sie richtig angezeigt, ergo: |
||||
$pattern = '/(.*), ([0-9]{5} [^,]+)/'; |
||||
preg_match ($pattern, $location, $matches); |
||||
if ( empty ($matches[2])) { |
||||
return ($location); |
||||
} else { |
||||
return ($matches[2]); |
||||
} |
||||
} |
||||
|
||||
|
||||
function gcal_import_geoshow($location) { |
||||
|
||||
// later |
||||
// not NULL so kal3000_the_termin_geo() displays a map if lat/lon are available. |
||||
// Hotel-Gasthof Maisberger, Bahnhofstraße 54, 85375 Neufahrn bei Freising, Deutschland |
||||
// alles bis ", [0-9]{5}" ist geoshow |
||||
// alles ab [0-9]{5}[\,]+ ist geocity. |
||||
$pattern = '/(.*), ([0-9]{5} [^,]+)/'; |
||||
preg_match ($pattern, $location, $matches); |
||||
return ($matches[1]); |
||||
|
||||
} |
||||
|
||||
|
||||
function gcal_import_geocode($location) { |
||||
|
||||
// we try to cache results as we will need many times the same results especially for recurring events. |
||||
// we will use a hash for the location because the hash has a fixed length, while the location has not. |
||||
// This table will grow indefinitely over time, so we need to add a timestamp field and remove |
||||
// entries that are older than, say, 30 days each time. |
||||
// this will also cope with Google subtly changing location strings in Maps over time. |
||||
// new entries will thus replace outdated ones over time. |
||||
|
||||
/* |
||||
Caching neu: in wp_options-> gcal_options ein Array geocache anlegen. Darunter für jeden hash ein Array schreiben, also: |
||||
|
||||
Datenmodell: |
||||
|
||||
$geocache = array ( |
||||
hash1 = array ( |
||||
'gcal_geo_lat' => '', |
||||
'gcal_geo_lon' => '', |
||||
'gcal_geo_timestamp' => 0, |
||||
), |
||||
hash2 = array ... |
||||
); |
||||
|
||||
|
||||
Schreiben: |
||||
$options = get_options ( 'gcal_options' ); |
||||
|
||||
$geocache = $options ( 'geocache' ); |
||||
$geocache['hashx'] = array ( $lat, $lon, time(), ); |
||||
|
||||
$options ( 'geocache' ) = $geocache; |
||||
|
||||
Löschen: |
||||
|
||||
foreach ( $geocache as $key => $value ) { |
||||
if ( $key['gcal_geo_timestamp'] < time() - 2592000 ) { |
||||
unset ( $options['geocache']['hashx'] ) |
||||
} |
||||
} |
||||
|
||||
set_options ( 'gcal_options' ); |
||||
|
||||
Suchen: if ( isset ( $options['geocache']['hashx'] ) ) ... |
||||
|
||||
|
||||
|
||||
*/ |
||||
|
||||
|
||||
if ( '' == $location ) { |
||||
return array ('', ''); |
||||
} |
||||
// check the cache first |
||||
global $wpdb; |
||||
$table = $wpdb->prefix.GCAL_GEO_TABLE; |
||||
$hash = hash ('md5', $location); |
||||
$query = "SELECT gcal_geo_lat, gcal_geo_lon FROM $table WHERE gcal_geo_hash = '$hash'"; |
||||
$result = $wpdb->get_row( $query, ARRAY_N ); |
||||
if ( $wpdb->num_rows == 1 ) { // it should only be a single row! |
||||
gcal_error_log (INFO, "geocode cache hit hash $hash lat $result[0] lon $result[1]"); |
||||
} else { |
||||
// do the housekeeping first, before we create a new caching entry. |
||||
// remove all cache entries which are older than 30 days. |
||||
$outdated = time() - 2592000; // 30 Tage |
||||
$query = "DELETE FROM $table WHERE gcal_geo_timestamp < $outdated"; |
||||
$wpdb->query($query); |
||||
|
||||
$options = get_option('gcal_options'); |
||||
$result = array ('', ''); |
||||
|
||||
switch ( $options['gcal_geocoding'] ) { |
||||
case "official" : |
||||
$result = gcal_import_geocode_official($location); |
||||
break; |
||||
case "inofficial" : |
||||
$result = gcal_import_geocode_inofficial($location); |
||||
break; |
||||
case "osm" : |
||||
$result = gcal_import_geocode_osm($location); |
||||
break; |
||||
} |
||||
|
||||
$file = dirname (__FILE__) . "/geocode-result-$hash.txt"; |
||||
file_put_contents ($file, var_export ($result, TRUE)); |
||||
// do the caching now, but only if both values are set. |
||||
// $wpdb_insert does all the sanitizing for us. |
||||
$lat = $result[0]; |
||||
$lon = $result[1]; |
||||
if ('' != $lat && '' != $lon) { |
||||
$wpdb->insert($table, array( |
||||
'gcal_geo_location' => substr( $location, 0, 128 ), |
||||
'gcal_geo_hash' => $hash, |
||||
'gcal_geo_lat' => $lat, |
||||
'gcal_geo_lon' => $lon, |
||||
'gcal_geo_timestamp' => time(), |
||||
)); |
||||
gcal_error_log (INFO, "geocoded and cached lat=$lat lon=$lon for location $location"); |
||||
} |
||||
// error handling? |
||||
} |
||||
return ($result); |
||||
} |
||||
|
||||
|
||||
function gcal_import_geocode_official($location) { |
||||
$options = get_option('gcal_options'); |
||||
if ( ! isset ( $options['gcal_apikey'] ) || '' == $options['gcal_apikey'] ) { // ??? we should handle this in the admin frontend. |
||||
gcal_error_log (WARN, "using Google official geocoding but provided no APIKEY"); |
||||
return array ('',''); |
||||
} else { |
||||
$apikey = $options['gcal_apikey']; |
||||
$location = urlencode($location); |
||||
$useragent = 'Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0'; |
||||
// https://developers.google.com/maps/documentation/geocoding/start |
||||
$url = "https://maps.googleapis.com/maps/api/geocode/json?address=$location&key=$apikey"; |
||||
|
||||
$response = curl_get_remote($url); |
||||
$decoded = json_decode($response, true); |
||||
$lat = $decoded['results']['0']['geometry']['location']['lat']; |
||||
$lon = $decoded['results']['0']['geometry']['location']['lng']; |
||||
gcal_error_log (INFO, __FUNCTION__ . " found lat $lat lon $lon"); |
||||
return array ($lat, $lon); |
||||
/* |
||||
{ |
||||
"error_message" : "The provided API key is invalid.", |
||||
"results" : [], |
||||
"status" : "REQUEST_DENIED" |
||||
} |
||||
*/ |
||||
} |
||||
} |
||||
|
||||
|
||||
function gcal_import_geocode_osm($location) { |
||||
// https://wiki.openstreetmap.org/wiki/Nominatim |
||||
// https://nominatim.openstreetmap.org/search?q=Hotel+Gumberger+Gasthof+GmbH&format=json' |
||||
$location = urlencode($location); |
||||
gcal_error_log (INFO, "gcal_import_geocode_osm: location $location"); |
||||
// the main problem with Nominatim is that it doesn't understand GCal location information very well. |
||||
// we ought to cut off the location name and the country, i.e. zip code, city & street address only |
||||
$url = 'https://nominatim.openstreetmap.org/search?q="' . $location . '"&format=json'; |
||||
$response = wp_remote_get($url); |
||||
$json = wp_remote_retrieve_body($response); |
||||
$http_code = wp_remote_retrieve_response_code($response); |
||||
// we need to catch errors |
||||
|
||||
// https://www.php.net/manual/en/function.json-decode.php |
||||
$decoded = json_decode($json, true); |
||||
// TODO error handling e.g. if we get no usable values. |
||||
/* |
||||
$file = dirname (__FILE__) . '/json-decoded.txt'; |
||||
// should simply be ->lat and -> lon |
||||
file_put_contents ($file, var_export ($decoded, TRUE)); |
||||
// The first array level ([0]) is only needed because OSM returns a JSON with enclosing []. |
||||
*/ |
||||
$lat = $decoded['0']['lat']; |
||||
$lon = $decoded['0']['lon']; |
||||
gcal_error_log (INFO, "gcal_import_geocode_osm found lat=$lat lon=$lon loc $location"); |
||||
return array ($lat, $lon); |
||||
} |
||||
|
||||
|
||||
function gcal_import_geocode_inofficial($location) { |
||||
|
||||
$attempts = 0; |
||||
$success = false; |
||||
// we'll need to be easy with GMaps in order no to get a 429 Too Many Requests reply. |
||||
// max 3 retries with 2 second pauses, else we give up. |
||||
while ($success == false && $attempts < 3) { |
||||
// @ = 'ignore_errors' => TRUE |
||||
$url = "https://maps.google.com/maps?q=" . urlencode ($location); |
||||
// we use wp-remote.* instead of file_get_contents because it does many high level things e.g. redirects |
||||
$response = wp_remote_get($url); |
||||
$result = wp_remote_retrieve_body($response); |
||||
$http_code = wp_remote_retrieve_response_code($response); |
||||
if (200 == $http_code) { |
||||
$success = true; |
||||
} elseif (429 == $http_code) { |
||||
time.sleep(2); |
||||
++$attempts; |
||||
gcal_error_log (INFO, "got $attempts HTTP 429 Too Many Requests on $url"); |
||||
} else { |
||||
gcal_error_log (WARN, "Unspecified HTTP error $http_code"); |
||||
return array ('', ''); |
||||
} |
||||
} |
||||
|
||||
// ok so $result seems to be valid. |
||||
// and now we need to look for: |
||||
$pattern = '#www.google.com/maps/preview/place/[^/]+/@([\d\.]+),([\d\.]+),.*#'; |
||||
preg_match ($pattern, $result, $matches); |
||||
|
||||
// and return the result: |
||||
return array ($matches[1], $matches[2]); |
||||
} |
||||
|
||||
|
@ -1,88 +0,0 @@ |
||||
=== Kal3000 Google Calender Importer === |
||||
Contributors: hmilz |
||||
Tags: kal3000, urwahl3000, calendar |
||||
Donate link: https://www.paypal.me/HaraldMilz |
||||
Requires at least: 4.0 |
||||
Tested up to: 4.9 |
||||
Requires PHP: 7.3 |
||||
Stable tag: 0.2.0 |
||||
License: GPLv3 or later |
||||
License URI: https://www.gnu.org/licenses/gpl-3.0 |
||||
|
||||
Imports and Merges an Arbitrary Number of Public Google Calendars into Kal3000. |
||||
|
||||
== Beschreibung == |
||||
Ein Wordpress-Plugin, das auf das Grüne Wordpress-Theme <a href="http://kre8tiv.de/urwahl3000/">Urwahl3000</a> aufsetzt und eine Integration beliebig vieler öffentlicher Google-Kalender ermöglicht. |
||||
|
||||
Das hier ist noch "work in progress", und es ist noch nicht produktiv benutzbar! Das Plugin könnte Dein Wordpress zerschießen, Deinen Kreis- oder Ortsverband versehentlich auflösen oder den Klimawandel beschleunigen! Aber für mich funktioniert es schon recht ordentlich. |
||||
|
||||
* Administration in Wordpress über die Admin-Oberfläche. |
||||
* Einbinden beliebig vieler Google-Kalender. |
||||
* Zuordnung dieser Google-Kalender zu bereits angelegten Terminkategorien, beispielsweise je OV. |
||||
* Geocoding von Veranstaltungsorten, wie sie aus Google Kalender übernommen werden. Derart angelegte Termine werden auf der Übersichtskarte richtig angezeigt. |
||||
|
||||
|
||||
== Installation == |
||||
|
||||
1. Um eine auf Urwahl3000 und Wordpress basierende KV- oder OV-Seite betreiben zu können, braucht man zunächst eine irgendwo gehostete aktuelle Wordpress-Umgebung. Dazu wird auf die Dokumentation von Urwahl3000 verwiesen. |
||||
|
||||
2. Als nächstes holt man sich das Plugin unter <a href="http://www.seneca.muc.de/kal3000-gcal-import/">http://www.seneca.muc.de/kal3000-gcal-import/</a> und installiert es über die WP-Oberfläche wie gewohnt. |
||||
|
||||
Hinweis: kal3000-gcal-import nutzt für das Parsen von ICAL-Files und -Feeds das PHP-Modul icalparser (https://github.com/OzzyCzech/icalparser). Die Verwendung und die Einbindung in die Release-ZIP-Files erfolgt mit freundlicher Genehmigung des Autors Roman Ožana. |
||||
|
||||
== Konfiguration == |
||||
|
||||
1. in WP legt man Terminkategorien an, z.B. eine pro OV und eine für den KV, plus weitere nach Bedarf. Das funktioniert am besten mit einer entsprechenden Seitenhierarchie wie auf https://www.gruene-freising.de/... . |
||||
|
||||
2. Im Admin-Teil des Plugins unter "Einstellungen / GCal Importer" erscheinen die angelegten Terminkategorien. Jeder Kategorie weist man dann einen öffentlichen Google-Kalender in Form des "public ics"-Links zu, beispielsweise <a href="https://calendar.google.com/calendar/ical/gruene.freising%40gmail.com/public/basic.ics">https://calendar.google.com/calendar/ical/gruene.freising%40gmail.com/public/basic.ics</a>. |
||||
|
||||
3. Im Admin-Teil kann man das Zeitintervall einstellen, mit dem die Kalender synchronisiert werden. Standardeinstellung ist 60 Minuten. Bitte beachten, dass der Wordpress-Scheduler die Zeitintervalle nur ungefähr und abhängig von der Seitenaktivität einhält. |
||||
|
||||
4. Im Admin-Teil kann man das Geocoding aktivieren. Derzeit ist nur ein inoffizieller Weg über Google Maps verfügbar, den Google nicht gerne sieht. Das offizielle <a href="https://developers.google.com/maps/documentation/geocoding/start">Google-API</a> erfordert einen API-Key, der bei intensiver Nutzung nicht kostenlos ist. Auf die Google-Policy wird hingewiesen. Außerdem ist OpenStreetMap verfügbar, aber es kann nicht sehr gut mit den Lokationen aus Google Maps umgehen. Im Moment ist es benutzbar, funktioniert aber nicht zuverlässig. |
||||
|
||||
5. Speichern und fertig. |
||||
|
||||
Unter "Debugging" finden sich zwei weitere Einstellungen: |
||||
|
||||
1. zum einen kann man ein Debug-Logging aktivieren, mit dem das Plugin Einträge in ${APACHE_LOG_DIR}/error.log schreibt. NONE schreibt nichts, CRIT (critical) am wenigsten, INFO (alles) am meisten. |
||||
|
||||
2. Die zweite Einstellung löscht den Geocoding-Cache bei jedem Plugin-Neustart, um das Geocoding für jede Lokation neu zu erzwingen, beispielsweise wenn man die Geocoding-Methode geändert hat. |
||||
|
||||
== Benutzung == |
||||
|
||||
Um die Termine in WP anzuzeigen, gibt es zwei Wege: Das Termine-Widget in der rechten Spalte zeigt immer alle Termine an. Darüber hinaus kann man beispielsweise pro OV eine Unterseite mit dem Titel "OV Termine" anlegen, in der folgender Shortcode steht: <code>[wpcalendar kat=TERMINKATEGORIE]</code>. Auf dieser Seite werden dann nur die Termine des dazugehörigen OV angezeigt. |
||||
|
||||
Mit "Aktivieren" beginnt das Plugin sofort mit der Synchronisation. |
||||
|
||||
|
||||
== Frequently Asked Questions == |
||||
|
||||
Keine bisher. |
||||
|
||||
== Changelog == |
||||
|
||||
= 0.3.1 = |
||||
* multi-level debugging |
||||
* OSM geocoding (unstable) |
||||
* secret events handling |
||||
* using cURL for more stability in some places |
||||
* added geocoding cache reset on restart option |
||||
* geoshow / geocity mapping |
||||
|
||||
= 0.3 = |
||||
* new branch |
||||
* added OpenStreetMap geocoding |
||||
|
||||
= 0.2 = |
||||
* First fully functioning release. |
||||
|
||||
= 0.1 = |
||||
* Initial release. |
||||
|
||||
== Upgrade Notice == |
||||
= 0.2 = |
||||
Upgrade notices describe the reason a user should upgrade |
||||
|
||||
= 0.1 = |
||||
This version fixes a security related bug. Upgrade immediately. |
||||
|
@ -0,0 +1,202 @@ |
||||
<?php |
||||
|
||||
defined( 'ABSPATH' ) or die( 'No script kiddies please!' ); |
||||
|
||||
require_once dirname( __FILE__ ) . "/wolkal3000-config.php"; |
||||
|
||||
/** |
||||
* Display the admin table |
||||
* |
||||
* @since 0.1.0 |
||||
* |
||||
*/ |
||||
|
||||
/* |
||||
* Quellen: |
||||
* https://codex.wordpress.org/Creating_Options_Pages |
||||
* http://ottopress.com/2009/wordpress-settings-api-tutorial/ |
||||
*/ |
||||
|
||||
add_action('admin_menu', 'wolkal3000_admin_add_page'); |
||||
|
||||
function wolkal3000_admin_add_page() { |
||||
add_options_page( 'WolKal3000 – Synchronisation von Kal3000 mit Wolke-Kalendern', 'WolKal3000', 'manage_options', 'wolkal3000', 'wolkal3000_options_page'); |
||||
} |
||||
|
||||
function wolkal3000_options_page() { |
||||
|
||||
if ( !current_user_can( 'manage_options' ) ) { |
||||
wp_die( __( 'You do not have sufficient permissions to access this page.' ) ); |
||||
} |
||||
if ( !defined('WOLKAL_PREFIX') ) { |
||||
wp_die( __( 'WOLKAL_PREFIX is not configured in wolkal3000-config.php, please check the installation steps!' ) ); |
||||
} |
||||
if ( !file_exists(__DIR__ . '/icalparser/src/IcalParser.php')) { |
||||
wp_die( __( 'icalparser is not properly installed, please check the installation steps!' ) ); |
||||
} |
||||
?> |
||||
|
||||
<div class="wrap"> |
||||
<h1><?= esc_html(get_admin_page_title()); ?></h1>
|
||||
<p>Mit WolKal3000 kannst du deine Urwahl3000 "Termine" automatisch mit Ereignissen aus Kalendern in der grünen Wolke befüllen.</p> |
||||
|
||||
<form action="options.php" method="post"> |
||||
<?php settings_fields('wolkal3000_options'); ?> |
||||
<?php do_settings_sections('wolkal3000'); ?> |
||||
<input name="Submit" type="submit" value="<?php esc_attr_e('Save Changes'); ?>" />
|
||||
</br></br></br><hr></br> |
||||
<?php do_settings_sections('wolkal3000_adv'); ?> |
||||
<input name="Submit" type="submit" value="<?php esc_attr_e('Save Changes'); ?>" />
|
||||
</form></div> |
||||
|
||||
<?php |
||||
} |
||||
|
||||
add_action('admin_init', 'wolkal3000_admin_init'); |
||||
|
||||
function wolkal3000_admin_init(){ |
||||
register_setting( 'wolkal3000_options', 'wolkal3000_options', 'wolkal3000_options_validate' ); |
||||
add_settings_section('wolkal3000_feeds', 'Wolke-Kalender einer Terminkategorie zuordnen', 'wolkal3000_feeds_section_text', 'wolkal3000'); |
||||
|
||||
// settings fields dynamisch pro Feed generieren, nur ein Callback mit Args nutzen. |
||||
$terms = get_terms( array( |
||||
'taxonomy' => 'termine_type', |
||||
'hide_empty' => false, ) |
||||
); |
||||
foreach($terms as $term){ |
||||
$unique_id = 'wolkal3000_feed_' . $term->name; |
||||
$feed_name = $term->name; |
||||
add_settings_field($unique_id, 'Terminkategorie "'.$feed_name.'"', 'wolkal3000_feeds_setting_string', 'wolkal3000', 'wolkal3000_feeds', array($unique_id)); |
||||
} |
||||
|
||||
add_settings_section('wolkal3000_timer', 'Synchronisationsintervall', 'wolkal3000_timer_section_text', 'wolkal3000'); |
||||
add_settings_field('wolkal3000_timer', 'Synchronisiere alle …', 'wolkal3000_timer_setting_string', 'wolkal3000', 'wolkal3000_timer'); |
||||
|
||||
add_settings_section('wolkal3000_geocoding', 'Geocoding (EXPERIMENTELL)', 'wolkal3000_geocoding_section_text', 'wolkal3000_adv'); |
||||
add_settings_field('wolkal3000_geocoding', 'Geocoding-Methode', 'wolkal3000_geocoding_setting_string', 'wolkal3000_adv', 'wolkal3000_geocoding'); |
||||
|
||||
add_settings_section('wolkal3000_debugging', 'Entwickler*innenoptionen', 'wolkal3000_debugging_section_text', 'wolkal3000_adv'); |
||||
add_settings_field('wolkal3000_debugging', 'Debugging', 'wolkal3000_debugging_setting_string', 'wolkal3000_adv', 'wolkal3000_debugging'); |
||||
} |
||||
|
||||
|
||||
function wolkal3000_feeds_section_text() { |
||||
?> |
||||
<p><b>Wolke-Kalender synchronisieren in eine ausgewählte Terminkategorie von Kal3000. Bitte trage hierfür die entsprechende Export-Adresse des gewünschten Wolke-Kalenders ein.</b></br><b>Falls zu einer Terminkategorie kein Wolke-Kalender synchronisiert werden soll, entsprechendes Feld bitte leer lassen.</b></p> |
||||
<p><b><a href="https://doku.netzbegruenung.de/e/de/wolke/wolke-kalender-webseite" target="_blank">Erfahre mehr darüber, wie du die Export-Adresse eines Wolke-Kalenders findest.</a></b> |
||||
</p> |
||||
<?php |
||||
} |
||||
|
||||
|
||||
|
||||
function wolkal3000_feeds_setting_string($args) { |
||||
$options = get_option('wolkal3000_options'); |
||||
$placeholder = "ABCDEFGHIKLMNOPQ"; |
||||
// die id entspricht dem unique_id in add_settings_field. |
||||
// der name wird options.php als Name der zu setzenden Option übergeben |
||||
// der Value ist der inhalt von der $option[unique_id]. |
||||
echo WOLKAL_PREFIX . '<input type="text" id="' . $args[0] . '" name="wolkal3000_options[' . $args[0] . ']" value="' . $options[$args[0]] . '" size="16" maxlength="16" placeholder="' . $placeholder . '" >' . WOLKAL_SUFFIX . '</br>'; |
||||
} |
||||
|
||||
|
||||
function wolkal3000_timer_section_text() { |
||||
?> |
||||
<p><b>In welcher Regelmäßigkeit sollen die Wolke-Kalender synchronisiert werden? Bitte Zeitintervall in Minuten angeben.</b></br> |
||||
<b>Achtung: Um Änderungen wirksam werden zu lassen, muss das Plugin deaktiviert und wieder aktiviert werden. (Menüpunkt "Plugins")</b></p> |
||||
<?php |
||||
} |
||||
|
||||
|
||||
function wolkal3000_timer_setting_string() { |
||||
$options = get_option('wolkal3000_options'); |
||||
$placeholder = "default 60"; |
||||
echo '<input type="text" id="wolkal3000_timer" name="wolkal3000_options[wolkal3000_timer]" value="' . $options['wolkal3000_timer'] . '" size="6" maxlength="4" placeholder="' . $placeholder . '" > Minuten </br>'; |
||||
} |
||||
|
||||
|
||||
|
||||
function wolkal3000_geocoding_section_text() { |
||||
?> |
||||
<p>Damit der Termin-Ort auf einer Karte eingezeichnet werden kann, müssen die Ortsinformationen von der Textform in geografische Länge und Breite umgerechnet werden.</br>Dies nennt sich Geocoding. Dabei handelt es sich um eine experimentelle Funktion von WolKal3000.</p> |
||||
<?php |
||||
} |
||||
|
||||
|
||||
function wolkal3000_geocoding_setting_string() { |
||||
$options = get_option('wolkal3000_options'); |
||||
$current = ( isset ($options['wolkal3000_geocoding']) ? $options['wolkal3000_geocoding'] : 'off' ); // default off |
||||
|
||||
$coders = array( |
||||
array( |
||||
'option' => 'off', |
||||
'name' => 'deaktiviert', |
||||
), /* |
||||
array( |
||||
'option' => 'official', |
||||
'name' => 'Google official - erfordert einen API Key --> ', |
||||
), |
||||
array( |
||||
'option' => 'inofficial', |
||||
'name' => 'Google inofficial', |
||||
), */ |
||||
array( |
||||
'option' => 'osm', |
||||
'name' => 'OpenStreetMap (EXPERIMENTELL)', |
||||
), |
||||
); |
||||
|
||||
foreach ( $coders as $coder ) { |
||||
$checked = ( $current == $coder['option'] ? 'checked' : '' ); |
||||
echo '<input type="radio" id="wolkal3000_geocoding" name="wolkal3000_options[wolkal3000_geocoding]" value ="' . $coder['option'] . '" ' . $checked . '> ' . $coder['name']; |
||||
echo '</br>' ; |
||||
} |
||||
} |
||||
|
||||
|
||||
function wolkal3000_debugging_section_text() { |
||||
?> |
||||
<p>Debugging aktivieren? Speicherort: ${APACHE_LOG_DIR}/error.log</br></br> |
||||
Um die Performance zu verbessern, werden gefundene Geocoding-Daten zwischengespeichert. </br> |
||||
Zu Debugging-Zwecken kann der Zwischenspeicher (Cache) des Plugins gelöscht </br> |
||||
werden, um ein neues Geocoding aller Termin-Orte zu erzwingen. </br> |
||||
</p> |
||||
<?php |
||||
} |
||||
|
||||
|
||||
|
||||
function wolkal3000_debugging_setting_string($args) { |
||||
$options = get_option('wolkal3000_options'); |
||||
// example from https://code.tutsplus.com/tutorials/the-wordpress-settings-api-part-8-validation-sanitisation-and-input-i--wp-25361 |
||||
// echo '<input type="checkbox" id="wolkal3000_debugging" name="wolkal3000_options[wolkal3000_debugging]" value="1"' . checked( 1, $options['wolkal3000_debugging'], false ) . '> Debug-Logging aktivieren </br>'; |
||||
// let's make a select box: |
||||
?> |
||||
<select id="wolkal3000_debugging" name="wolkal3000_options[wolkal3000_debugging]"> |
||||
<option value=0 <?php selected($options['wolkal3000_debugging'], NONE); ?>>off</option>
|
||||
<option value=1 <?php selected($options['wolkal3000_debugging'], CRIT); ?>>critical</option>
|
||||
<option value=2 <?php selected($options['wolkal3000_debugging'], WARN); ?>>critical + warnings</option>
|
||||
<option value=3 <?php selected($options['wolkal3000_debugging'], INFO); ?>>critical + warnings + info</option>
|
||||
</select> </br> |
||||
<?php |
||||
// actual logging is done by wolkal3000_error_log() |
||||
// Cache reset on restart |
||||
echo '</br><input type="checkbox" id="wolkal3000_reset_cache" name="wolkal3000_options[wolkal3000_reset_cache]" value="1"' . checked( 1, $options['wolkal3000_reset_cache'], false ) . '> Geocoding-Cache nach Deaktivieren und Aktivieren des Plugins löschen </br>';} |
||||
|
||||
|
||||
|
||||
function wolkal3000_options_validate($input) { |
||||
return $input; |
||||
|
||||
// TODO |
||||
|
||||
/* |
||||
$newinput['text_string'] = trim($input['text_string']); |
||||
if(!preg_match('/^[a-z0-9]{32}$/i', $newinput['text_string'])) { |
||||
$newinput['text_string'] = ''; |
||||
} |
||||
return $newinput; |
||||
*/ |
||||
} |
||||
|
||||
|
@ -0,0 +1,10 @@ |
||||
<?php |
||||
define ('WOLKAL_PREFIX', 'https://wolke.netzbegruenung.de/remote.php/dav/public-calendars/'); |
||||
define ('WOLKAL_SUFFIX', '?export'); |
||||
define ('WOLKAL3000_GEO_TABLE', 'wolkal3000_geocache'); |
||||
// For wolkal3000_error_log |
||||
define ('INFO', 3); |
||||
define ('WARN', 2); |
||||
define ('CRIT', 1); |
||||
define ('NONE', 0); |
||||
?> |
@ -0,0 +1,158 @@ |
||||
<?php |
||||
|
||||
defined( 'ABSPATH' ) or die( 'No script kiddies please!' ); |
||||
|
||||
|
||||
function wolkal3000_geocity($location) { |
||||
|
||||
// Wenn die Adresse im Feld Stadt steht, wird sie richtig angezeigt, ergo: |
||||
$pattern = '/(.*), ([0-9]{5} [^,]+)/'; |
||||
preg_match ($pattern, $location, $matches); |
||||
if ( empty ($matches[2])) { |
||||
return ($location); |
||||
} else { |
||||
return ($matches[2]); |
||||
} |
||||
} |
||||
|
||||
|
||||
function wolkal3000_geoshow($location) { |
||||
|
||||
// later |
||||
// not NULL so kal3000_the_termin_geo() displays a map if lat/lon are available. |
||||
// Hotel-Gasthof Maisberger, Bahnhofstraße 54, 85375 Neufahrn bei Freising, Deutschland |
||||
// alles bis ", [0-9]{5}" ist geoshow |
||||
// alles ab [0-9]{5}[\,]+ ist geocity. |
||||
$pattern = '/(.*), ([0-9]{5} [^,]+)/'; |
||||
preg_match ($pattern, $location, $matches); |
||||
return ($matches[1]); |
||||
|
||||
} |
||||
|
||||
|
||||
function wolkal3000_geocode($location) { |
||||
|
||||
// we try to cache results as we will need many times the same results especially for recurring events. |
||||
// we will use a hash for the location because the hash has a fixed length, while the location has not. |
||||
// This table will grow indefinitely over time, so we need to add a timestamp field and remove |
||||
// entries that are older than, say, 30 days each time. |
||||
// this will also cope with Google subtly changing location strings in Maps over time. |
||||
// new entries will thus replace outdated ones over time. |
||||
|
||||
/* |
||||
Caching neu: in wp_options-> wolkal3000_options ein Array geocache anlegen. Darunter für jeden hash ein Array schreiben, also: |
||||
|
||||
Datenmodell: |
||||
|
||||
$geocache = array ( |
||||
hash1 = array ( |
||||
'wolkal3000_geo_lat' => '', |
||||
'wolkal3000_geo_lon' => '', |
||||
'wolkal3000_geo_timestamp' => 0, |
||||
), |
||||
hash2 = array ... |
||||
); |
||||
|
||||
|
||||
Schreiben: |
||||
$options = get_options ( 'wolkal3000_options' ); |
||||
|
||||
$geocache = $options ( 'geocache' ); |
||||
$geocache['hashx'] = array ( $lat, $lon, time(), ); |
||||
|
||||
$options ( 'geocache' ) = $geocache; |
||||
|
||||
Löschen: |
||||
|
||||
foreach ( $geocache as $key => $value ) { |
||||
if ( $key['wolkal3000_geo_timestamp'] < time() - 2592000 ) { |
||||
unset ( $options['geocache']['hashx'] ) |
||||
} |
||||
} |
||||
|
||||
set_options ( 'wolkal3000_options' ); |
||||
|
||||
Suchen: if ( isset ( $options['geocache']['hashx'] ) ) ... |
||||
|
||||
|
||||
|
||||
*/ |
||||
|
||||
|
||||
if ( '' == $location ) { |
||||
return array ('', ''); |
||||
} |
||||
// check the cache first |
||||
global $wpdb; |
||||
$table = $wpdb->prefix.WOLKAL3000_GEO_TABLE; |
||||
$hash = hash ('md5', $location); |
||||
$query = "SELECT wolkal3000_geo_lat, wolkal3000_geo_lon FROM $table WHERE wolkal3000_geo_hash = '$hash'"; |
||||
$result = $wpdb->get_row( $query, ARRAY_N ); |
||||
if ( $wpdb->num_rows == 1 ) { // it should only be a single row! |
||||
wolkal3000_error_log (INFO, "geocode cache hit hash $hash lat $result[0] lon $result[1]"); |
||||
} else { |
||||
// do the housekeeping first, before we create a new caching entry. |
||||
// remove all cache entries which are older than 30 days. |
||||
$outdated = time() - 2592000; // 30 Tage |
||||
$query = "DELETE FROM $table WHERE wolkal3000_geo_timestamp < $outdated"; |
||||
$wpdb->query($query); |
||||
|
||||
$options = get_option('wolkal3000_options'); |
||||
$result = array ('', ''); |
||||
|
||||
switch ( $options['wolkal3000_geocoding'] ) { |
||||
case "osm" : |
||||
$result = wolkal3000_geocode_osm($location); |
||||
break; |
||||
} |
||||
|
||||
$file = dirname (__FILE__) . "/geocode-result-$hash.txt"; |
||||
file_put_contents ($file, var_export ($result, TRUE)); |
||||
// do the caching now, but only if both values are set. |
||||
// $wpdb_insert does all the sanitizing for us. |
||||
$lat = $result[0]; |
||||
$lon = $result[1]; |
||||
if ('' != $lat && '' != $lon) { |
||||
$wpdb->insert($table, array( |
||||
'wolkal3000_geo_location' => substr( $location, 0, 128 ), |
||||
'wolkal3000_geo_hash' => $hash, |
||||
'wolkal3000_geo_lat' => $lat, |
||||
'wolkal3000_geo_lon' => $lon, |
||||
'wolkal3000_geo_timestamp' => time(), |
||||
)); |
||||
wolkal3000_error_log (INFO, "geocoded and cached lat=$lat lon=$lon for location $location"); |
||||
} |
||||
// error handling? |
||||
} |
||||
return ($result); |
||||
} |
||||
|
||||
|
||||
function wolkal3000_geocode_osm($location) { |
||||
// https://wiki.openstreetmap.org/wiki/Nominatim |
||||
// https://nominatim.openstreetmap.org/search?q=Hotel+Gumberger+Gasthof+GmbH&format=json' |
||||
$location = urlencode($location); |
||||
wolkal3000_error_log (INFO, "wolkal3000_geocode_osm: location $location"); |
||||
// the main problem with Nominatim is that it doesn't understand calendar location information very well. |
||||
// we ought to cut off the location name and the country, i.e. zip code, city & street address only |
||||
$url = 'https://nominatim.openstreetmap.org/search?q="' . $location . '"&format=json'; |
||||
$response = wp_remote_get($url); |
||||
$json = wp_remote_retrieve_body($response); |
||||
$http_code = wp_remote_retrieve_response_code($response); |
||||
// we need to catch errors |
||||
|
||||
// https://www.php.net/manual/en/function.json-decode.php |
||||
$decoded = json_decode($json, true); |
||||
// TODO error handling e.g. if we get no usable values. |
||||
/* |
||||
$file = dirname (__FILE__) . '/json-decoded.txt'; |
||||
// should simply be ->lat and -> lon |
||||
file_put_contents ($file, var_export ($decoded, TRUE)); |
||||
// The first array level ([0]) is only needed because OSM returns a JSON with enclosing []. |
||||
*/ |
||||
$lat = $decoded['0']['lat']; |
||||
$lon = $decoded['0']['lon']; |
||||
wolkal3000_error_log (INFO, "wolkal3000_geocode_osm found lat=$lat lon=$lon loc $location"); |
||||
return array ($lat, $lon); |
||||
} |
||||
|
Loading…
Reference in new issue