Compare commits

..

50 commits

Author SHA1 Message Date
Sebastian Steinbach 87450bc719 Dateien hochladen nach „“ 2020-06-03 19:01:19 +02:00
Willi Junga ec72218e92
Revert "version bump"
This reverts commit f53fb60e32.
2020-05-31 14:00:01 +02:00
Willi Junga 99d0237929
Revert "test"
This reverts commit 2cccc4c423.
2020-05-31 13:59:34 +02:00
Willi Junga 58b2428927
Revert "removed gmaps"
This reverts commit 3d9be7445e.
2020-05-31 13:59:32 +02:00
Willi Junga 7981403067
Revert "elf"
This reverts commit 621a8457d8.
2020-05-31 13:59:29 +02:00
Willi Junga d1b1d77054
Revert "zwölf"
This reverts commit 73c0b7dd6f.
2020-05-31 13:59:21 +02:00
Willi Junga 885cf17555
Revert "vierzehn"
This reverts commit 2a6b894460.
2020-05-31 13:25:25 +02:00
Willi Junga f461f42f88
Revert "sechzehn"
This reverts commit f38a94ee09.
2020-05-31 13:25:09 +02:00
Willi Junga f38a94ee09
sechzehn 2020-05-31 13:22:42 +02:00
Willi Junga 2a6b894460
vierzehn 2020-05-31 01:35:15 +02:00
Willi Junga 861196e39c Merge pull request 'patch' (#11) from patch into master 2020-05-31 01:30:14 +02:00
Willi Junga 6519ffb541
Merge branch 'devel' 2020-05-31 01:27:52 +02:00
Willi Junga 73c0b7dd6f
zwölf 2020-05-31 01:27:34 +02:00
Willi Junga 5b769164d0 Merge pull request 'patch' (#10) from patch into master 2020-05-31 01:14:31 +02:00
Willi Junga 4d89a09704
Merge branch 'willi' 2020-05-31 01:12:32 +02:00
Willi Junga 621a8457d8
elf 2020-05-31 01:12:20 +02:00
Willi Junga 9f0e72ee05
Merge remote-tracking branch 'origin/master' 2020-05-31 00:49:57 +02:00
Willi Junga 3d9be7445e
removed gmaps 2020-05-31 00:48:11 +02:00
Willi Junga 95a0ed2789 Merge pull request 'test' (#9) from patch into master 2020-05-31 00:46:09 +02:00
Willi Junga 2cccc4c423
test 2020-05-31 00:38:45 +02:00
Willi Junga a2e44e0182 Merge pull request 'version bump' (#8) from devel-wj into master 2020-05-31 00:10:43 +02:00
Willi Junga f53fb60e32
version bump 2020-05-30 23:23:17 +02:00
Willi Junga c717801cd4 Merge pull request 'devel-wj' (#7) from devel-wj into master 2020-05-30 23:05:18 +02:00
Willi Junga b2fbc4ed09
Merge remote-tracking branch 'origin/master' into devel-wj 2020-05-30 23:01:22 +02:00
Willi Junga 1410ca5ceb
try again 2020-05-30 23:00:22 +02:00
Willi Junga 4bc3feb8f9 Merge pull request 'devel-wj' (#6) from devel-wj into master 2020-05-30 22:50:33 +02:00
Willi Junga 39db039472
php version hmm 2020-05-30 22:37:25 +02:00
Willi Junga 68cfee339b
Merge remote-tracking branch 'origin/master' into devel-wj 2020-05-30 22:34:36 +02:00
Willi Junga f080741204
removed gitignore 2020-05-30 22:30:12 +02:00
Willi Junga 4246eef0fb Merge pull request 'cloned icalparser' (#5) from devel-wj into master 2020-05-30 22:29:33 +02:00
Willi Junga 88f2084833
cloned icalparser 2020-05-30 22:27:27 +02:00
Willi Junga 1c2922ce74 Merge pull request 'devel-wj' (#4) from devel-wj into master 2020-05-30 20:20:18 +02:00
Willi Junga 655bbe7919
Merge branch 'master' of git.verdigado.com:/NB-Public/WolKal3000 2020-05-30 19:21:01 +02:00
Willi Junga c8d1895839
immerwieder google 2020-05-30 19:19:25 +02:00
Willi Junga 0fed9238f4
what the 2020-05-30 18:57:46 +02:00
Willi Junga 0f5031b730
ejfie2 2020-05-30 18:54:31 +02:00
Willi Junga a67ac39c56
hmm 2020-05-30 18:48:52 +02:00
Willi Junga ad7c607643
warnung 2020-05-30 18:31:15 +02:00
Willi Junga b4b87d0e45 Merge pull request 'devel-wj' (#3) from devel-wj into master 2020-05-30 18:23:54 +02:00
Willi Junga 5eeaf6bc2d
markdowning 2020-05-30 18:20:36 +02:00
Willi Junga ca7a90f095
removing more gooogle
Signed-off-by: willi.junga <willi.junga@gruene-treptow-koepenick.de>
2020-05-30 18:14:09 +02:00
Willi Junga 24d61af4fb
google must go 2020-05-30 18:12:32 +02:00
Willi Junga cd51297c70
hm 2020-05-30 18:10:57 +02:00
Willi Junga 668dccf8ab Merge pull request 'URLs' (#2) from devel-wj into master 2020-05-30 18:08:54 +02:00
Willi Junga f060b2efaf
URLs 2020-05-30 18:06:46 +02:00
Willi Junga f545ed2be1
version in readme 2020-05-30 15:26:19 +02:00
Willi Junga 777e573ef2
URL of Urwahl3000 2020-05-30 15:01:39 +02:00
Willi Junga 98e860e4de
empty spaces removed 2020-05-30 14:51:12 +02:00
Willi Junga 0c453f3029
version correction 2020-05-30 14:43:07 +02:00
Willi Junga 408e75a3eb
changed project name in Makefile 2020-05-30 14:01:34 +02:00
13 changed files with 783 additions and 591 deletions

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "icalparser"]
path = icalparser
url = https://github.com/OzzyCzech/icalparser.git

31
Makefile Normal file
View file

@ -0,0 +1,31 @@
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)

View file

@ -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.9
Version: 0.3.8
## Warnung
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.
Nicht produktiv verwenden. Nur zu Testzwecken.
## Motivation
Für eine solche Integration gibt es eine Reihe von Motivatoren:
* 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
* 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>.
* 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,32 +21,28 @@ 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..
* Multi-Site fähig
* ~~Geocoding von Veranstaltungsorten, wie sie aus Google Kalender übernommen werden. Derart angelegte Termine werden auf der Übersichtskarte richtig angezeigt.~~ - Wollen wir nicht
## 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="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.
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: 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.
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.
## Konfiguration
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');
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).
2. in WP legt man Terminkategorien an, z.B. eine pro Kampagne oder Verband aus dem die Nachrichten kommen.
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 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".
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 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. Im Admin-Teil kann man das Geocoding aktivieren. Derzeit ist nur ein experimenteller Weg über OpenStreetMap verfügbar.
6. Speichern und fertig.
5. Speichern und fertig.
Unter "Debugging" finden sich zwei weitere Einstellungen:
@ -54,6 +50,7 @@ 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:
@ -67,8 +64,7 @@ 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 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>.
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>.
## Support
@ -76,9 +72,23 @@ Bitte ein Ticket (issue) auf https://git.verdigado.com/NB-Public/WolKal3000/issu
## Bekannte Fehler
siehe Support
Vermutlich viele. Ich bin alles andere als ein begnadeter Programmierer.
## 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! :-)

201
gcal-import-admin.php Normal file
View file

@ -0,0 +1,201 @@
<?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( 'GCal Importer Einstellungen', 'GCal Importer', '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>
<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'); ?>" />
</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', 'Terminkategorien und ICS-Feeds', '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, $feed_name, 'gcal_feeds_setting_string', 'gcal', 'gcal_feeds', array($unique_id));
}
add_settings_section('gcal_timer', 'Zeitintervall', 'gcal_timer_section_text', 'gcal');
add_settings_field('gcal_timer', 'Zeitintervall', 'gcal_timer_setting_string', 'gcal', 'gcal_timer');
add_settings_section('gcal_geocoding', 'Geocoding', 'gcal_geocoding_section_text', 'gcal');
add_settings_field('gcal_geocoding', 'Geocoding', 'gcal_geocoding_setting_string', 'gcal', 'gcal_geocoding');
add_settings_section('gcal_debugging', 'Debugging', 'gcal_debugging_section_text', 'gcal');
add_settings_field('gcal_debugging', 'Debugging', 'gcal_debugging_setting_string', 'gcal', 'gcal_debugging');
}
function gcal_feeds_section_text() {
?>
<p><b>Bitte hier die zu den Terminkategorien gehörigen Feeds eintragen (copy & paste!).</b></br>
<b>Wenn zu einer Terminkategorie kein Feed gehört, einfach leer lassen.</b></p>
<?php
}
function gcal_feeds_setting_string($args) {
$options = get_option('gcal_options');
$placeholder = "z.B. https://calendar.google.com/calendar/ical/.../public/basic.ics";
// 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>Zeitintervall in Minuten, in dem die Feeds synchronisiert werden sollen.</b></br>
<b>Neu setzen erfordert einen Neustart des Plugins (Deaktivieren / Aktivieren).</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><b>Um Termine auf der Karte zu sehen, ist es nötig, die Orte zu geocoden, d.h. </br>
deren geografische Länge und Breite herauszufinden. Dafür sind mehrere </br>
Verfahren wählbar. </br>
</b></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 - in Entwicklung',
),
);
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><b>Debugging aktivieren (landet in ${APACHE_LOG_DIR}/error.log).</br>
Um die Performance zu verbessern, werden gefundene Geocoding-Daten zwischengespeichert. </br>
Zu Debugging-Zwecken kann der Zwischenspeicher (Cache) beim Neustart des Plugins gelöscht </br>
werden, um ein neues Geocoding aller Event-Lokationen zu erzwingen. </br>
</b></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 '<input type="checkbox" id="gcal_reset_cache" name="gcal_options[gcal_reset_cache]" value="1"' . checked( 1, $options['gcal_reset_cache'], false ) . '> Geocoding-Cache bei Neustart 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;
*/
}

229
gcal-import-geocode.php Normal file
View file

@ -0,0 +1,229 @@
<?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]);
}

