Compare commits

...

4 Commits
0.3.8 ... main

Author SHA1 Message Date
Christian Tramnitz 577bdc4a77 fix help link 6 months ago
ctr49 44960a2ad4 Revert "cast interval to int in case of unexpected option setting" 2 years ago
Christian Tramnitz 66d399832e cast interval to int in case of unexpected option setting 2 years ago
Christian Tramnitz 3f03ea7cda vorkonfiguriert fuer die Wolke (#13) 2 years ago
  1. 31
      Makefile
  2. 48
      README.md
  3. 201
      gcal-import-admin.php
  4. 229
      gcal-import-geocode.php
  5. 88
      readme.txt
  6. 202
      wolkal3000-admin.php
  7. 10
      wolkal3000-config.php
  8. 158
      wolkal3000-geocode.php
  9. 82
      wolkal3000-worker.php
  10. 96
      wolkal3000.php

@ -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)

@ -2,18 +2,18 @@
Ein Wordpress-Plugin, das auf das Grüne Wordpress-Theme <a href="https://www.urwahl3000.de/">Urwahl3000</a> aufsetzt und eine Integration beliebig vieler öffentlicher ICS-Kalender ermöglicht.
Version: 0.3.8
Version: 0.3.9
## Warnung
Nicht produktiv verwenden. Nur zu Testzwecken.
Das hier ist noch in einem frühen Entwicklungsstadium aber durch die Begrenzung auf eine (vertrauenswürdige!) Quelle, sollte sich möglicher Schaden in Grenzen halten.
## Motivation
Für eine solche Integration gibt es eine Reihe von Motivatoren:
* Manche (viele?) Seitenadmins scheuen dem Umgang mit einem Blog- oder CMS-System. Die Terminpflege auszulagern erleichtert den Admins den Umgang mit dem Kalendersystem und senkt die Hemmschwelle.
* (Öffentliche) ICS-Kalender lassen sich auf einfache Weise auch per Smartphone administrieren. Dazu muss der Admin lediglich in GCal entsprechende Admin-Zugänge z.B. für den Ortssprecher oder den News-Redakteur vergeben. Ebenso lassen sich solche Kalender leicht von jedermann in den eigenen Kalender einbinden, um jederzeit die aktuelle Terminübersicht greifbar zu haben. Eine weiter führende Dokumentation findet sich <a href="https://www.gruene-freising.de/... ">hier</a>.
* Termine werden in Regel bereits in einem Kalender gepflegt. Die Arbeit diese auch noch manuell in das CMS zu übernehmen entfällt.
* Der Umgang mit Terminen im Quellsystem (z.B. der Wolke) ist u.U. leichter handzuhaben als im CMS
* So schön Urwahl3000 ist - der auf wpCalendar basierende kal3000 Kalender unterstützt keine Serientermine. Mit diesem Plugin ist das kein Problem mehr, da es Serientermine im Google Kalender automatisch als Serie von Einzelterminen anlegt.
## Eigenschaften
@ -21,28 +21,32 @@ Für eine solche Integration gibt es eine Reihe von Motivatoren:
* Administration in Wordpress über die Admin-Oberfläche.
* Einbinden beliebig vieler ICS-Kalender.
* Zuordnung dieser ICS-Kalender zu bereits angelegten Terminkategorien, beispielsweise KV Allgemein, AG Klima etc..
* ~~Geocoding von Veranstaltungsorten, wie sie aus Google Kalender übernommen werden. Derart angelegte Termine werden auf der Übersichtskarte richtig angezeigt.~~ - Wollen wir nicht
* Multi-Site fähig
## Voraussetzungen / 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.
2. Als nächstes holt man sich das Plugin unter <a href="https://git.verdigado.com/NB-Public/WolKal3000/releases">https://git.verdigado.com/NB-Public/WolKal3000/releases</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 <a href="https://github.com/OzzyCzech/icalparser">icalparser</a>. Die Verwendung und die Einbindung in die Release-ZIP-Files erfolgt mit freundlicher Genehmigung des Autors Roman Ožana.
Hinweis: WolKal3000 nutzt für das Parsen von ICAL-Files und -Feeds das PHP-Modul icalparser (https://github.com/OzzyCzech/icalparser). Bei einem Clone des Repositories sollte daher rekursiv inkl. Submodules gecloned werden, alternativ muss icalparserer manuell im WolKal3000-Plugin-Verzeichnis installiert werden.
## 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/... (Beispiele folgen).
1. In der Konfiguration (wolkal3000-config.php) sind die globalen Konfigurationsvariablen WOLKAL_PREFIX und WOLKAL_SUFFIX ggf. anzupassen. Für die Grüne Wolke der Netzbegrünung sind die Default Werte ausreichend:
define ('WOLKAL_PREFIX', 'https://wolke.netzbegruenung.de/remote.php/dav/public-calendars/');
define ('WOLKAL_SUFFIX', '?export');
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>.
2. in WP legt man Terminkategorien an, z.B. eine pro Kampagne oder Verband aus dem die Nachrichten kommen.
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.
3. Im Admin-Teil des Plugins unter "Einstellungen / WolKal3000" erscheinen die angelegten Terminkategorien. Jeder Kategorie weist man dann einen öffentlichen (keine Authentifizierung möglich!) Kalender in Form der 16-stelligen öffentlichen Kalender Freigabe-ID zu, beispielsweise "SEZ0123456789ABC".
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.
4. 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.
5. Speichern und fertig.
5. Im Admin-Teil kann man das Geocoding aktivieren. Derzeit ist nur ein experimenteller Weg über OpenStreetMap verfügbar.
6. Speichern und fertig.
Unter "Debugging" finden sich zwei weitere Einstellungen:
@ -50,7 +54,6 @@ Unter "Debugging" finden sich zwei weitere Einstellungen:
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:
@ -64,7 +67,8 @@ Mit "Aktivieren" beginnt das Plugin sofort mit der Synchronisation.
## Proxy-Konfiguration
Das Plugin benötigt den Zugriff nach "draußen", um ICAL-Feeds zu holen oder auf Google Maps zuzugreifen. Wenn Du mit Deinem Wordpress-Server z.B. in einem Firmennetz bist, musst Du möglicherweise über einen Proxy nach draußen gehen. In Wordpress werden Proxy-Einstellungen in <code>/usr/share/wordpress/wp-config.php</code> bzw. auf Ubuntu / Debian in <code>/etc/wordpress/config-SITE.php</code> festgelegt. Zur Dokumentation bitte <a href="https://developer.wordpress.org/reference/classes/wp_http_proxy/">hier entlang</a>.
Das Plugin benötigt den Zugriff nach "draußen", um ICAL-Feeds zu holen oder auf OpenStreetMap zuzugreifen. Wenn Du mit Deinem Wordpress-Server hinter einer Firewall bist, musst Du möglicherweise über einen Proxy nach draußen gehen. In Wordpress werden Proxy-Einstellungen in <code>/usr/share/wordpress/wp-config.php</code> bzw. auf Ubuntu / Debian in <code>/etc/wordpress/config-SIT
E.php</code> festgelegt. Zur Dokumentation bitte <a href="https://developer.wordpress.org/reference/classes/wp_http_proxy/">hier entlang</a>.
## Support
@ -72,23 +76,9 @@ Bitte ein Ticket (issue) auf https://git.verdigado.com/NB-Public/WolKal3000/issu
## Bekannte Fehler
Vermutlich viele. Ich bin alles andere als ein begnadeter Programmierer.
siehe Support
## Internationalization
Since this plugin is only relevant for people using the Urwahl3000 theme, and this includes only members of Bündnis 90 / Die Grünen, the user interface of the plugin will only be available in German. Should a demand for other languages arise, feel free to contact me - contributions welcome! :-)

@ -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);
}

@ -7,7 +7,7 @@ defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
// we'll set this as category => proxy, link => link, active => 0;
// in the admin page, all entries will be displayed with a checkbox for activating, deactivating, deleting.
require_once __DIR__ . "/gcal-import-geocode.php";
require_once __DIR__ . "/wolkal3000-geocode.php";
/**
@ -17,7 +17,7 @@ require_once __DIR__ . "/gcal-import-geocode.php";
*
*/
function gcal_import_worker() {
function wolkal3000_worker() {
/*
* retrieve the proxy from the db, and if it exists, construct a context.
@ -26,16 +26,16 @@ function gcal_import_worker() {
* http://www.pirob.com/2013/06/php-using-getheaders-and-filegetcontents-functions-behind-proxy.html
*/
gcal_error_log (INFO, __FUNCTION__ . " started");
$options = get_option('gcal_options');
wolkal3000_error_log (INFO, __FUNCTION__ . " started");
$options = get_option('wolkal3000_options');
$terms = get_terms( array(
'taxonomy' => 'termine_type',
'hide_empty' => false, )
);
foreach($terms as $term){
$unique_id = 'gcal_feed_' . $term->name;
$unique_id = 'wolkal3000_feed_' . $term->name;
if ( empty ( $options[$unique_id] ) || $options[$unique_id] == '' ) {
gcal_error_log (INFO, "link for event category $term->name is not known; next");
wolkal3000_error_log (INFO, "link for event category $term->name is not known; next");
continue;
}
@ -50,11 +50,11 @@ The update and delete logic goes as follows:
Apparently, they were deleted on the remote end.
*/
// so we look for all published event posts in the GCal event category
// so we look for all published event posts in the calendar event category
$args = array (
'post_type' => 'termine',
'post_status' => 'publish',
'meta_key' => '_gcal_category',
'meta_key' => '_wolkal3000_category',
'meta_value' => $term->name,
);
$post_ids = get_posts( $args );
@ -62,13 +62,13 @@ The update and delete logic goes as follows:
if(is_array($post_ids)) {
foreach( $post_ids as $post_id ) {
$id = $post_id->ID;
update_post_meta( $id, '_gcal_recent', 'false' );
update_post_meta( $id, '_wolkal3000_recent', 'false' );
}
}
// now we process the current feed.
$link = $options[$unique_id];
gcal_error_log (INFO, "importing event category $term->name");
gcal_import_do_import($term->name, $link);
$link = WOLKAL_PREFIX . $options[$unique_id] . WOLKAL_SUFFIX;
wolkal3000_error_log (INFO, "importing event category $term->name");
wolkal3000_do_import($term->name, $link);
// look if there are any published event posts in the current event category which were not posted anew or updated (ie recent == false)
$args = array (
@ -76,11 +76,11 @@ The update and delete logic goes as follows:
'post_status' => 'publish',
'meta_query' => array(
array(
'key' => '_gcal_category',
'key' => '_wolkal3000_category',
'value' => $term->name,
),
array(
'key' => '_gcal_recent',
'key' => '_wolkal3000_recent',
'value' => 'false',
),
)
@ -91,14 +91,14 @@ The update and delete logic goes as follows:
foreach( $post_ids as $post_id ) {
$id = $post_id->ID;
wp_trash_post( $id );
gcal_error_log (INFO, "Event post $id gelöscht.");
wolkal3000_error_log (INFO, "Event post $id gelöscht.");
}
}
}
gcal_error_log (INFO, __FUNCTION__ . " finished");
wolkal3000_error_log (INFO, __FUNCTION__ . " finished");
}
add_action( 'gcal_import_worker_hook', 'gcal_import_worker' );
add_action( 'wolkal3000_worker_hook', 'wolkal3000_worker' );
require_once __DIR__ . '/icalparser/src/IcalParser.php';
@ -116,7 +116,7 @@ function curl_get_remote($url) {
if ( curl_errno($ch) ) {
// $info = curl_getinfo($ch);
$message = __FUNCTION__ . ": cURL error " . curl_error($ch);
// gcal_error_log (WARN, $message);
// wolkal3000_error_log (WARN, $message);
curl_close($ch);
throw new \RuntimeException($message);
}
@ -126,7 +126,7 @@ function curl_get_remote($url) {
}
function gcal_import_do_import($category, $link) {
function wolkal3000_do_import($category, $link) {
$my_latlon = array('', '');
$cal = new \om\IcalParser();
@ -152,7 +152,7 @@ function gcal_import_do_import($category, $link) {
if ($r['DTEND'] < $now) {
continue;
} else {
gcal_error_log (INFO, "processing $summary on $dtstart");
wolkal3000_error_log (INFO, "processing $summary on $dtstart");
}
// The zeitstempel. No idea what it's for, but kal3000 seems to use it.
@ -176,11 +176,11 @@ function gcal_import_do_import($category, $link) {
}
// geocoden
$options = get_option('gcal_options');
if ( $options['gcal_geocoding'] != "off" ) {
$options = get_option('wolkal3000_options');
if ( $options['wolkal3000_geocoding'] != "off" ) {
$location = urldecode ($r['LOCATION']);
$my_latlon = gcal_import_geocode($location);
$my_latlon = wolkal3000_geocode($location);
$file = dirname (__FILE__) . "/latlon-$hash.txt";
file_put_contents ($file, var_export ($my_latlon, TRUE));
}
@ -222,7 +222,7 @@ function gcal_import_do_import($category, $link) {
// create image attachment and associate with new post
$attach = $r['ATTACH'];
$summary = $r['SUMMARY'];
gcal_error_log (INFO, "found attachment $attach for $summary");
wolkal3000_error_log (INFO, "found attachment $attach for $summary");
}
if ( isset ( $r['CLASS'] ) && 'PRIVATE' == $r['CLASS']) {
@ -260,20 +260,20 @@ function gcal_import_do_import($category, $link) {
$post->meta_input = array(
'_wpcal_from' => $r['DTSTART']->format('d.m.Y H:i'),
'_bis' => $r['DTEND']->format('d.m.Y H:i'),
'_geostadt' => gcal_import_geocity($r['LOCATION']),
'_geoshow' => gcal_import_geoshow($r['LOCATION']),
'_geostadt' => wolkal3000_geocity($r['LOCATION']),
'_geoshow' => wolkal3000_geoshow($r['LOCATION']),
'_lat' => $my_latlon[0],
'_lon' => $my_latlon[1],
'_zoom' => '7',
'_veranstalter' => '',
'_veranstalterlnk' => '',
'_zeitstempel' => $zeitstempel,
'_gcal_uid' => $r['UID'],
'_gcal_recent' => 'true',
// '_gcal_created' => $r['LAST-MODIFIED']->format('U'),
// '_gcal_created' => $r['LAST-MODIFIED']->format('d.m.Y H:i'),
// '_gcal_created' => $r['LAST-MODIFIED']->format('U'),
'_gcal_category' => $category,
'_wolkal3000_uid' => $r['UID'],
'_wolkal3000_recent' => 'true',
// '_wolkal3000_created' => $r['LAST-MODIFIED']->format('U'),
// '_wolkal3000_created' => $r['LAST-MODIFIED']->format('d.m.Y H:i'),
// '_wolkal3000_created' => $r['LAST-MODIFIED']->format('U'),
'_wolkal3000_category' => $category,
'_secretevent' => $secretevent,
);
@ -285,7 +285,7 @@ function gcal_import_do_import($category, $link) {
'post_status' => 'publish',
'meta_query' => array(
array(
'key' => '_gcal_uid',
'key' => '_wolkal3000_uid',
'value' => $r['UID'],
),
array(
@ -302,7 +302,7 @@ function gcal_import_do_import($category, $link) {
$post_id = wp_insert_post( $post );
if ( is_wp_error( $post_id ) ) {
$message = $post_id->get_error_message();
gcal_error_log ( WARN, $message );
wolkal3000_error_log ( WARN, $message );
} else {
update_post_meta( $post_id, '_edit_last', $user_id );
$now = time();
@ -310,12 +310,12 @@ function gcal_import_do_import($category, $link) {
update_post_meta( $post_id, '_edit_lock', $lock );
// and assign the taxonomy type and event category.
wp_set_object_terms( $post_id, $category, 'termine_type' );
gcal_error_log (INFO, "posted new post $post_id");
wolkal3000_error_log (INFO, "posted new post $post_id");
}
} else {
// good, the post exists already.
$id = $post_ids[0]->ID;
$created = get_post_meta( $id, '_gcal_created', true );
$created = get_post_meta( $id, '_wolkal3000_created', true );
$lastmodified = $r['LAST-MODIFIED']->format('U');
// was it updated on the remote calendar? (was if modified after it was created remotely?)
if ( $lastmodified > $created ) {
@ -323,17 +323,17 @@ function gcal_import_do_import($category, $link) {
$post->ID = $id ;
$post_id = wp_update_post( $post, false );
// and update the _created field
update_post_meta ( $id, '_gcal_created', $lastmodified );
gcal_error_log (INFO, "updated post $post_id");
update_post_meta ( $id, '_wolkal3000_created', $lastmodified );
wolkal3000_error_log (INFO, "updated post $post_id");
} elseif ( $lastmodified < $created ) {
// iiiiek! A time reversal or a secret time machine! That should not happen!
gcal_error_log (WARN, "post $id last-modified : created $lastmodified < $created ");
wolkal3000_error_log (WARN, "post $id last-modified : created $lastmodified < $created ");
} // else both are equal, and we do nothing except setting recent to true.
update_post_meta ( $id, '_gcal_recent', 'true' );
update_post_meta ( $id, '_wolkal3000_recent', 'true' );
}
} else {
$file = dirname (__FILE__) . '/get_posts-' . $post->post_name . '.txt';
gcal_error_log (WARN, "hmmm, get_posts() did not return an array. Logging to $file");
wolkal3000_error_log (WARN, "hmmm, get_posts() did not return an array. Logging to $file");
file_put_contents ($file, var_export ($post_ids, TRUE));
}
// and on the next entry.

@ -3,8 +3,8 @@
* Plugin Name: WolKal3000 – Termin-Synchronisation
* Plugin URI: https://git.netzbegruenung.de/NB-Public/WolKal3000/
* Description: Synchronisation des Kal3000-Plugins mit Wolke-Kalendern und ICS-Dateien
* Version: 0.3.8
* Author: Harald Milz & Netzbegrünung <hm@seneca.muc.de>
* Version: 0.3.9
* Author: Harald Milz & Netzbegrünung e.V.
* License: GPLv3
* License URI: https://www.gnu.org/licenses/gpl-3.0
* Domain Path: /languages
@ -25,30 +25,20 @@
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
define ('GCAL_GEO_TABLE', 'gcal_import_geocache');
// For gcal_error_log
define ('INFO', 3);
define ('WARN', 2);
define ('CRIT', 1);
define ('NONE', 0);
// The real work goes here.
require_once dirname( __FILE__ ) . "/gcal-import-worker.php";
require_once dirname( __FILE__ ) . "/gcal-import-admin.php";
require_once dirname( __FILE__ ) . "/wolkal3000-worker.php";
require_once dirname( __FILE__ ) . "/wolkal3000-admin.php";
// create custom scheduler from custom option
add_filter( 'cron_schedules', 'gcal_cron_interval' );
add_filter( 'cron_schedules', 'wolkal3000_cron_interval' );
function gcal_cron_interval( $schedules ) {
$options = get_option('gcal_options');
$current = ( isset ($options['gcal_timer']) ? $options['gcal_timer'] : 60 ); // default 60 minutes
function wolkal3000_cron_interval( $schedules ) {
$options = get_option('wolkal3000_options');
$current = ( isset ($options['wolkal3000_timer']) ? $options['wolkal3000_timer'] : 60 ); // default 60 minutes
$interval = 60 * $current; // wir speichern Minuten
$schedules['gcal_interval'] = array(
$schedules['wolkal3000_interval'] = array(
'interval' => $interval,
'display' => esc_html__( 'GCal fetch interval' ),
'display' => esc_html__( 'Calendar fetch interval' ),
);
return $schedules;
}
@ -60,47 +50,47 @@ function gcal_cron_interval( $schedules ) {
*
* @since 0.1.0
*
* - gcal_category - name of the calendar, for later per-unit display
* - gcal_link - the public or private .ics link
* - gcal_veranstalter - ?
* - gcal_active - flag if a calendar is active or not. Default active.
* - wolkal3000_category - name of the calendar, for later per-unit display
* - wolkal3000_link - the public or private .ics link
* - wolkal3000_veranstalter - ?
* - wolkal3000_active - flag if a calendar is active or not. Default active.
*
* Since there is no install hook in WP, we will use the activation hook for both.
*/
function gcal_import_activate()
function wolkal3000_activate()
{
global $wpdb;
// CREATE geocoding caching table if it does not exist already.
// the location field will be used only during development and debugging, and will be omitted in production.
$table = $wpdb->prefix.GCAL_GEO_TABLE;
$table = $wpdb->prefix.WOLKAL3000_GEO_TABLE;
$query = "CREATE TABLE IF NOT EXISTS $table (
id INT(9) NOT NULL AUTO_INCREMENT,
gcal_geo_location VARCHAR(128) NOT NULL,
gcal_geo_hash VARCHAR(40) NOT NULL,
gcal_geo_lat VARCHAR(20) NOT NULL,
gcal_geo_lon VARCHAR(20) NOT NULL,
gcal_geo_timestamp INT(16) NOT NULL,
wolkal3000_geo_location VARCHAR(128) NOT NULL,
wolkal3000_geo_hash VARCHAR(40) NOT NULL,
wolkal3000_geo_lat VARCHAR(20) NOT NULL,
wolkal3000_geo_lon VARCHAR(20) NOT NULL,
wolkal3000_geo_timestamp INT(16) NOT NULL,
UNIQUE KEY id (id)
);";
$wpdb->query($query);
// and start the scheduler;
if ( ! wp_next_scheduled( 'gcal_import_worker_hook' ) ) {
wp_schedule_event( time(), 'gcal_interval', 'gcal_import_worker_hook' );
if ( ! wp_next_scheduled( 'wolkal3000_worker_hook' ) ) {
wp_schedule_event( time(), 'wolkal3000_interval', 'wolkal3000_worker_hook' );
}
gcal_error_log (INFO, "gcal_import activated");
wolkal3000_error_log (INFO, "wolkal3000 activated");
// empty geocode cache if option is set.
$options = get_option('gcal_options');
if ( isset ( $options['gcal_reset_cache'] ) && '1' == $options['gcal_reset_cache'] ) {
$options = get_option('wolkal3000_options');
if ( isset ( $options['wolkal3000_reset_cache'] ) && '1' == $options['wolkal3000_reset_cache'] ) {
$wpdb->query("DELETE IGNORE FROM $table WHERE 1=1");
gcal_error_log (INFO, "emptied geocoding cache");
wolkal3000_error_log (INFO, "emptied geocoding cache");
}
}
register_activation_hook( __FILE__, 'gcal_import_activate' );
register_activation_hook( __FILE__, 'wolkal3000_activate' );
/**
@ -110,14 +100,14 @@ register_activation_hook( __FILE__, 'gcal_import_activate' );
*
*/
function gcal_import_deactivate()
function wolkal3000_deactivate()
{
// clean up! Many plugins forget the housekeeping when deactivating.
wp_clear_scheduled_hook('gcal_import_worker_hook');
gcal_error_log (INFO, "gcal_import deactivated");
wp_clear_scheduled_hook('wolkal3000_worker_hook');
wolkal3000_error_log (INFO, "wolkal3000 deactivated");
}
register_deactivation_hook( __FILE__, 'gcal_import_deactivate' );
register_deactivation_hook( __FILE__, 'wolkal3000_deactivate' );
/**
@ -127,21 +117,21 @@ register_deactivation_hook( __FILE__, 'gcal_import_deactivate' );
*
*/
function gcal_import_uninstall()
function wolkal3000_uninstall()
{
// clean up! Many plugins forget the housekeeping when uninstalling.
gcal_error_log (INFO, "uninstalling gcal_import");
wolkal3000_error_log (INFO, "uninstalling wolkal3000");
// can we uninstall without deactivating first?
// gcal_import_deactivate;
// wolkal3000_deactivate;
global $wpdb;
// drop the geocache table
$table = $wpdb->prefix.GCAL_GEO_TABLE ;
$table = $wpdb->prefix.WOLKAL3000_GEO_TABLE ;
$wpdb->query( "DROP TABLE IF EXISTS $table" );
// and the options.
delete_option ( 'gcal_options' );
delete_option ( 'wolkal3000_options' );
}
register_uninstall_hook( __FILE__, 'gcal_import_uninstall' );
register_uninstall_hook( __FILE__, 'wolkal3000_uninstall' );
/*
@ -150,12 +140,12 @@ register_uninstall_hook( __FILE__, 'gcal_import_uninstall' );
* @since 0.3.0
*/
function gcal_error_log($level, $args) {
function wolkal3000_error_log($level, $args) {
$levels = array ( 'NONE', 'CRIT', 'WARN', 'INFO' );
$options = get_option('gcal_options');
if ( isset ( $options['gcal_debugging'] )) {
if ( $level <= (int) $options['gcal_debugging'] ) {
error_log ( "GCal [" . $levels[$level] . "] " . $args );
$options = get_option('wolkal3000_options');
if ( isset ( $options['wolkal3000_debugging'] )) {
if ( $level <= (int) $options['wolkal3000_debugging'] ) {
error_log ( "WolKal3000 [" . $levels[$level] . "] " . $args );
}
}
}
Loading…
Cancel
Save