View file

@ -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__ . "/wolkal3000-geocode.php";
require_once __DIR__ . "/gcal-import-geocode.php";
/**
@ -17,7 +17,7 @@ require_once __DIR__ . "/wolkal3000-geocode.php";
*
*/
function wolkal3000_worker() {
function gcal_import_worker() {
/*
* retrieve the proxy from the db, and if it exists, construct a context.
@ -26,16 +26,16 @@ function wolkal3000_worker() {
* http://www.pirob.com/2013/06/php-using-getheaders-and-filegetcontents-functions-behind-proxy.html
*/
wolkal3000_error_log (INFO, __FUNCTION__ . " started");
$options = get_option('wolkal3000_options');
gcal_error_log (INFO, __FUNCTION__ . " started");
$options = get_option('gcal_options');
$terms = get_terms( array(
'taxonomy' => 'termine_type',
'hide_empty' => false, )
);
foreach($terms as $term){
$unique_id = 'wolkal3000_feed_' . $term->name;
$unique_id = 'gcal_feed_' . $term->name;
if ( empty ( $options[$unique_id] ) || $options[$unique_id] == '' ) {
wolkal3000_error_log (INFO, "link for event category $term->name is not known; next");
gcal_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 calendar event category
// so we look for all published event posts in the GCal event category
$args = array (
'post_type' => 'termine',
'post_status' => 'publish',
'meta_key' => '_wolkal3000_category',
'meta_key' => '_gcal_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, '_wolkal3000_recent', 'false' );
update_post_meta( $id, '_gcal_recent', 'false' );
}
}
// now we process the current feed.
$link = WOLKAL_PREFIX . $options[$unique_id] . WOLKAL_SUFFIX;
wolkal3000_error_log (INFO, "importing event category $term->name");
wolkal3000_do_import($term->name, $link);
$link = $options[$unique_id];
gcal_error_log (INFO, "importing event category $term->name");
gcal_import_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' => '_wolkal3000_category',
'key' => '_gcal_category',
'value' => $term->name,
),
array(
'key' => '_wolkal3000_recent',
'key' => '_gcal_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 );
wolkal3000_error_log (INFO, "Event post $id gelöscht.");
gcal_error_log (INFO, "Event post $id gelöscht.");
}
}
}
wolkal3000_error_log (INFO, __FUNCTION__ . " finished");
gcal_error_log (INFO, __FUNCTION__ . " finished");
}
add_action( 'wolkal3000_worker_hook', 'wolkal3000_worker' );
add_action( 'gcal_import_worker_hook', 'gcal_import_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);
// wolkal3000_error_log (WARN, $message);
// gcal_error_log (WARN, $message);
curl_close($ch);
throw new \RuntimeException($message);
}
@ -126,7 +126,7 @@ function curl_get_remote($url) {
}
function wolkal3000_do_import($category, $link) {
function gcal_import_do_import($category, $link) {
$my_latlon = array('', '');
$cal = new \om\IcalParser();
@ -152,7 +152,7 @@ function wolkal3000_do_import($category, $link) {
if ($r['DTEND'] < $now) {
continue;
} else {
wolkal3000_error_log (INFO, "processing $summary on $dtstart");
gcal_error_log (INFO, "processing $summary on $dtstart");
}
// The zeitstempel. No idea what it's for, but kal3000 seems to use it.
@ -176,14 +176,10 @@ function wolkal3000_do_import($category, $link) {
}
// geocoden
$options = get_option('wolkal3000_options');
if ( $options['wolkal3000_geocoding'] != "off" ) {
$location = urldecode ($r['LOCATION']);
$my_latlon = wolkal3000_geocode($location);
$file = dirname (__FILE__) . "/latlon-$hash.txt";
file_put_contents ($file, var_export ($my_latlon, TRUE));
}
$location = urldecode ($r['LOCATION']);
$my_latlon = gcal_import_geocode($location);
$file = dirname (__FILE__) . "/latlon-$hash.txt";
file_put_contents ($file, var_export ($my_latlon, TRUE));
// create a default form
// $post = get_default_post_to_edit ('termine', false);
@ -222,7 +218,7 @@ function wolkal3000_do_import($category, $link) {
// create image attachment and associate with new post
$attach = $r['ATTACH'];
$summary = $r['SUMMARY'];
wolkal3000_error_log (INFO, "found attachment $attach for $summary");
gcal_error_log (INFO, "found attachment $attach for $summary");
}
if ( isset ( $r['CLASS'] ) && 'PRIVATE' == $r['CLASS']) {
@ -260,20 +256,20 @@ function wolkal3000_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' => wolkal3000_geocity($r['LOCATION']),
'_geoshow' => wolkal3000_geoshow($r['LOCATION']),
'_geostadt' => gcal_import_geocity($r['LOCATION']),
'_geoshow' => gcal_import_geoshow($r['LOCATION']),
'_lat' => $my_latlon[0],
'_lon' => $my_latlon[1],
'_zoom' => '7',
'_veranstalter' => '',
'_veranstalterlnk' => '',
'_zeitstempel' => $zeitstempel,
'_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,
'_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,
'_secretevent' => $secretevent,
);
@ -285,7 +281,7 @@ function wolkal3000_do_import($category, $link) {
'post_status' => 'publish',
'meta_query' => array(
array(
'key' => '_wolkal3000_uid',
'key' => '_gcal_uid',
'value' => $r['UID'],
),
array(
@ -302,7 +298,7 @@ function wolkal3000_do_import($category, $link) {
$post_id = wp_insert_post( $post );
if ( is_wp_error( $post_id ) ) {
$message = $post_id->get_error_message();
wolkal3000_error_log ( WARN, $message );
gcal_error_log ( WARN, $message );
} else {
update_post_meta( $post_id, '_edit_last', $user_id );
$now = time();
@ -310,12 +306,12 @@ function wolkal3000_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' );
wolkal3000_error_log (INFO, "posted new post $post_id");
gcal_error_log (INFO, "posted new post $post_id");
}
} else {
// good, the post exists already.
$id = $post_ids[0]->ID;
$created = get_post_meta( $id, '_wolkal3000_created', true );
$created = get_post_meta( $id, '_gcal_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 +319,17 @@ function wolkal3000_do_import($category, $link) {
$post->ID = $id ;
$post_id = wp_update_post( $post, false );
// and update the _created field
update_post_meta ( $id, '_wolkal3000_created', $lastmodified );
wolkal3000_error_log (INFO, "updated post $post_id");
update_post_meta ( $id, '_gcal_created', $lastmodified );
gcal_error_log (INFO, "updated post $post_id");
} elseif ( $lastmodified < $created ) {
// iiiiek! A time reversal or a secret time machine! That should not happen!
wolkal3000_error_log (WARN, "post $id last-modified : created $lastmodified < $created ");
gcal_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, '_wolkal3000_recent', 'true' );
update_post_meta ( $id, '_gcal_recent', 'true' );
}
} else {
$file = dirname (__FILE__) . '/get_posts-' . $post->post_name . '.txt';
wolkal3000_error_log (WARN, "hmmm, get_posts() did not return an array. Logging to $file");
gcal_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.

162
gcal-import.php Normal file
View file

@ -0,0 +1,162 @@
<?php
/**
* Plugin Name: Kal3000 Google Calender Importer
* Plugin URI: https://github.com/hmilz/kal3000-gcal-import
* Description: Imports and Merges an Arbitrary Number of Public Google Calendars into Kal3000
* Version: 0.2.0
* Author: Harald Milz <hm@seneca.muc.de>
* License: GPLv3
* License URI: https://www.gnu.org/licenses/gpl-3.0
* Domain Path: /languages
*
* {Plugin Name} is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* any later version.
*
* {Plugin Name} is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with {Plugin Name}. If not, see {License URI}.
*/
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";
// create custom scheduler from custom option
add_filter( 'cron_schedules', 'gcal_cron_interval' );
function gcal_cron_interval( $schedules ) {
$options = get_option('gcal_options');
$current = ( isset ($options['gcal_timer']) ? $options['gcal_timer'] : 60 ); // default 60 minutes
$interval = 60 * $current; // wir speichern Minuten
$schedules['gcal_interval'] = array(
'interval' => $interval,
'display' => esc_html__( 'GCal fetch interval' ),
);
return $schedules;
}
/**
* Initializes the plugin and creates a table
*
* @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.
*
* Since there is no install hook in WP, we will use the activation hook for both.
*/
function gcal_import_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;
$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,
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' );
}
gcal_error_log (INFO, "gcal_import activated");
// empty geocode cache if option is set.
$options = get_option('gcal_options');
if ( isset ( $options['gcal_reset_cache'] ) && '1' == $options['gcal_reset_cache'] ) {
$wpdb->query("DELETE IGNORE FROM $table WHERE 1=1");
gcal_error_log (INFO, "emptied geocoding cache");
}
}
register_activation_hook( __FILE__, 'gcal_import_activate' );
/**
* Deactivate unregisters the scheduling function.
*
* @since 0.1.0
*
*/
function gcal_import_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");
}
register_deactivation_hook( __FILE__, 'gcal_import_deactivate' );
/**
* Uninstall drops our DB table
*
* @since 0.1.0
*
*/
function gcal_import_uninstall()
{
// clean up! Many plugins forget the housekeeping when uninstalling.
gcal_error_log (INFO, "uninstalling gcal_import");
// can we uninstall without deactivating first?
// gcal_import_deactivate;
global $wpdb;
// drop the geocache table
$table = $wpdb->prefix.GCAL_GEO_TABLE ;
$wpdb->query( "DROP TABLE IF EXISTS $table" );
// and the options.
delete_option ( 'gcal_options' );
}
register_uninstall_hook( __FILE__, 'gcal_import_uninstall' );
/*
* Debug logging if debugging is activated
*
* @since 0.3.0
*/
function gcal_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 );
}
}
}

@ -1 +1 @@
Subproject commit e180dea8447c9e78f92306f892f809d5f5ad2948
Subproject commit 667aa26e1bd498c3558405f182452beb9d5a8757

88
readme.txt Normal file
View file

@ -0,0 +1,88 @@
=== 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.

View file

@ -1,202 +0,0 @@
<?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;
*/
}

View file

@ -1,10 +0,0 @@
<?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);
?>

View file

@ -1,158 +0,0 @@
<?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);
}

View file

@ -1,152 +0,0 @@
<?php
/**
* 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.9
* Author: Harald Milz & Netzbegrünung e.V.
* License: GPLv3
* License URI: https://www.gnu.org/licenses/gpl-3.0
* Domain Path: /languages
*
* {Plugin Name} is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* any later version.
*
* {Plugin Name} is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with {Plugin Name}. If not, see {License URI}.
*/
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
// The real work goes here.
require_once dirname( __FILE__ ) . "/wolkal3000-worker.php";
require_once dirname( __FILE__ ) . "/wolkal3000-admin.php";
// create custom scheduler from custom option
add_filter( 'cron_schedules', 'wolkal3000_cron_interval' );
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['wolkal3000_interval'] = array(
'interval' => $interval,
'display' => esc_html__( 'Calendar fetch interval' ),
);
return $schedules;
}
/**
* Initializes the plugin and creates a table
*
* @since 0.1.0
*
* - 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 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.WOLKAL3000_GEO_TABLE;
$query = "CREATE TABLE IF NOT EXISTS $table (
id INT(9) NOT NULL AUTO_INCREMENT,
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( 'wolkal3000_worker_hook' ) ) {
wp_schedule_event( time(), 'wolkal3000_interval', 'wolkal3000_worker_hook' );
}
wolkal3000_error_log (INFO, "wolkal3000 activated");
// empty geocode cache if option is set.
$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");
wolkal3000_error_log (INFO, "emptied geocoding cache");
}
}
register_activation_hook( __FILE__, 'wolkal3000_activate' );
/**
* Deactivate unregisters the scheduling function.
*
* @since 0.1.0
*
*/
function wolkal3000_deactivate()
{
// clean up! Many plugins forget the housekeeping when deactivating.
wp_clear_scheduled_hook('wolkal3000_worker_hook');
wolkal3000_error_log (INFO, "wolkal3000 deactivated");
}
register_deactivation_hook( __FILE__, 'wolkal3000_deactivate' );
/**
* Uninstall drops our DB table
*
* @since 0.1.0
*
*/
function wolkal3000_uninstall()
{
// clean up! Many plugins forget the housekeeping when uninstalling.
wolkal3000_error_log (INFO, "uninstalling wolkal3000");
// can we uninstall without deactivating first?
// wolkal3000_deactivate;
global $wpdb;
// drop the geocache table
$table = $wpdb->prefix.WOLKAL3000_GEO_TABLE ;
$wpdb->query( "DROP TABLE IF EXISTS $table" );
// and the options.
delete_option ( 'wolkal3000_options' );
}
register_uninstall_hook( __FILE__, 'wolkal3000_uninstall' );
/*
* Debug logging if debugging is activated
*
* @since 0.3.0
*/
function wolkal3000_error_log($level, $args) {
$levels = array ( 'NONE', 'CRIT', 'WARN', 'INFO' );
$options = get_option('wolkal3000_options');
if ( isset ( $options['wolkal3000_debugging'] )) {
if ( $level <= (int) $options['wolkal3000_debugging'] ) {
error_log ( "WolKal3000 [" . $levels[$level] . "] " . $args );
}
}
}